Merge pull request #203 from writefreely/T319-admin-delete-acct
T319 admin delete acct
This commit is contained in:
commit
4e0912b32a
33
admin.go
33
admin.go
|
@ -189,6 +189,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
|
|||
*AdminPage
|
||||
Config config.AppCfg
|
||||
Message string
|
||||
Flashes []string
|
||||
|
||||
Users *[]User
|
||||
CurPage int
|
||||
|
@ -201,6 +202,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
|
|||
Message: r.FormValue("m"),
|
||||
}
|
||||
|
||||
p.Flashes, _ = getSessionFlashes(app, w, r, nil)
|
||||
p.TotalUsers = app.db.GetAllUsersCount()
|
||||
ttlPages := p.TotalUsers / adminUsersPerPage
|
||||
p.TotalPages = []int{}
|
||||
|
@ -312,6 +314,37 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
|
|||
return nil
|
||||
}
|
||||
|
||||
func handleAdminDeleteUser(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
||||
if !u.IsAdmin() {
|
||||
return impart.HTTPError{http.StatusForbidden, "Administrator privileges required for this action"}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
username := vars["username"]
|
||||
confirmUsername := r.PostFormValue("confirm-username")
|
||||
|
||||
if confirmUsername != username {
|
||||
return impart.HTTPError{http.StatusBadRequest, "Username was not confirmed"}
|
||||
}
|
||||
|
||||
user, err := app.db.GetUserForAuth(username)
|
||||
if err == ErrUserNotFound {
|
||||
return impart.HTTPError{http.StatusNotFound, fmt.Sprintf("User '%s' was not found", username)}
|
||||
} else if err != nil {
|
||||
log.Error("get user for deletion: %v", err)
|
||||
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user with username '%s': %v", username, err)}
|
||||
}
|
||||
|
||||
err = app.db.DeleteAccount(user.ID)
|
||||
if err != nil {
|
||||
log.Error("delete user %s: %v", user.Username, err)
|
||||
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not delete user account for '%s': %v", username, err)}
|
||||
}
|
||||
|
||||
_ = addSessionFlash(app, w, r, fmt.Sprintf("User \"%s\" was deleted successfully.", username), nil)
|
||||
return impart.HTTPError{http.StatusFound, "/admin/users"}
|
||||
}
|
||||
|
||||
func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
username := vars["username"]
|
||||
|
|
|
@ -1044,6 +1044,19 @@ li {
|
|||
background-color: #dff0d8;
|
||||
border-color: #d6e9c6;
|
||||
}
|
||||
&.danger {
|
||||
border-color: #856404;
|
||||
background-color: white;
|
||||
h3 {
|
||||
margin: 0 0 0.5em 0;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
color: black !important;
|
||||
}
|
||||
h3 + p, button {
|
||||
font-size: 0.86em;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
|
|
|
@ -340,6 +340,15 @@ body#pad {
|
|||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
line-height: 1.5;
|
||||
|
||||
input[type=text].confirm {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.short {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -166,6 +166,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
write.HandleFunc("/admin/settings", handler.Admin(handleViewAdminSettings)).Methods("GET")
|
||||
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET")
|
||||
write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET")
|
||||
write.HandleFunc("/admin/user/{username}/delete", handler.Admin(handleAdminDeleteUser)).Methods("POST")
|
||||
write.HandleFunc("/admin/user/{username}/status", handler.Admin(handleAdminToggleUserStatus)).Methods("POST")
|
||||
write.HandleFunc("/admin/user/{username}/passphrase", handler.Admin(handleAdminResetUserPass)).Methods("POST")
|
||||
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright © 2016-2021 A Bunch Tell LLC.
|
||||
*
|
||||
* This file is part of WriteFreely.
|
||||
*
|
||||
* WriteFreely is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, included
|
||||
* in the LICENSE file in this source code package.
|
||||
*/
|
||||
|
||||
function showModal(id) {
|
||||
document.getElementById('overlay').style.display = 'block';
|
||||
document.getElementById('modal-'+id).style.display = 'block';
|
||||
}
|
||||
|
||||
var closeModals = function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('overlay').style.display = 'none';
|
||||
var modals = document.querySelectorAll('.modal');
|
||||
for (var i=0; i<modals.length; i++) {
|
||||
modals[i].style.display = 'none';
|
||||
}
|
||||
};
|
||||
H.getEl('overlay').on('click', closeModals);
|
|
@ -4,6 +4,12 @@
|
|||
<div class="snug content-container">
|
||||
{{template "admin-header" .}}
|
||||
|
||||
<!-- TODO: if other use for flashes use patern like account_import.go -->
|
||||
{{if .Flashes}}
|
||||
<p class="alert success">
|
||||
{{range .Flashes}}{{.}}{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
<div class="row admin-actions" style="justify-content: space-between;">
|
||||
<span style="font-style: italic; font-size: 1.2em">{{.TotalUsers}} {{pluralize "user" "users" .TotalUsers}}</span>
|
||||
<a class="btn cta" href="/me/invites">+ Invite people</a>
|
||||
|
|
|
@ -32,8 +32,13 @@ input.copy-text {
|
|||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.modal {
|
||||
position: fixed;
|
||||
}
|
||||
</style>
|
||||
<div class="snug content-container">
|
||||
<div id="overlay"></div>
|
||||
|
||||
{{template "admin-header" .}}
|
||||
|
||||
<h2 id="posts-header">{{.User.Username}}</h2>
|
||||
|
@ -139,9 +144,60 @@ input.copy-text {
|
|||
{{end}}
|
||||
</table>
|
||||
{{end}}
|
||||
|
||||
{{ if not .User.IsAdmin }}
|
||||
<h2>Incinerator</h2>
|
||||
<div class="alert danger">
|
||||
<div class="row">
|
||||
<div>
|
||||
<h3>Delete this user</h3>
|
||||
<p>Permanently erase all user data, with no way to recover it.</p>
|
||||
</div>
|
||||
<button class="cta danger" onclick="prepareDeleteUser()">Delete this user...</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div id="modal-delete-user" class="modal">
|
||||
<h2>Are you sure?</h2>
|
||||
<div class="body">
|
||||
<p style="text-align:left">This action <strong>cannot</strong> be undone. It will permanently erase all traces of this user, <strong>{{.User.Username}}</strong>, including their account information, blogs, and posts.</p>
|
||||
<p>Please type <strong>{{.User.Username}}</strong> to confirm.</p>
|
||||
|
||||
<ul id="delete-errors" class="errors"></ul>
|
||||
|
||||
<form action="/admin/user/{{.User.Username}}/delete" method="post" onsubmit="confirmDeletion()">
|
||||
<input id="confirm-text" placeholder="{{.User.Username}}" type="text" class="confirm boxy" name="confirm-username" style="margin-top: 0.5em;" />
|
||||
<div style="text-align:right; margin-top: 1em;">
|
||||
<a id="cancel-delete" style="margin-right:2em" href="#">Cancel</a>
|
||||
<input class="danger" type="submit" id="confirm-delete" value="Delete this user" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/h.js"></script>
|
||||
<script src="/js/modals.js"></script>
|
||||
<script type="text/javascript">
|
||||
H.getEl('cancel-delete').on('click', closeModals);
|
||||
|
||||
let $confirmDelBtn = document.getElementById('confirm-delete');
|
||||
let $confirmText = document.getElementById('confirm-text')
|
||||
$confirmText.addEventListener('input', function() {
|
||||
$confirmDelBtn.disabled = this.value !== '{{.User.Username}}'
|
||||
});
|
||||
|
||||
function prepareDeleteUser() {
|
||||
$confirmText.value = ''
|
||||
showModal('delete-user')
|
||||
$confirmText.focus()
|
||||
}
|
||||
|
||||
function confirmDeletion() {
|
||||
$confirmDelBtn.disabled = true
|
||||
$confirmDelBtn.value = 'Deleting...'
|
||||
}
|
||||
|
||||
function confirmSilence() {
|
||||
return confirm("Silence this user? They'll still be able to log in and access their posts, but no one else will be able to see them anymore. You can reverse this decision at any time.");
|
||||
}
|
||||
|
|
|
@ -193,25 +193,10 @@ textarea.section.norm {
|
|||
</div>
|
||||
|
||||
<script src="/js/h.js"></script>
|
||||
<script src="/js/modals.js"></script>
|
||||
<script src="/js/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script>
|
||||
// Begin shared modal code
|
||||
function showModal(id) {
|
||||
document.getElementById('overlay').style.display = 'block';
|
||||
document.getElementById('modal-'+id).style.display = 'block';
|
||||
}
|
||||
|
||||
var closeModals = function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('overlay').style.display = 'none';
|
||||
var modals = document.querySelectorAll('.modal');
|
||||
for (var i=0; i<modals.length; i++) {
|
||||
modals[i].style.display = 'none';
|
||||
}
|
||||
};
|
||||
H.getEl('overlay').on('click', closeModals);
|
||||
H.getEl('cancel-delete').on('click', closeModals);
|
||||
// end
|
||||
var deleteBlog = function(e) {
|
||||
if (document.getElementById('confirm-text').value != '{{.Alias}}') {
|
||||
document.getElementById('delete-errors').innerHTML = '<li class="urgent">Enter <strong>{{.Alias}}</strong> in the box below.</li>';
|
||||
|
|
Loading…
Reference in New Issue