Skip to content

Commit

Permalink
Merge pull request #1071 from nextcloud/backport/1026/stable0.7
Browse files Browse the repository at this point in the history
[stable0.7] enh: Delete Application and its shares
  • Loading branch information
blizzz committed May 6, 2024
2 parents a9cd9e9 + 23cce3e commit c18020c
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 17 deletions.
10 changes: 10 additions & 0 deletions lib/Db/ContextNodeRelationMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/** @template-extends QBMapper<ContextNodeRelation> */
Expand All @@ -18,6 +19,15 @@ public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, ContextNodeRelation::class);
}

public function deleteAllByContextId(int $contextId): void {
$qb = $this->db->getQueryBuilder();

$qb->delete($this->tableName)
->where($qb->expr()->eq('context_id', $qb->createNamedParameter($contextId, IQueryBuilder::PARAM_INT)));

$qb->executeStatement();
}

/**
* @throws MultipleObjectsReturnedException
* @throws DoesNotExistException
Expand Down
10 changes: 10 additions & 0 deletions lib/Db/PageContentMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/** @template-extends QBMapper<PageContent> */
Expand Down Expand Up @@ -57,4 +58,13 @@ public function findByNodeRelation(int $nodeRelId): array {

return $this->findEntities($qb);
}

public function deleteByPageId(int $pageId): int {
$qb = $this->db->getQueryBuilder();

$qb->delete($this->table)
->where($qb->expr()->eq('page_id', $qb->createNamedParameter($pageId, IQueryBuilder::PARAM_INT)));

return $qb->executeStatement();
}
}
26 changes: 26 additions & 0 deletions lib/Db/PageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace OCA\Tables\Db;

use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/** @template-extends QBMapper<Page> */
Expand All @@ -12,4 +13,29 @@ class PageMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, Page::class);
}

public function getPageIdsForContext(int $contextId): array {
$qb = $this->db->getQueryBuilder();

$qb->select('id')
->from($this->table)
->where($qb->expr()->eq('context_id', $qb->createNamedParameter($contextId, IQueryBuilder::PARAM_INT)));

$result = $qb->executeQuery();
$pageIds = [];
while ($row = $result->fetch()
) {
$pageIds[] = $row['id'];
}
return $pageIds;
}

