diff --git a/src/hmnurl/hmnurl_test.go b/src/hmnurl/hmnurl_test.go
index 77dba56..9c18d64 100644
--- a/src/hmnurl/hmnurl_test.go
+++ b/src/hmnurl/hmnurl_test.go
@@ -105,6 +105,8 @@ func TestUserSettings(t *testing.T) {
func TestAdmin(t *testing.T) {
AssertRegexMatch(t, BuildAdminAtomFeed(), RegexAdminAtomFeed, nil)
AssertRegexMatch(t, BuildAdminApprovalQueue(), RegexAdminApprovalQueue, nil)
+ AssertRegexMatch(t, BuildAdminSetUserStatus(), RegexAdminSetUserStatus, nil)
+ AssertRegexMatch(t, BuildAdminNukeUser(), RegexAdminNukeUser, nil)
}
func TestSnippet(t *testing.T) {
diff --git a/src/hmnurl/urls.go b/src/hmnurl/urls.go
index 4e8e8ba..560f5db 100644
--- a/src/hmnurl/urls.go
+++ b/src/hmnurl/urls.go
@@ -216,6 +216,20 @@ func BuildAdminApprovalQueue() string {
return Url("/admin/approvals", nil)
}
+var RegexAdminSetUserStatus = regexp.MustCompile(`^/admin/setuserstatus$`)
+
+func BuildAdminSetUserStatus() string {
+ defer CatchPanic()
+ return Url("/admin/setuserstatus", nil)
+}
+
+var RegexAdminNukeUser = regexp.MustCompile(`^/admin/nukeuser$`)
+
+func BuildAdminNukeUser() string {
+ defer CatchPanic()
+ return Url("/admin/nukeuser", nil)
+}
+
/*
* Snippets
*/
diff --git a/src/templates/mapping.go b/src/templates/mapping.go
index f6f178c..b2848db 100644
--- a/src/templates/mapping.go
+++ b/src/templates/mapping.go
@@ -193,6 +193,7 @@ func UserToTemplate(u *models.User, currentTheme string) User {
Username: u.Username,
Email: email,
IsStaff: u.IsStaff,
+ Status: int(u.Status),
Name: u.BestName(),
Bio: u.Bio,
diff --git a/src/templates/src/user_profile.html b/src/templates/src/user_profile.html
index 88c24af..adffc7d 100644
--- a/src/templates/src/user_profile.html
+++ b/src/templates/src/user_profile.html
@@ -1,5 +1,32 @@
{{ template "base.html" . }}
+{{ define "extrahead" }}
+
+{{ end }}
+
{{ define "content" }}
{{ if or .OwnProfile .ProfileUserProjects }}
diff --git a/src/templates/types.go b/src/templates/types.go
index 1d95d7f..73bda64 100644
--- a/src/templates/types.go
+++ b/src/templates/types.go
@@ -157,6 +157,7 @@ type User struct {
Username string
Email string
IsStaff bool
+ Status int
Name string
Blurb string
diff --git a/src/website/routes.go b/src/website/routes.go
index e4b3a13..e9c948d 100644
--- a/src/website/routes.go
+++ b/src/website/routes.go
@@ -190,6 +190,8 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool) ht
hmnOnly.GET(hmnurl.RegexAdminAtomFeed, AdminAtomFeed)
hmnOnly.GET(hmnurl.RegexAdminApprovalQueue, adminMiddleware(AdminApprovalQueue))
hmnOnly.POST(hmnurl.RegexAdminApprovalQueue, adminMiddleware(csrfMiddleware(AdminApprovalQueueSubmit)))
+ hmnOnly.POST(hmnurl.RegexAdminSetUserStatus, adminMiddleware(csrfMiddleware(UserProfileAdminSetStatus)))
+ hmnOnly.POST(hmnurl.RegexAdminNukeUser, adminMiddleware(csrfMiddleware(UserProfileAdminNuke)))
hmnOnly.GET(hmnurl.RegexFeed, Feed)
hmnOnly.GET(hmnurl.RegexAtomFeed, AtomFeed)
diff --git a/src/website/user.go b/src/website/user.go
index 9d48c2f..7d05530 100644
--- a/src/website/user.go
+++ b/src/website/user.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"sort"
+ "strconv"
"strings"
"time"
@@ -32,6 +33,9 @@ type UserProfileTemplateData struct {
CanAddProject bool
NewProjectUrl string
+
+ AdminSetStatusUrl string
+ AdminNukeUrl string
}
func UserProfile(c *RequestContext) ResponseData {
@@ -191,6 +195,9 @@ func UserProfile(c *RequestContext) ResponseData {
CanAddProject: numPersonalProjects < maxPersonalProjects,
NewProjectUrl: hmnurl.BuildProjectNew(),
+
+ AdminSetStatusUrl: hmnurl.BuildAdminSetUserStatus(),
+ AdminNukeUrl: hmnurl.BuildAdminNukeUser(),
}, c.Perf)
return res
}
@@ -440,6 +447,64 @@ func UserSettingsSave(c *RequestContext) ResponseData {
return res
}
+func UserProfileAdminSetStatus(c *RequestContext) ResponseData {
+ c.Req.ParseForm()
+
+ userIdStr := c.Req.Form.Get("user_id")
+ userId, err := strconv.Atoi(userIdStr)
+ if err != nil {
+ return RejectRequest(c, "No user id provided")
+ }
+
+ status := c.Req.Form.Get("status")
+ var desiredStatus models.UserStatus
+ switch status {
+ case "inactive":
+ desiredStatus = models.UserStatusInactive
+ case "confirmed":
+ desiredStatus = models.UserStatusConfirmed
+ case "approved":
+ desiredStatus = models.UserStatusApproved
+ case "banned":
+ desiredStatus = models.UserStatusBanned
+ default:
+ return RejectRequest(c, "No legal user status provided")
+ }
+
+ _, err = c.Conn.Exec(c.Context(),
+ `
+ UPDATE auth_user
+ SET status = $1
+ WHERE id = $2
+ `,
+ desiredStatus,
+ userId,
+ )
+ if err != nil {
+ return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to update user status"))
+ }
+ res := c.Redirect(hmnurl.BuildUserProfile(c.Req.Form.Get("username")), http.StatusSeeOther)
+ res.AddFutureNotice("success", "Successfully set status")
+ return res
+}
+
+func UserProfileAdminNuke(c *RequestContext) ResponseData {
+ c.Req.ParseForm()
+ userIdStr := c.Req.Form.Get("user_id")
+ userId, err := strconv.Atoi(userIdStr)
+ if err != nil {
+ return RejectRequest(c, "No user id provided")
+ }
+
+ err = deleteAllPostsForUser(c.Context(), c.Conn, userId)
+ if err != nil {
+ return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete user posts"))
+ }
+ res := c.Redirect(hmnurl.BuildUserProfile(c.Req.Form.Get("username")), http.StatusSeeOther)
+ res.AddFutureNotice("success", "Successfully nuked user")
+ return res
+}
+
func updatePassword(c *RequestContext, tx pgx.Tx, old, new, confirm string) *ResponseData {
if new != confirm {
res := RejectRequest(c, "Your password and password confirmation did not match.")