Content jailing: hide all posts/comments on ban/suspend, restore on activate

This commit is contained in:
Patrick Britton 2026-02-06 12:37:03 -06:00
parent d32da021fb
commit d1b01aa5b2
3 changed files with 23 additions and 1 deletions

View file

@ -470,6 +470,9 @@ func (h *AdminHandler) UpdateUserStatus(c *gin.Context) {
}
// Revoke ALL refresh tokens immediately
h.pool.Exec(ctx, `UPDATE refresh_tokens SET revoked = true WHERE user_id = $1::uuid`, targetUserID)
// Jail all their content (hidden from feeds until restored)
h.pool.Exec(ctx, `UPDATE posts SET status = 'jailed' WHERE author_id = $1::uuid AND status = 'active' AND deleted_at IS NULL`, targetUserID)
h.pool.Exec(ctx, `UPDATE comments SET status = 'jailed' WHERE author_id = $1::uuid AND status = 'active' AND deleted_at IS NULL`, targetUserID)
} else if req.Status == "suspended" {
suspendUntil := time.Now().Add(7 * 24 * time.Hour) // Default 7 day suspension from admin
_, err := h.pool.Exec(ctx, `UPDATE users SET status = 'suspended', suspended_until = $2 WHERE id = $1::uuid`, targetUserID, suspendUntil)
@ -477,12 +480,20 @@ func (h *AdminHandler) UpdateUserStatus(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user status"})
return
}
// Jail all their content during suspension
h.pool.Exec(ctx, `UPDATE posts SET status = 'jailed' WHERE author_id = $1::uuid AND status = 'active' AND deleted_at IS NULL`, targetUserID)
h.pool.Exec(ctx, `UPDATE comments SET status = 'jailed' WHERE author_id = $1::uuid AND status = 'active' AND deleted_at IS NULL`, targetUserID)
} else {
_, err := h.pool.Exec(ctx, `UPDATE users SET status = $1, suspended_until = NULL WHERE id = $2::uuid`, req.Status, targetUserID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user status"})
return
}
// If reactivating, restore any jailed content
if req.Status == "active" {
h.pool.Exec(ctx, `UPDATE posts SET status = 'active' WHERE author_id = $1::uuid AND status = 'jailed'`, targetUserID)
h.pool.Exec(ctx, `UPDATE comments SET status = 'active' WHERE author_id = $1::uuid AND status = 'jailed'`, targetUserID)
}
}
// Log status change

View file

@ -86,9 +86,13 @@ func AuthMiddleware(jwtSecret string, pool ...*pgxpool.Pool) gin.HandlerFunc {
}
if status == "suspended" {
if suspendedUntil != nil && time.Now().After(*suspendedUntil) {
// Suspension expired — reactivate
// Suspension expired — reactivate and restore jailed content
dbPool.Exec(context.Background(),
`UPDATE users SET status = 'active', suspended_until = NULL WHERE id = $1::uuid`, userID)
dbPool.Exec(context.Background(),
`UPDATE posts SET status = 'active' WHERE author_id = $1::uuid AND status = 'jailed'`, userID)
dbPool.Exec(context.Background(),
`UPDATE comments SET status = 'active' WHERE author_id = $1::uuid AND status = 'jailed'`, userID)
} else {
c.JSON(http.StatusForbidden, gin.H{"error": "Your account is temporarily suspended.", "code": "suspended"})
c.Abort()

View file

@ -165,6 +165,9 @@ func (cf *ContentFilter) RecordStrikeWithIP(ctx context.Context, userID uuid.UUI
cf.pool.Exec(ctx, `UPDATE users SET status = 'banned' WHERE id = $1`, userID)
// Revoke ALL refresh tokens immediately so the user is logged out
cf.pool.Exec(ctx, `UPDATE refresh_tokens SET revoked = true WHERE user_id = $1`, userID)
// Jail all their content
cf.pool.Exec(ctx, `UPDATE posts SET status = 'jailed' WHERE author_id = $1 AND status = 'active' AND deleted_at IS NULL`, userID)
cf.pool.Exec(ctx, `UPDATE comments SET status = 'jailed' WHERE author_id = $1 AND status = 'active' AND deleted_at IS NULL`, userID)
// Log IP for ban evasion prevention
if clientIP != "" {
cf.pool.Exec(ctx, `
@ -177,11 +180,15 @@ func (cf *ContentFilter) RecordStrikeWithIP(ctx context.Context, userID uuid.UUI
consequence = "suspend_7d"
suspendUntil := time.Now().Add(7 * 24 * time.Hour)
cf.pool.Exec(ctx, `UPDATE users SET status = 'suspended', suspended_until = $2 WHERE id = $1`, userID, suspendUntil)
cf.pool.Exec(ctx, `UPDATE posts SET status = 'jailed' WHERE author_id = $1 AND status = 'active' AND deleted_at IS NULL`, userID)
cf.pool.Exec(ctx, `UPDATE comments SET status = 'jailed' WHERE author_id = $1 AND status = 'active' AND deleted_at IS NULL`, userID)
fmt.Printf("Content filter: user %s suspended 7 days (%d strikes)\n", userID, count)
case count >= 3:
consequence = "suspend_24h"
suspendUntil := time.Now().Add(24 * time.Hour)
cf.pool.Exec(ctx, `UPDATE users SET status = 'suspended', suspended_until = $2 WHERE id = $1`, userID, suspendUntil)
cf.pool.Exec(ctx, `UPDATE posts SET status = 'jailed' WHERE author_id = $1 AND status = 'active' AND deleted_at IS NULL`, userID)
cf.pool.Exec(ctx, `UPDATE comments SET status = 'jailed' WHERE author_id = $1 AND status = 'active' AND deleted_at IS NULL`, userID)
fmt.Printf("Content filter: user %s suspended 24h (%d strikes)\n", userID, count)
default:
fmt.Printf("Content filter: user %s warning (%d strikes)\n", userID, count)