public function deleteByPageId(int $pageId): int {
$qb = $this->db->getQueryBuilder();

$qb->delete($this->table)
->where($qb->expr()->eq('id', $qb->createNamedParameter($pageId, IQueryBuilder::PARAM_INT)));

return $qb->executeStatement();
}
}
16 changes: 15 additions & 1 deletion lib/Service/ContextService.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ContextService {
private IUserManager $userManager;
private IEventDispatcher $eventDispatcher;
private IDBConnection $dbc;
private ShareService $shareService;

public function __construct(
ContextMapper $contextMapper,
Expand All @@ -51,6 +52,7 @@ public function __construct(
IUserManager $userManager,
IEventDispatcher $eventDispatcher,
IDBConnection $dbc,
ShareService $shareService,
bool $isCLI,
) {
$this->contextMapper = $contextMapper;
Expand All @@ -63,6 +65,7 @@ public function __construct(
$this->userManager = $userManager;
$this->eventDispatcher = $eventDispatcher;
$this->dbc = $dbc;
$this->shareService = $shareService;
}
use TTransactional;

Expand Down Expand Up @@ -233,7 +236,18 @@ public function update(int $contextId, string $userId, ?string $name, ?string $i
*/
public function delete(int $contextId, string $userId): Context {
$context = $this->contextMapper->findById($contextId, $userId);
return $this->contextMapper->delete($context);

$this->atomic(function () use ($context): void {
$this->shareService->deleteAllForContext($context);
$this->contextNodeRelMapper->deleteAllByContextId($context->getId());
$pageIds = $this->pageMapper->getPageIdsForContext($context->getId());
foreach ($pageIds as $pageId) {
$this->pageContentMapper->deleteByPageId($pageId);
$this->pageMapper->deleteByPageId($pageId);
}
$this->contextMapper->delete($context);
}, $this->dbc);
return $context;
}

/**
Expand Down
23 changes: 23 additions & 0 deletions lib/Service/ShareService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTime;

use InvalidArgumentException;
use OCA\Tables\Db\Context;
use OCA\Tables\Db\ContextNavigation;
use OCA\Tables\Db\ContextNavigationMapper;
use OCA\Tables\Db\Share;
Expand All @@ -24,13 +25,17 @@
use OCA\Tables\ResponseDefinitions;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\Exception;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;

/**
* @psalm-import-type TablesShare from ResponseDefinitions
*/
class ShareService extends SuperService {
use TTransactional;

protected ShareMapper $mapper;

protected TableMapper $tableMapper;
Expand All @@ -41,6 +46,7 @@ class ShareService extends SuperService {

protected GroupHelper $groupHelper;
private ContextNavigationMapper $contextNavigationMapper;
private IDBConnection $dbc;

public function __construct(
PermissionsService $permissionsService,
Expand All @@ -52,6 +58,7 @@ public function __construct(
UserHelper $userHelper,
GroupHelper $groupHelper,
ContextNavigationMapper $contextNavigationMapper,
IDBConnection $dbc,
) {
parent::__construct($logger, $userId, $permissionsService);
$this->mapper = $shareMapper;
Expand All @@ -60,6 +67,7 @@ public function __construct(
$this->userHelper = $userHelper;
$this->groupHelper = $groupHelper;
$this->contextNavigationMapper = $contextNavigationMapper;
$this->dbc = $dbc;
}


Expand Down Expand Up @@ -418,6 +426,21 @@ public function deleteAllForView(View $view):void {
}
}

public function deleteAllForContext(Context $context): void {
try {
$this->atomic(function () use ($context) {
$shares = $this->mapper->findAllSharesForNode('context', $context->getId(), $this->userId);
foreach ($shares as $share) {
/** @var Share $share */
$this->contextNavigationMapper->deleteByShareId($share->getId());
}
$this->mapper->deleteByNode($context->getId(), 'context');
}, $this->dbc);
} catch (Exception $e) {
$this->logger->error('something went wrong while deleting shares for context: '.$context->getId());
}
}

/**
* @throws InternalError
* @return Share[]
Expand Down
2 changes: 1 addition & 1 deletion src/modules/modals/CreateContext.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export default {
if (res) {
return res.id
} else {
showError(t('tables', 'Could not create new table'))
showError(t('tables', 'Could not create new application'))
}
},
reset() {
Expand Down
55 changes: 55 additions & 0 deletions src/modules/modals/DeleteContext.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<template>
<div>
<DialogConfirmation :description="getTranslatedDescription"
:title="t('tables', 'Confirm application deletion')"
:cancel-title="t('tables', 'Cancel')"
:confirm-title="t('tables', 'Delete')"
confirm-class="error"
:show-modal="showModal"
@confirm="deleteContext"
@cancel="$emit('cancel')" />
</div>
</template>

<script>
import DialogConfirmation from '../../shared/modals/DialogConfirmation.vue'
import { showSuccess } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/dist/index.css'
import { mapState } from 'vuex'
export default {
components: {
DialogConfirmation,
},
props: {
context: {
type: Object,
default: null,
},
showModal: {
type: Boolean,
default: false,
},
},
computed: {
...mapState(['activeContextId']),
getTranslatedDescription() {
return t('tables', 'Do you really want to delete the application "{context}"? This will also delete the shares and unshare the resources that are connected to this application.', { context: this.context?.name })
},
},
methods: {
async deleteContext() {
const res = await this.$store.dispatch('removeContext', { context: this.context, receivers: this.context.sharing })
if (res) {
showSuccess(t('tables', 'Application "{context}" removed.', { context: this.context.name }))
// if the active context was deleted, go to startpage
if (this.context.id === this.activeContextId) {
await this.$router.push('/').catch(err => err)
}
this.$emit('cancel')
}
},
},
}
</script>
62 changes: 54 additions & 8 deletions src/modules/modals/EditContext.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,22 @@
<NcContextResource :resources.sync="resources" :receivers.sync="receivers" />
</div>

<div class="row space-R row space-T">
<div class="fix-col-4 end">
<NcButton type="primary" @click="submit">
{{ t('tables', 'Save') }}
<div class="row space-T">
<div class="fix-col-4 space-T justify-between">
<NcButton v-if="!prepareDeleteContext" type="error" @click="prepareDeleteContext = true">
{{ t('tables', 'Delete') }}
</NcButton>
<NcButton v-if="prepareDeleteContext" :wide="true" type="error" @click="actionDeleteContext">
{{ t('tables', 'I really want to delete this application!') }}
</NcButton>
<div class="right-additional-button">
<NcButton v-if="ownsContext(localContext)" @click="actionTransfer">
{{ t('tables', 'Transfer application') }}
</NcButton>
<NcButton type="primary" @click="submit">
{{ t('tables', 'Save') }}
</NcButton>
</div>
</div>
</div>
</div>
Expand All @@ -58,6 +69,8 @@ import NcIconPicker from '../../shared/components/ncIconPicker/NcIconPicker.vue'
import { NODE_TYPE_TABLE, NODE_TYPE_VIEW, PERMISSION_READ, PERMISSION_CREATE, PERMISSION_UPDATE, PERMISSION_DELETE } from '../../shared/constants.js'
import svgHelper from '../../shared/components/ncIconPicker/mixins/svgHelper.js'
import permissionBitmask from '../../shared/components/ncContextResource/mixins/permissionBitmask.js'
import { emit } from '@nextcloud/event-bus'
import permissionsMixin from '../../shared/components/ncTable/mixins/permissionsMixin.js'
export default {
name: 'EditContext',
Expand All @@ -68,7 +81,7 @@ export default {
NcIconSvgWrapper,
NcContextResource,
},
mixins: [svgHelper, permissionBitmask],
mixins: [svgHelper, permissionBitmask, permissionsMixin],
props: {
showModal: {
type: Boolean,
Expand All @@ -94,12 +107,12 @@ export default {
PERMISSION_CREATE,
PERMISSION_UPDATE,
PERMISSION_DELETE,
prepareDeleteContext: false,
}
},
computed: {
...mapGetters(['getContext']),
...mapState(['tables', 'views']),
...mapState(['tables', 'views', 'activeContextId']),
localContext() {
return this.getContext(this.contextId)
},
Expand Down Expand Up @@ -153,7 +166,7 @@ export default {
const res = await this.$store.dispatch('updateContext', { id: this.contextId, data, previousReceivers: Object.values(context.sharing), receivers: this.receivers })
if (res) {
showSuccess(t('tables', 'Updated context "{contextTitle}".', { contextTitle: this.title }))
showSuccess(t('tables', 'Updated application "{contextTitle}".', { contextTitle: this.title }))
this.actionCancel()
}
}
Expand All @@ -166,6 +179,7 @@ export default {
this.description = ''
this.resources = context ? this.getContextResources(context) : []
this.receivers = context ? this.getContextReceivers(context) : []
this.prepareDeleteContext = false
},
getContextReceivers(context) {
let sharing = Object.values(context.sharing)
Expand Down Expand Up @@ -209,6 +223,38 @@ export default {
}
return resources
},
async actionDeleteContext() {
this.prepareDeleteContext = false
const context = this.getContext(this.contextId)
const res = await this.$store.dispatch('removeContext', { context, receivers: context.sharing })
if (res) {
showSuccess(t('tables', 'Application "{context}" removed.', { context: this.title }))
// if the active context was deleted, go to startpage
if (this.contextId === this.activeContextId) {
await this.$router.push('/').catch(err => err)
}
this.actionCancel()
}
},
actionTransfer() {
emit('tables:context:edit', null)
emit('tables:context:transfer', this.localContext)
},
},
}
</script>
<style lang="scss" scoped>
.right-additional-button {
display: inline-flex;
}
.right-additional-button>button {
margin-left: calc(var(--default-grid-baseline) * 3);
}
:deep(.element-description) {
padding-inline: 0 !important;
max-width: 100%;
}
</style>
Loading

0 comments on commit c18020c

Please sign in to comment.