Skip to content

Commit

Permalink
transfer ownership of a form
Browse files Browse the repository at this point in the history
Signed-off-by: hamza221 <hamzamahjoubi221@gmail.com>
  • Loading branch information
hamza221 committed Dec 7, 2022
1 parent 674d340 commit ade1ea6
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 7 deletions.
8 changes: 8 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@
'apiVersion' => 'v2'
]
],
[
'name' => 'api#ownerTransfer',
'url' => '/api/{apiVersion}/form/transfer',
'verb' => 'POST',
'requirements' => [
'apiVersion' => 'v2'
]
],

// Questions
[
Expand Down
45 changes: 45 additions & 0 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1136,4 +1136,49 @@ public function exportSubmissionsToCloud(string $hash, string $path) {

return new DataResponse($fileName);
}
/**
* @NoAdminRequired
*
* Transfer ownership of a form to another user
*
* @param int $formId id of the form to update
* @param string $uid id of the new owner
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function ownerTransfer(int $formId, string $uid): DataResponse {
$this->logger->debug('Updating owner: formId: {formId}, userId: {uid}', [
'formId' => $formId,
'uid' => $uid
]);

try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException('Could not find form');
}

if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}

// update form owner
$form->setOwnerId($uid);

// Update changed Columns in Db.
$this->formMapper->update($form);

//delete this form from shares for the new owner
try {
$share = $this->shareMapper->findPublicShareByFormIdAndUid($formId, $uid);
$this->shareMapper->deleteById($share->getId());
} catch (IMapperException $e) {
$this->logger->debug('No shares found');
}

return new DataResponse($form->getOwnerId());
}
}
21 changes: 21 additions & 0 deletions lib/Db/ShareMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,28 @@ public function findPublicShareByHash(string $hash): Share {

return $this->findEntity($qb);
}
/**
* Find Share by formId and user id
* @param int $formId
* @param string $uid
* @return Share
* @throws MultipleObjectsReturnedException if more than one result
* @throws DoesNotExistException if not found
*/
public function findPublicShareByFormIdAndUid(int $formId, string $uid): Share {
$qb = $this->db->getQueryBuilder();

$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
)
->andWhere(
$qb->expr()->eq('share_with', $qb->createNamedParameter($uid, IQueryBuilder::PARAM_STR))
);

return $this->findEntity($qb);
}
/**
* Delete a share
* @param int $id of the share.
Expand Down
15 changes: 14 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@nextcloud/browserslist-config": "^2.3.0",
"@nextcloud/eslint-config": "^8.1.4",
"@nextcloud/stylelint-config": "^2.3.0",
"@nextcloud/webpack-vue-config": "^5.4.0"
"@nextcloud/webpack-vue-config": "^5.4.0",
"@types/debounce": "^1.2.1"
}
}
112 changes: 109 additions & 3 deletions src/Forms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,27 +96,62 @@
:form="selectedForm"
:opened.sync="sidebarOpened"
:active.sync="sidebarActive"
name="sidebar" />
name="sidebar"
@transfer:ownership="openModal" />
</template>
<NcModal v-if="modal"
ref="modal"
size="normal"
:title="'Transfer '+selectedForm.title"
name="NcModal"
:outTransition="true"
@close="closeModal">
<div class="modal__content">
<h1 class="modal_section">{{ t('forms', 'Transfer ') }} {{ selectedForm.title}}</h1>
<div class="modal_section">
<p class="modal_text">{{ t('forms', 'Account to transfer to') }}</p>
<SharingSearchDiv :isOwnershipTransfer="true" @add-share="setNewOwner" />
<div class="selected_user" v-if="transferData.displayName.length>0">
<p>{{transferData.displayName}}</p>
<NcButton type="tertiary-no-background" @click="clearSelected">
X
</NcButton>
</div>
</div>
<div class="modal_section">
<p class="modal_text">{{ t('forms', 'Type ') }} {{confirmationString}} {{ t('forms', ' to confirm') }}</p>
<NcTextField :value.sync="confirmation" :success="confirmation===confirmationString" :show-trailing-button="confirmation !== ''"
@trailing-button-click="clearText" >
</NcTextField>
</div>
<NcButton :disabled="confirmation!=confirmationString" type="error" @click="onOwnershipTransfer">
{{ t('forms', 'I understand, transfer this form') }}
</NcButton>
</div>
</NcModal>
</NcContent>
</template>

<script>
import { emit } from '@nextcloud/event-bus'
import { generateOcsUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import { showError, showSuccess } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
import NcAppNavigationCaption from '@nextcloud/vue/dist/Components/NcAppNavigationCaption.js'
import NcAppNavigationNew from '@nextcloud/vue/dist/Components/NcAppNavigationNew.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcContent from '@nextcloud/vue/dist/Components/NcContent.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import SharingSearchDiv from './components/SidebarTabs/SharingSearchDiv.vue'
import IconPlus from 'vue-material-design-icons/Plus.vue'
Expand All @@ -141,23 +176,34 @@ export default {
NcContent,
NcEmptyContent,
NcLoadingIcon,
NcModal,
SharingSearchDiv,
NcTextField,
},
mixins: [isMobile, PermissionTypes],
data() {
return {
loading: true,
modal: false,
sidebarOpened: false,
sidebarActive: 'forms-sharing',
forms: [],
sharedForms: [],
transferData: { formId: null, userId: null ,displayName: ''},
confirmation:'',
canCreateForms: loadState(appName, 'appConfig').canCreateForms,
}
},
computed: {
canEdit() {
return this.selectedForm.permissions.includes(this.PERMISSION_TYPES.PERMISSION_EDIT)
},
confirmationString(){
return `${this.selectedForm.ownerId}/${this.selectedForm.title}`
},
hasForms() {
return !this.noOwnedForms || !this.noSharedForms
},
Expand Down Expand Up @@ -215,12 +261,54 @@ export default {
},
},
},
beforeMount() {
this.loadForms()
},
methods: {
clearSelected(){
this.transferData= { formId: null, userId: null ,displayName: ''}
},
clearText() {
this.confirmation = ''
},
setNewOwner(share){
console.log(share)
this.transferData.userId=share.shareWith
this.transferData.formId=this.selectedForm.id
this.transferData.displayName=share.displayName
},
closeModal() {
this.modal = false
showError(t('forms', 'Ownership transfer was Cancelled'))
},
openModal() {
this.modal = true
},
async onOwnershipTransfer() {
this.modal = false
if (this.transferData.formId && this.transferData.userId) {
try {
await axios.post(generateOcsUrl('apps/forms/api/v2/form/transfer'), {
formId: this.transferData.formId,
uid: this.transferData.userId,
})
showSuccess(`${t('forms', 'This form is now owned by')} ${this.transferData.displayName}`)
this.$router.push({ name: 'root' })
} catch (error) {
logger.error('Error while transfering form ownership', { error })
showError(t('forms', 'An error occurred while transfering ownership'))
}
} else {
logger.error('Null parameters while transfering form ownership', { transferData: this.tranferData })
showError(t('forms', 'An error occurred while transfering ownership'))
}
},
/**
* Closes the App-Navigation on mobile-devices
*/
Expand Down Expand Up @@ -348,3 +436,21 @@ export default {
},
}
</script>
<style scoped>
.modal__content {
margin: 50px;
}
.modal_text{
text-align: start;
margin-bottom: 10px;
}
.modal_section{
margin-bottom: 20px;
}
.selected_user{
display: flex;
align-items: center;
}
</style>
15 changes: 15 additions & 0 deletions src/components/SidebarTabs/SettingsSidebarTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,29 @@
type="datetime"
@change="onExpirationDateChange" />
</div>
<div>
<NcButton @click="onChangeOwner">
{{ t('forms', 'Transfer ownership') }}
</NcButton>
</div>
</div>
</template>

