allow public users changing their name

Signed-off-by: dartcafe <github@dartcafe.de>
This commit is contained in:
dartcafe 2022-07-08 22:50:48 +02:00
Родитель 312d6990f5
Коммит 2aae76db80
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CCE73CEF3035D3C8
10 изменённых файлов: 188 добавлений и 4 удалений

Просмотреть файл

@ -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