User accounts can now be deleted with some UI to prevent accidental deletion
This commit is contained in:
@@ -28,10 +28,14 @@ const removeCreature = new ValidatedMethod({
|
||||
},
|
||||
run({charId}) {
|
||||
assertOwnership(charId, this.userId)
|
||||
Creatures.remove(charId);
|
||||
this.unblock();
|
||||
removeRelatedDocuments(charId);
|
||||
this.unblock();
|
||||
removeCreatureWork(charId)
|
||||
},
|
||||
});
|
||||
|
||||
export function removeCreatureWork(creatureId){
|
||||
Creatures.remove(creatureId);
|
||||
removeRelatedDocuments(creatureId);
|
||||
}
|
||||
|
||||
export default removeCreature;
|
||||
|
||||
@@ -118,10 +118,14 @@ const removeLibrary = new ValidatedMethod({
|
||||
run({_id}){
|
||||
let library = Libraries.findOne(_id);
|
||||
assertOwnership(library, this.userId);
|
||||
Libraries.remove(_id);
|
||||
this.unblock();
|
||||
LibraryNodes.remove({'ancestors.id': _id});
|
||||
this.unblock();
|
||||
removeLibaryWork(_id)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export function removeLibaryWork(libraryId){
|
||||
Libraries.remove(libraryId);
|
||||
LibraryNodes.remove({'ancestors.id': libraryId});
|
||||
}
|
||||
|
||||
export { LibrarySchema, insertLibrary, setLibraryDefault, updateLibraryName, removeLibrary };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import '/imports/api/users/deleteMyAccount.js';
|
||||
|
||||
const userSchema = new SimpleSchema({
|
||||
username: {
|
||||
|
||||
61
app/imports/api/users/deleteMyAccount.js
Normal file
61
app/imports/api/users/deleteMyAccount.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Libraries, {removeLibaryWork} from '/imports/api/library/Libraries.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import {removeCreatureWork} from '/imports/api/creature/removeCreature.js';
|
||||
|
||||
Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||
name: 'users.deleteMyAccount',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run(){
|
||||
let userId = Meteor.userId();
|
||||
if (!userId) throw new Meteor.Error('No user',
|
||||
'You must be logged into to delete your account');
|
||||
|
||||
// Delete all creatures
|
||||
let creatures = Creatures.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||
creatures.forEach(creature => removeCreatureWork(creature._id));
|
||||
|
||||
// Remove permissions from all creatures
|
||||
Creatures.update({
|
||||
$or: [
|
||||
{writers: userId},
|
||||
{readers: userId},
|
||||
],
|
||||
}, {
|
||||
$pull: {
|
||||
writers: userId,
|
||||
readers: userId
|
||||
},
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// Delete all libraries
|
||||
let libraries = Libraries.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||
libraries.forEach(library => removeLibaryWork(library._id));
|
||||
|
||||
// Remove permissions from all creatures
|
||||
Libraries.update({
|
||||
$or: [
|
||||
{writers: userId},
|
||||
{readers: userId},
|
||||
],
|
||||
}, {
|
||||
$pull: {
|
||||
writers: userId,
|
||||
readers: userId
|
||||
},
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// delete the account
|
||||
Meteor.users.remove(userId);
|
||||
}
|
||||
});
|
||||
@@ -7,3 +7,4 @@ import '/imports/server/publications/users.js';
|
||||
import '/imports/server/publications/icons.js';
|
||||
import '/imports/server/publications/tabletops.js';
|
||||
import '/imports/server/publications/slotFillers.js'
|
||||
import '/imports/server/publications/ownedDocuments.js'
|
||||
|
||||
15
app/imports/server/publications/ownedDocuments.js
Normal file
15
app/imports/server/publications/ownedDocuments.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
|
||||
Meteor.publish('ownedDocuments', function(){
|
||||
this.autorun(function (){
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
Creatures.find({owner: userId}),
|
||||
Libraries.find({owner: userId}),
|
||||
]
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import CreaturePropertyCreationDialog from '/imports/ui/creature/creaturePropert
|
||||
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
||||
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'
|
||||
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
||||
import DeleteUserAccountDialog from '/imports/ui/user/DeleteUserAccountDialog.vue';
|
||||
import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue';
|
||||
import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue';
|
||||
import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
||||
@@ -26,6 +27,7 @@ export default {
|
||||
CreaturePropertyDialog,
|
||||
CreaturePropertyFromLibraryDialog,
|
||||
DeleteConfirmationDialog,
|
||||
DeleteUserAccountDialog,
|
||||
ExperienceInsertDialog,
|
||||
ExperienceListDialog,
|
||||
InviteDialog,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
name="toolbar"
|
||||
/>
|
||||
<v-toolbar
|
||||
v-if="!$route.matched[0].components.toolbar"
|
||||
v-if="!$route.matched[0] || !$route.matched[0].components.toolbar"
|
||||
app
|
||||
color="secondary"
|
||||
dark
|
||||
|
||||
@@ -141,6 +141,19 @@
|
||||
</template>
|
||||
</v-list>
|
||||
</template>
|
||||
<v-layout
|
||||
row
|
||||
justify-end
|
||||
class="mt-3"
|
||||
>
|
||||
<v-btn
|
||||
color="error"
|
||||
data-id="delete-account-btn"
|
||||
@click="deleteAccount"
|
||||
>
|
||||
Delete Account
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -270,6 +283,12 @@
|
||||
this.updatePatreonLoading = false;
|
||||
if (error) this.updatePatreonError = error;
|
||||
});
|
||||
},
|
||||
deleteAccount(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'delete-user-account-dialog',
|
||||
elementId: 'delete-account-btn',
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
153
app/imports/ui/user/DeleteUserAccountDialog.vue
Normal file
153
app/imports/ui/user/DeleteUserAccountDialog.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template lang="html">
|
||||
<dialog-base>
|
||||
<v-toolbar-title slot="toolbar">
|
||||
Delete User Account
|
||||
</v-toolbar-title>
|
||||
<div>
|
||||
<h2>Are you sure you want to delete your account?</h2>
|
||||
<v-alert
|
||||
:value="true"
|
||||
icon="warning"
|
||||
color="error"
|
||||
outline
|
||||
>
|
||||
Deleted accounts can not be recovered
|
||||
</v-alert>
|
||||
<p>We will immediately delete your account and all of your data</p>
|
||||
<p>Your username will become available to anyone on DiceCloud</p>
|
||||
<template v-if="characters.length">
|
||||
<h3 v-if="characters.length > 1">
|
||||
These {{ characters.length }} characters will be deleted:
|
||||
</h3>
|
||||
<h3 v-else>
|
||||
This character will be deleted:
|
||||
</h3>
|
||||
<v-list>
|
||||
<creature-list-tile
|
||||
v-for="character in characters"
|
||||
:key="character._id"
|
||||
:model="character"
|
||||
/>
|
||||
</v-list>
|
||||
</template>
|
||||
<template v-if="libraries.length">
|
||||
<h3 v-if="libraries.length > 1">
|
||||
These {{ libraries.length }} libraries will be deleted:
|
||||
</h3>
|
||||
<h3 v-else>
|
||||
This library will be deleted:
|
||||
</h3>
|
||||
<v-list>
|
||||
<creature-list-tile
|
||||
v-for="library in libraries"
|
||||
:key="library._id"
|
||||
:model="library"
|
||||
/>
|
||||
</v-list>
|
||||
</template>
|
||||
<v-layout
|
||||
column
|
||||
align-start
|
||||
>
|
||||
<v-text-field
|
||||
v-if="user.username"
|
||||
v-model="usernameInput"
|
||||
label="Type your username or email"
|
||||
style="width: 350px;"
|
||||
:error-messages="usernameInputValid ? undefined : ' '"
|
||||
:append-icon="usernameInputValid ? 'done' : undefined"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="verificationInput"
|
||||
label="To verify type 'delete my account'"
|
||||
style="width: 350px;"
|
||||
:error-messages="verificationInputValid ? undefined : ' '"
|
||||
:append-icon="verificationInputValid ? 'done' : undefined"
|
||||
/>
|
||||
<v-btn
|
||||
class="mt-4"
|
||||
color="error"
|
||||
:disabled="!valid"
|
||||
@click="deleteAccount"
|
||||
>
|
||||
Permanently delete account
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</div>
|
||||
<div
|
||||
slot="actions"
|
||||
class="layout row justify-end"
|
||||
>
|
||||
<v-btn
|
||||
flat
|
||||
@click="$store.dispatch('popDialogStack')"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
</div>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import CreatureListTile from '/imports/ui/creature/CreatureListTile.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
CreatureListTile,
|
||||
},
|
||||
data(){return {
|
||||
usernameInput: '',
|
||||
verificationInput: '',
|
||||
};},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'ownedDocuments'(){
|
||||
return [];
|
||||
},
|
||||
},
|
||||
characters(){
|
||||
return Creatures.find({owner: Meteor.userId()});
|
||||
},
|
||||
libraries(){
|
||||
return Libraries.find({owner: Meteor.userId()});
|
||||
},
|
||||
user(){
|
||||
return Meteor.user();
|
||||
},
|
||||
},
|
||||
computed:{
|
||||
usernameInputValid(){
|
||||
let username = this.user.username;
|
||||
if (!username) return true;
|
||||
let input = this.usernameInput;
|
||||
if (!input) return false;
|
||||
if (input.toLowerCase() === username.toLowerCase()){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
verificationInputValid(){
|
||||
let input = this.verificationInput || '';
|
||||
return input.toLowerCase() === 'delete my account'
|
||||
},
|
||||
valid(){
|
||||
return this.usernameInputValid && this.verificationInputValid;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteAccount(){
|
||||
this.$router.push('/');
|
||||
Meteor.users.deleteMyAccount.call();
|
||||
this.$store.dispatch('popDialogStack');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
Reference in New Issue
Block a user