<script>
import moment from '@nextcloud/moment'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcDatetimePicker from '@nextcloud/vue/dist/Components/NcDatetimePicker.js'
import ShareTypes from '../../mixins/ShareTypes.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
export default {
components: {
NcCheckboxRadioSwitch,
NcDatetimePicker,
NcButton,
NcEmptyContent,
},
mixins: [ShareTypes],
Expand All @@ -85,6 +95,7 @@ export default {
stringify: this.stringifyDate,
parse: this.parseTimestampToDate,
},
}
},
Expand Down Expand Up @@ -196,6 +207,10 @@ export default {
notBeforeNow(datetime) {
return datetime < moment().toDate()
},
onChangeOwner() {
this.localLoading=true
this.$emit('transfer:ownership')
},
},
}
</script>
Expand Down
7 changes: 6 additions & 1 deletion src/components/SidebarTabs/SharingSearchDiv.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<template>
<div>
<NcMultiselect :clear-on-select="false"
:close-on-select="false"
:close-on-select="isOwnershipTransfer"
:hide-selected="true"
:internal-search="false"
:loading="showLoadingCircle"
Expand Down Expand Up @@ -74,6 +74,11 @@ export default {
type: Boolean,
default: false,
},
isOwnershipTransfer: {
type:Boolean,
default: false,
required:false
}
},
data() {
Expand Down
Loading

0 comments on commit ade1ea6

Please sign in to comment.