allow public users changing their name
Signed-off-by: dartcafe <github@dartcafe.de>
This commit is contained in:
Родитель
312d6990f5
Коммит
2aae76db80
|
@ -31,6 +31,7 @@ return [
|
|||
['name' => 'public#get_votes', 'url' => '/s/{token}/votes', 'verb' => 'GET'],
|
||||
['name' => 'public#get_subscription', 'url' => '/s/{token}/subscription', 'verb' => 'GET'],
|
||||
['name' => 'public#set_email_address', 'url' => '/s/{token}/email', 'verb' => 'PUT'],
|
||||
['name' => 'public#set_display_name', 'url' => '/s/{token}/name/{displayName}', 'verb' => 'PUT'],
|
||||
['name' => 'public#delete_email_address', 'url' => '/s/{token}/email', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'public#set_vote', 'url' => '/s/{token}/vote', 'verb' => 'PUT'],
|
||||
|
|
|
@ -51,6 +51,7 @@ use OCA\Polls\Event\PollOwnerChangeEvent;
|
|||
use OCA\Polls\Event\PollRestoredEvent;
|
||||
use OCA\Polls\Event\PollTakeoverEvent;
|
||||
use OCA\Polls\Event\PollUpdatedEvent;
|
||||
use OCA\Polls\Event\ShareChangedDisplayNameEvent;
|
||||
use OCA\Polls\Event\ShareCreateEvent;
|
||||
use OCA\Polls\Event\ShareTypeChangedEvent;
|
||||
use OCA\Polls\Event\ShareChangedEmailEvent;
|
||||
|
@ -103,6 +104,7 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerEventListener(PollTakeoverEvent::class, PollListener::class);
|
||||
$context->registerEventListener(PollUpdatedEvent::class, PollListener::class);
|
||||
$context->registerEventListener(ShareChangedEmailEvent::class, ShareListener::class);
|
||||
$context->registerEventListener(ShareChangedDisplayNameEvent::class, ShareListener::class);
|
||||
$context->registerEventListener(ShareChangedRegistrationConstraintEvent::class, ShareListener::class);
|
||||
$context->registerEventListener(ShareCreateEvent::class, ShareListener::class);
|
||||
$context->registerEventListener(ShareDeletedEvent::class, ShareListener::class);
|
||||
|
|
|
@ -301,6 +301,16 @@ class PublicController extends Controller {
|
|||
return $this->response(fn () => ['result' => $this->systemService->validateEmailAddress($emailAddress), 'emailAddress' => $emailAddress]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change displayName
|
||||
* @PublicPage
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function setDisplayName(string $token, string $displayName): JSONResponse {
|
||||
return $this->response(fn () => ['share' => $this->shareService->setDisplayname($token, $displayName)]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set EmailAddress
|
||||
* @PublicPage
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 René Gieling <github@dartcafe.de>
|
||||
*
|
||||
* @author René Gieling <github@dartcafe.de>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Polls\Event;
|
||||
|
||||
use OCA\Polls\Db\Share;
|
||||
|
||||
class ShareChangedDisplayNameEvent extends ShareEvent {
|
||||
public function __construct(Share $share) {
|
||||
parent::__construct($share);
|
||||
$this->activitySubject = self::CHANGE_DISPLAY_NAME;
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ abstract class ShareEvent extends BaseEvent {
|
|||
public const ADD = 'share_add';
|
||||
public const ADD_PUBLIC = 'share_add_public';
|
||||
public const CHANGE_EMAIL = 'share_change_email';
|
||||
public const CHANGE_DISPLAY_NAME = 'share_change_display_name';
|
||||
public const CHANGE_TYPE = 'share_change_type';
|
||||
public const CHANGE_REG_CONSTR = 'share_change_reg_const';
|
||||
public const REGISTRATION = 'share_registration';
|
||||
|
|
|
@ -30,7 +30,13 @@ use OCA\Polls\Exceptions\InvalidClassException;
|
|||
class ShareListener extends BaseListener {
|
||||
|
||||
/** @var array */
|
||||
protected $watchTables = [Watch::OBJECT_SHARES, Watch::OBJECT_POLLS];
|
||||
protected $watchTables = [
|
||||
Watch::OBJECT_SHARES,
|
||||
Watch::OBJECT_POLLS,
|
||||
Watch::OBJECT_VOTES,
|
||||
Watch::OBJECT_OPTIONS,
|
||||
Watch::OBJECT_COMMENTS
|
||||
];
|
||||
|
||||
protected function checkClass() : void {
|
||||
if (!($this->event instanceof ShareEvent)) {
|
||||
|
|
|
@ -184,6 +184,10 @@ class ActivityService {
|
|||
return $this->userIsActor
|
||||
? $this->l10n->t('You have changed your email address')
|
||||
: $this->l10n->t('{sharee} has changed his email address');
|
||||
case ShareEvent::CHANGE_DISPLAY_NAME:
|
||||
return $this->userIsActor
|
||||
? $this->l10n->t('You have changed your name')
|
||||
: $this->l10n->t('{sharee} has changed his name');
|
||||
case ShareEvent::CHANGE_TYPE:
|
||||
return $this->userIsActor
|
||||
? $this->l10n->t('You have changed the share type')
|
||||
|
@ -330,6 +334,10 @@ class ActivityService {
|
|||
return $this->userIsActor
|
||||
? $this->l10n->t('You have changed your email address')
|
||||
: $this->l10n->t('Email address of {sharee} has been changed');
|
||||
case ShareEvent::CHANGE_DISPLAY_NAME:
|
||||
return $this->userIsActor
|
||||
? $this->l10n->t('You have changed your name')
|
||||
: $this->l10n->t('Displaynname of {sharee} has been changed');
|
||||
case ShareEvent::CHANGE_TYPE:
|
||||
return $this->userIsActor
|
||||
? $this->l10n->t('You have changed the share type')
|
||||
|
|
|
@ -40,6 +40,7 @@ use OCA\Polls\Exceptions\InvalidUsernameException;
|
|||
use OCA\Polls\Db\PollMapper;
|
||||
use OCA\Polls\Db\ShareMapper;
|
||||
use OCA\Polls\Db\Share;
|
||||
use OCA\Polls\Event\ShareChangedDisplayNameEvent;
|
||||
use OCA\Polls\Event\ShareCreateEvent;
|
||||
use OCA\Polls\Event\ShareTypeChangedEvent;
|
||||
use OCA\Polls\Event\ShareChangedEmailEvent;
|
||||
|
@ -342,8 +343,7 @@ class ShareService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set emailAddress to personal share
|
||||
* or update an email share with the username
|
||||
* Set emailAddress of personal share
|
||||
*
|
||||
* @return Share
|
||||
*/
|
||||
|
@ -368,6 +368,32 @@ class ShareService {
|
|||
return $this->share;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set displayName of personal share
|
||||
*
|
||||
* @return Share
|
||||
*/
|
||||
public function setDisplayName(string $token, string $displayName): Share {
|
||||
try {
|
||||
$this->share = $this->shareMapper->findByToken($token);
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new NotFoundException('Token ' . $token . ' does not exist');
|
||||
}
|
||||
|
||||
if ($this->share->getType() === Share::TYPE_EXTERNAL) {
|
||||
$this->systemService->validatePublicUsername($displayName, $token);
|
||||
$this->share->setDisplayName($displayName);
|
||||
// TODO: Send confirmation
|
||||
$this->share = $this->shareMapper->update($this->share);
|
||||
} else {
|
||||
throw new InvalidShareTypeException('Displayname can only be changed in external shares.');
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatchTyped(new ShareChangedDisplayNameEvent($this->share));
|
||||
|
||||
return $this->share;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete emailAddress of personal share
|
||||
*
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
<ActionInput v-if="$route.name === 'publicVote'"
|
||||
:class="check.status"
|
||||
:value="emailAddressTemp"
|
||||
@click="deleteEmailAddress"
|
||||
@update:value="validateEmailAddress"
|
||||
@submit="submitEmailAddress">
|
||||
<template #icon>
|
||||
|
@ -40,6 +39,16 @@
|
|||
</template>
|
||||
{{ t('polls', 'Edit Email Address') }}
|
||||
</ActionInput>
|
||||
<ActionInput v-if="$route.name === 'publicVote'"
|
||||
:class="checkDisplayName.status"
|
||||
:value="displayNameTemp"
|
||||
@update:value="validateDisplayName"
|
||||
@submit="submitDisplayName">
|
||||
<template #icon>
|
||||
<EditAccountIcon />
|
||||
</template>
|
||||
{{ t('polls', 'Change name') }}
|
||||
</ActionInput>
|
||||
<ActionButton v-if="$route.name === 'publicVote'"
|
||||
:disabled="!emailAddress"
|
||||
:value="emailAddress"
|
||||
|
@ -92,6 +101,7 @@ import { generateUrl } from '@nextcloud/router'
|
|||
import { Actions, ActionButton, ActionCheckbox, ActionInput, ActionSeparator } from '@nextcloud/vue'
|
||||
import { mapState } from 'vuex'
|
||||
import SettingsIcon from 'vue-material-design-icons/Cog.vue'
|
||||
import EditAccountIcon from 'vue-material-design-icons/AccountEdit.vue'
|
||||
import EditEmailIcon from 'vue-material-design-icons/EmailEditOutline.vue'
|
||||
import SendLinkPerEmailIcon from 'vue-material-design-icons/LinkVariant.vue'
|
||||
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
|
||||
|
@ -110,6 +120,7 @@ export default {
|
|||
ActionInput,
|
||||
ActionSeparator,
|
||||
SettingsIcon,
|
||||
EditAccountIcon,
|
||||
EditEmailIcon,
|
||||
LogoutIcon,
|
||||
SendLinkPerEmailIcon,
|
||||
|
@ -120,10 +131,14 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
displayNameTemp: '',
|
||||
emailAddressTemp: '',
|
||||
checkResult: '',
|
||||
checkStatus: '',
|
||||
checking: false,
|
||||
displayNameCheckResult: '',
|
||||
displayNameCheckStatus: '',
|
||||
displayNameChecking: false,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -133,6 +148,7 @@ export default {
|
|||
share: (state) => state.share,
|
||||
subscribed: (state) => state.subscription.subscribed,
|
||||
emailAddress: (state) => state.share.emailAddress,
|
||||
displayName: (state) => state.poll.acl.displayName,
|
||||
}),
|
||||
|
||||
hasCookie() {
|
||||
|
@ -143,6 +159,10 @@ export default {
|
|||
return this.emailAddress === this.emailAddressTemp
|
||||
},
|
||||
|
||||
displayNameUnchanged() {
|
||||
return this.displayName === this.displayNameTemp
|
||||
},
|
||||
|
||||
check() {
|
||||
if (this.checking) {
|
||||
return {
|
||||
|
@ -164,6 +184,27 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
checkDisplayName() {
|
||||
if (this.displayNameChecking) {
|
||||
return {
|
||||
result: t('polls', 'Checking name …'),
|
||||
status: 'checking',
|
||||
}
|
||||
}
|
||||
|
||||
if (this.displayNameUnchanged) {
|
||||
return {
|
||||
result: '',
|
||||
status: '',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: this.displayNameCheckResult,
|
||||
status: this.displayNameCheckStatus,
|
||||
}
|
||||
},
|
||||
|
||||
personalLink() {
|
||||
return window.location.origin
|
||||
+ this.$router.resolve({
|
||||
|
@ -177,10 +218,14 @@ export default {
|
|||
emailAddress() {
|
||||
this.emailAddressTemp = this.emailAddress
|
||||
},
|
||||
displayName() {
|
||||
this.displayNameTemp = this.displayName
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.emailAddressTemp = this.emailAddress
|
||||
this.displayNameTemp = this.displayName
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -223,6 +268,27 @@ export default {
|
|||
}
|
||||
}, 500),
|
||||
|
||||
validateDisplayName: debounce(async function(value) {
|
||||
const endpoint = 'apps/polls/check/username'
|
||||
|
||||
this.displayNameTemp = value
|
||||
try {
|
||||
this.displayNameChecking = true
|
||||
await axios.post(generateUrl(endpoint), {
|
||||
headers: { Accept: 'application/json' },
|
||||
userName: this.displayNameTemp,
|
||||
token: this.$route.params.token,
|
||||
})
|
||||
this.displayNameCheckResult = t('polls', 'valid name.')
|
||||
this.displayNameCheckStatus = 'success'
|
||||
} catch {
|
||||
this.displayNameCheckResult = t('polls', 'Invalid email address.')
|
||||
this.displayNameCheckStatus = 'error'
|
||||
} finally {
|
||||
this.displayNameChecking = false
|
||||
}
|
||||
}, 500),
|
||||
|
||||
async submitEmailAddress() {
|
||||
try {
|
||||
await this.$store.dispatch('share/updateEmailAddress', { emailAddress: this.emailAddressTemp })
|
||||
|
@ -232,6 +298,15 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async submitDisplayName() {
|
||||
try {
|
||||
await this.$store.dispatch('share/updateDisplayName', { displayName: this.displayNameTemp })
|
||||
showSuccess(t('polls', 'Name changed.'))
|
||||
} catch {
|
||||
showError(t('polls', 'Error changing name.'))
|
||||
}
|
||||
},
|
||||
|
||||
async resendInvitation() {
|
||||
try {
|
||||
const response = await this.$store.dispatch('share/resendInvitation')
|
||||
|
|
|
@ -124,6 +124,28 @@ const actions = {
|
|||
}
|
||||
},
|
||||
|
||||
async updateDisplayName(context, payload) {
|
||||
if (context.rootState.route.name !== 'publicVote') {
|
||||
return
|
||||
}
|
||||
|
||||
const endPoint = `apps/polls/s/${context.rootState.route.params.token}/name/${payload.displayName}`
|
||||
|
||||
try {
|
||||
const response = await axios.put(generateUrl(endPoint), {
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
context.commit('set', { share: response.data.share })
|
||||
context.dispatch('poll/get', null, { root: true })
|
||||
context.dispatch('comments/list', null, { root: true })
|
||||
context.dispatch('votes/list', null, { root: true })
|
||||
context.dispatch('options/list', null, { root: true })
|
||||
} catch (e) {
|
||||
console.error('Error changing name', { error: e.response }, { payload })
|
||||
throw e
|
||||
}
|
||||
},
|
||||
|
||||
async deleteEmailAddress(context, payload) {
|
||||
if (context.rootState.route.name !== 'publicVote') {
|
||||
return
|
||||
|
|
Загрузка…
Ссылка в новой задаче