Deactivate Account; Friends Lists on Profiles
* Add a way for users to temporarily deactivate their accounts, in a recoverable way should they decide to return later. * A deactivated account may log in but have limited options: to reactivate their account, permanently delete it, or log out. * Fix several bugs around the display of comments, messages and forum threads for disabled, banned, or blocked users: * Messages (inbox and sentbox) will be hidden and the unread indicator will not count unread messages the user can't access. * Comments on photos and forum posts are hidden, and top-level threads on the "Newest" tab will show "[unavailable]" for their text and username. * Your historical notifications will hide users who are blocked, banned or disabled. * Add a "Friends" tab to user profile pages, to see other users' friends. * The page is Certification Required so non-cert users can't easily discover any members on the site.
This commit is contained in:
parent
0f35f135d2
commit
481bd0ae61
82
pkg/controller/account/deactivate.go
Normal file
82
pkg/controller/account/deactivate.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deactivate account page (self service).
|
||||||
|
func Deactivate() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("account/deactivate.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't get your current user: %s", err)
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm deletion.
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
var password = strings.TrimSpace(r.PostFormValue("password"))
|
||||||
|
if err := currentUser.CheckPassword(password); err != nil {
|
||||||
|
session.FlashError(w, r, "You must enter your correct account password to delete your account.")
|
||||||
|
templates.Redirect(w, r.URL.Path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate their account!
|
||||||
|
currentUser.Status = models.UserStatusDisabled
|
||||||
|
if err := currentUser.Save(); err != nil {
|
||||||
|
session.FlashError(w, r, "Error while deactivating your account: %s", err)
|
||||||
|
templates.Redirect(w, r.URL.Path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign them out.
|
||||||
|
session.LogoutUser(w, r)
|
||||||
|
session.Flash(w, r, "Your account has been deactivated and you are now logged out. If you wish to re-activate your account, sign in again with your username and password.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars = map[string]interface{}{}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactivate account page
|
||||||
|
func Reactivate() http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't get your current user: %s", err)
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentUser.Status != models.UserStatusDisabled {
|
||||||
|
session.FlashError(w, r, "Your account was not disabled in the first place!")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactivate them.
|
||||||
|
currentUser.Status = models.UserStatusActive
|
||||||
|
if err := currentUser.Save(); err != nil {
|
||||||
|
session.FlashError(w, r, "Error while reactivating your account: %s", err)
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Flash(w, r, "Welcome back! Your account has been reactivated.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
})
|
||||||
|
}
|
88
pkg/controller/account/friends.go
Normal file
88
pkg/controller/account/friends.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
var UserFriendsRegexp = regexp.MustCompile(`^/friends/u/([^@]+?)$`)
|
||||||
|
|
||||||
|
// User friends page (/friends/u/username)
|
||||||
|
func UserFriends() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("account/friends.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Parse the username out of the URL parameters.
|
||||||
|
var username string
|
||||||
|
m := UserFriendsRegexp.FindStringSubmatch(r.URL.Path)
|
||||||
|
if m != nil {
|
||||||
|
username = m[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find this user.
|
||||||
|
user, err := models.FindUser(username)
|
||||||
|
if err != nil {
|
||||||
|
templates.NotFoundPage(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the viewer.
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Unexpected error: could not get currentUser.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSelf = currentUser.ID == user.ID
|
||||||
|
|
||||||
|
// Banned or disabled? Only admin can view then.
|
||||||
|
if user.Status != models.UserStatusActive && !currentUser.IsAdmin {
|
||||||
|
templates.NotFoundPage(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is either one blocking?
|
||||||
|
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
|
||||||
|
templates.NotFoundPage(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get their friends.
|
||||||
|
pager := &models.Pagination{
|
||||||
|
PerPage: config.PageSizeFriends,
|
||||||
|
Sort: "updated_at desc",
|
||||||
|
}
|
||||||
|
pager.ParsePage(r)
|
||||||
|
friends, err := models.PaginateOtherUserFriends(currentUser, user, pager)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't paginate friends: %s", err)
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject relationship booleans.
|
||||||
|
models.SetUserRelationships(currentUser, friends)
|
||||||
|
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"User": user,
|
||||||
|
"IsSelf": isSelf,
|
||||||
|
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
||||||
|
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
||||||
|
"FriendCount": models.CountFriends(user.ID),
|
||||||
|
"Friends": friends,
|
||||||
|
"Pager": pager,
|
||||||
|
|
||||||
|
// Map our friendships to these users.
|
||||||
|
"FriendMap": models.MapFriends(currentUser, friends),
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -64,8 +64,8 @@ func Login() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is their account banned or disabled?
|
// Is their account banned?
|
||||||
if user.Status != models.UserStatusActive {
|
if user.Status == models.UserStatusBanned {
|
||||||
session.FlashError(w, r, "Your account has been %s. If you believe this was done in error, please contact support.", user.Status)
|
session.FlashError(w, r, "Your account has been %s. If you believe this was done in error, please contact support.", user.Status)
|
||||||
templates.Redirect(w, r.URL.Path)
|
templates.Redirect(w, r.URL.Path)
|
||||||
return
|
return
|
||||||
|
|
|
@ -114,13 +114,14 @@ func Profile() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := map[string]interface{}{
|
vars := map[string]interface{}{
|
||||||
"User": user,
|
"User": user,
|
||||||
"LikeMap": likeMap,
|
"LikeMap": likeMap,
|
||||||
"IsFriend": isFriend,
|
"IsFriend": isFriend,
|
||||||
"IsPrivate": isPrivate,
|
"IsPrivate": isPrivate,
|
||||||
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
||||||
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
||||||
"OnChat": worker.GetChatStatistics().IsOnline(user.Username),
|
"FriendCount": models.CountFriends(user.ID),
|
||||||
|
"OnChat": worker.GetChatStatistics().IsOnline(user.Username),
|
||||||
|
|
||||||
// Details on who likes the photo.
|
// Details on who likes the photo.
|
||||||
"LikeExample": likeExample,
|
"LikeExample": likeExample,
|
||||||
|
|
|
@ -139,10 +139,11 @@ func UserNotes() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := map[string]interface{}{
|
vars := map[string]interface{}{
|
||||||
"User": user,
|
"User": user,
|
||||||
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
||||||
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
||||||
"MyNote": myNote,
|
"FriendCount": models.CountFriends(user.ID),
|
||||||
|
"MyNote": myNote,
|
||||||
|
|
||||||
// Admin concerns.
|
// Admin concerns.
|
||||||
"Feedback": feedback,
|
"Feedback": feedback,
|
||||||
|
|
|
@ -191,7 +191,7 @@ func FilteredChatStatistics(currentUser *models.User) worker.ChatStatistics {
|
||||||
|
|
||||||
// Who are we blocking?
|
// Who are we blocking?
|
||||||
var blockedUsernames = map[string]interface{}{}
|
var blockedUsernames = map[string]interface{}{}
|
||||||
for _, username := range models.BlockedUsernames(currentUser.ID) {
|
for _, username := range models.BlockedUsernames(currentUser) {
|
||||||
blockedUsernames[username] = nil
|
blockedUsernames[username] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ func FilteredChatStatistics(currentUser *models.User) worker.ChatStatistics {
|
||||||
// SendBlocklist syncs the user blocklist to the chat server prior to sending them over.
|
// SendBlocklist syncs the user blocklist to the chat server prior to sending them over.
|
||||||
func SendBlocklist(user *models.User) error {
|
func SendBlocklist(user *models.User) error {
|
||||||
// Get the user's blocklist.
|
// Get the user's blocklist.
|
||||||
blockedUsernames := models.BlockedUsernames(user.ID)
|
blockedUsernames := models.BlockedUsernames(user)
|
||||||
log.Info("SendBlocklist(%s) to BareRTC: %d blocked usernames", user.Username, len(blockedUsernames))
|
log.Info("SendBlocklist(%s) to BareRTC: %d blocked usernames", user.Username, len(blockedUsernames))
|
||||||
|
|
||||||
// API request struct for BareRTC /api/blocklist endpoint.
|
// API request struct for BareRTC /api/blocklist endpoint.
|
||||||
|
|
|
@ -95,13 +95,13 @@ func Inbox() http.HandlerFunc {
|
||||||
// On the main inbox view, ?page= params page thru the message list, not a thread.
|
// On the main inbox view, ?page= params page thru the message list, not a thread.
|
||||||
pager.ParsePage(r)
|
pager.ParsePage(r)
|
||||||
}
|
}
|
||||||
messages, err := models.GetMessages(currentUser.ID, showSent, pager)
|
messages, err := models.GetMessages(currentUser, showSent, pager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
session.FlashError(w, r, "Couldn't get your messages from DB: %s", err)
|
session.FlashError(w, r, "Couldn't get your messages from DB: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// How many unreads?
|
// How many unreads?
|
||||||
unread, err := models.CountUnreadMessages(currentUser.ID)
|
unread, err := models.CountUnreadMessages(currentUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
session.FlashError(w, r, "Couldn't get your unread message count from DB: %s", err)
|
session.FlashError(w, r, "Couldn't get your unread message count from DB: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,7 @@ func UserPhotos() http.HandlerFunc {
|
||||||
"Photos": photos,
|
"Photos": photos,
|
||||||
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
"PhotoCount": models.CountPhotosICanSee(user, currentUser),
|
||||||
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
"NoteCount": models.CountNotesAboutUser(currentUser, user),
|
||||||
|
"FriendCount": models.CountFriends(user.ID),
|
||||||
"PublicPhotoCount": models.CountPublicPhotos(user.ID),
|
"PublicPhotoCount": models.CountPublicPhotos(user.ID),
|
||||||
"InnerCircleMinimumPublicPhotos": config.InnerCircleMinimumPublicPhotos,
|
"InnerCircleMinimumPublicPhotos": config.InnerCircleMinimumPublicPhotos,
|
||||||
"Pager": pager,
|
"Pager": pager,
|
||||||
|
|
|
@ -27,19 +27,19 @@ func LoginRequired(handler http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are they banned or disabled?
|
// Are they banned?
|
||||||
if user.Status == models.UserStatusDisabled {
|
if user.Status == models.UserStatusBanned {
|
||||||
session.LogoutUser(w, r)
|
|
||||||
session.FlashError(w, r, "Your account has been disabled and you are now logged out.")
|
|
||||||
templates.Redirect(w, "/")
|
|
||||||
return
|
|
||||||
} else if user.Status == models.UserStatusBanned {
|
|
||||||
session.LogoutUser(w, r)
|
session.LogoutUser(w, r)
|
||||||
session.FlashError(w, r, "Your account has been banned and you are now logged out.")
|
session.FlashError(w, r, "Your account has been banned and you are now logged out.")
|
||||||
templates.Redirect(w, "/")
|
templates.Redirect(w, "/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is their account disabled?
|
||||||
|
if DisabledAccount(user, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Is the site under a Maintenance Mode restriction?
|
// Is the site under a Maintenance Mode restriction?
|
||||||
if MaintenanceMode(user, w, r) {
|
if MaintenanceMode(user, w, r) {
|
||||||
return
|
return
|
||||||
|
@ -115,19 +115,19 @@ func CertRequired(handler http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are they banned or disabled?
|
// Are they banned?
|
||||||
if currentUser.Status == models.UserStatusDisabled {
|
if currentUser.Status == models.UserStatusBanned {
|
||||||
session.LogoutUser(w, r)
|
|
||||||
session.FlashError(w, r, "Your account has been disabled and you are now logged out.")
|
|
||||||
templates.Redirect(w, "/")
|
|
||||||
return
|
|
||||||
} else if currentUser.Status == models.UserStatusBanned {
|
|
||||||
session.LogoutUser(w, r)
|
session.LogoutUser(w, r)
|
||||||
session.FlashError(w, r, "Your account has been banned and you are now logged out.")
|
session.FlashError(w, r, "Your account has been banned and you are now logged out.")
|
||||||
templates.Redirect(w, "/")
|
templates.Redirect(w, "/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is their account disabled?
|
||||||
|
if DisabledAccount(currentUser, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Is the site under a Maintenance Mode restriction?
|
// Is the site under a Maintenance Mode restriction?
|
||||||
if MaintenanceMode(currentUser, w, r) {
|
if MaintenanceMode(currentUser, w, r) {
|
||||||
return
|
return
|
||||||
|
|
39
pkg/middleware/disabled_account.go
Normal file
39
pkg/middleware/disabled_account.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tmplDisabledAccount = templates.Must("errors/disabled_account.html")
|
||||||
|
|
||||||
|
// Whitelist of paths to allow disabled accounts to access.
|
||||||
|
var disabledAccountPathWhitelist = []string{
|
||||||
|
"/account/delete",
|
||||||
|
"/account/reactivate",
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisabledAccount check that limits a logged-in user's options to either reactivate their account,
|
||||||
|
// delete it, or log back out.
|
||||||
|
func DisabledAccount(currentUser *models.User, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
// Is their account disabled?
|
||||||
|
if currentUser.Status == models.UserStatusDisabled {
|
||||||
|
// Whitelisted paths?
|
||||||
|
for _, path := range disabledAccountPathWhitelist {
|
||||||
|
if strings.HasPrefix(r.URL.Path, path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the disabled account page to all other requests.
|
||||||
|
if err := tmplDisabledAccount.Execute(w, r, nil); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -93,27 +93,36 @@ func PaginateBlockList(user *User, pager *Pagination) ([]*User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockedUserIDs returns all user IDs blocked by the user (bidirectional, source or target user).
|
// BlockedUserIDs returns all user IDs blocked by the user (bidirectional, source or target user).
|
||||||
func BlockedUserIDs(userId uint64) []uint64 {
|
func BlockedUserIDs(user *User) []uint64 {
|
||||||
|
// Have we looked this up already on this request?
|
||||||
|
if user.cacheBlockedUserIDs != nil {
|
||||||
|
return user.cacheBlockedUserIDs
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bs = []*Block{}
|
bs = []*Block{}
|
||||||
userIDs = []uint64{}
|
userIDs = []uint64{}
|
||||||
)
|
)
|
||||||
DB.Where("source_user_id = ? OR target_user_id = ?", userId, userId).Find(&bs)
|
DB.Where("source_user_id = ? OR target_user_id = ?", user.ID, user.ID).Find(&bs)
|
||||||
for _, row := range bs {
|
for _, row := range bs {
|
||||||
for _, uid := range []uint64{row.TargetUserID, row.SourceUserID} {
|
for _, uid := range []uint64{row.TargetUserID, row.SourceUserID} {
|
||||||
if uid != userId {
|
if uid != user.ID {
|
||||||
userIDs = append(userIDs, uid)
|
userIDs = append(userIDs, uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache the result in the User so we don't query it again.
|
||||||
|
user.cacheBlockedUserIDs = userIDs
|
||||||
|
|
||||||
return userIDs
|
return userIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapBlockedUserIDs returns BlockedUserIDs as a lookup hashmap (not for front-end templates currently).
|
// MapBlockedUserIDs returns BlockedUserIDs as a lookup hashmap (not for front-end templates currently).
|
||||||
func MapBlockedUserIDs(userId uint64) map[uint64]interface{} {
|
func MapBlockedUserIDs(user *User) map[uint64]interface{} {
|
||||||
var (
|
var (
|
||||||
result = map[uint64]interface{}{}
|
result = map[uint64]interface{}{}
|
||||||
blockedIDs = BlockedUserIDs(userId)
|
blockedIDs = BlockedUserIDs(user)
|
||||||
)
|
)
|
||||||
for _, uid := range blockedIDs {
|
for _, uid := range blockedIDs {
|
||||||
result[uid] = nil
|
result[uid] = nil
|
||||||
|
@ -125,7 +134,7 @@ func MapBlockedUserIDs(userId uint64) map[uint64]interface{} {
|
||||||
func FilterBlockingUserIDs(currentUser *User, userIDs []uint64) []uint64 {
|
func FilterBlockingUserIDs(currentUser *User, userIDs []uint64) []uint64 {
|
||||||
var (
|
var (
|
||||||
// Get the IDs to exclude.
|
// Get the IDs to exclude.
|
||||||
blockedIDs = MapBlockedUserIDs(currentUser.ID)
|
blockedIDs = MapBlockedUserIDs(currentUser)
|
||||||
|
|
||||||
// Filter the result.
|
// Filter the result.
|
||||||
result = []uint64{}
|
result = []uint64{}
|
||||||
|
@ -157,9 +166,9 @@ func BlockedUserIDsByUser(userId uint64) []uint64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockedUsernames returns all usernames blocked by (or blocking) the user.
|
// BlockedUsernames returns all usernames blocked by (or blocking) the user.
|
||||||
func BlockedUsernames(userId uint64) []string {
|
func BlockedUsernames(user *User) []string {
|
||||||
var (
|
var (
|
||||||
userIDs = BlockedUserIDs(userId)
|
userIDs = BlockedUserIDs(user)
|
||||||
usernames = []string{}
|
usernames = []string{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -174,7 +183,7 @@ func BlockedUsernames(userId uint64) []string {
|
||||||
).Where(
|
).Where(
|
||||||
"id IN ?", userIDs,
|
"id IN ?", userIDs,
|
||||||
).Scan(&usernames); res.Error != nil {
|
).Scan(&usernames); res.Error != nil {
|
||||||
log.Error("BlockedUsernames(%d): %s", userId, res.Error)
|
log.Error("BlockedUsernames(%d): %s", user.Username, res.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return usernames
|
return usernames
|
||||||
|
|
|
@ -71,7 +71,7 @@ func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagin
|
||||||
var (
|
var (
|
||||||
cs = []*Comment{}
|
cs = []*Comment{}
|
||||||
query = (&Comment{}).Preload()
|
query = (&Comment{}).Preload()
|
||||||
blockedUserIDs = BlockedUserIDs(user.ID)
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
placeholders = []interface{}{}
|
placeholders = []interface{}{}
|
||||||
)
|
)
|
||||||
|
@ -84,6 +84,16 @@ func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagin
|
||||||
placeholders = append(placeholders, blockedUserIDs)
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't show comments from banned or disabled accounts.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = comments.user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
query = query.Where(
|
query = query.Where(
|
||||||
strings.Join(wheres, " AND "),
|
strings.Join(wheres, " AND "),
|
||||||
placeholders...,
|
placeholders...,
|
||||||
|
@ -102,7 +112,7 @@ func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagin
|
||||||
func ListComments(user *User, tableName string, tableID uint64) ([]*Comment, error) {
|
func ListComments(user *User, tableName string, tableID uint64) ([]*Comment, error) {
|
||||||
var (
|
var (
|
||||||
cs []*Comment
|
cs []*Comment
|
||||||
blockedUserIDs = BlockedUserIDs(user.ID)
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
placeholders = []interface{}{}
|
placeholders = []interface{}{}
|
||||||
)
|
)
|
||||||
|
@ -115,6 +125,16 @@ func ListComments(user *User, tableName string, tableID uint64) ([]*Comment, err
|
||||||
placeholders = append(placeholders, blockedUserIDs)
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't show comments from banned or disabled accounts.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = comments.user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
result := (&Comment{}).Preload().Where(
|
result := (&Comment{}).Preload().Where(
|
||||||
strings.Join(wheres, " AND "),
|
strings.Join(wheres, " AND "),
|
||||||
placeholders...,
|
placeholders...,
|
||||||
|
|
|
@ -22,10 +22,11 @@ type RecentPost struct {
|
||||||
// PaginateRecentPosts returns all of the comments on a forum paginated.
|
// PaginateRecentPosts returns all of the comments on a forum paginated.
|
||||||
func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]*RecentPost, error) {
|
func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]*RecentPost, error) {
|
||||||
var (
|
var (
|
||||||
result = []*RecentPost{}
|
result = []*RecentPost{}
|
||||||
query = (&Comment{}).Preload()
|
query = (&Comment{}).Preload()
|
||||||
wheres = []string{"table_name = 'threads'"}
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
placeholders = []interface{}{}
|
wheres = []string{"table_name = 'threads'"}
|
||||||
|
placeholders = []interface{}{}
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(categories) > 0 {
|
if len(categories) > 0 {
|
||||||
|
@ -43,6 +44,22 @@ func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]
|
||||||
wheres = append(wheres, "forums.inner_circle is not true")
|
wheres = append(wheres, "forums.inner_circle is not true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blocked users?
|
||||||
|
if len(blockedUserIDs) > 0 {
|
||||||
|
wheres = append(wheres, "comments.user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show comments from banned or disabled accounts.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = comments.user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
// Get the page of recent forum comment IDs of all time.
|
// Get the page of recent forum comment IDs of all time.
|
||||||
type scanner struct {
|
type scanner struct {
|
||||||
CommentID uint64
|
CommentID uint64
|
||||||
|
@ -130,7 +147,7 @@ func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]
|
||||||
comments, _ = GetComments(commentIDs)
|
comments, _ = GetComments(commentIDs)
|
||||||
}
|
}
|
||||||
if len(threadIDs) > 0 {
|
if len(threadIDs) > 0 {
|
||||||
threads, _ = GetThreads(threadIDs)
|
threads, _ = GetThreadsAsUser(user, threadIDs)
|
||||||
}
|
}
|
||||||
if len(forumIDs) > 0 {
|
if len(forumIDs) > 0 {
|
||||||
forums, _ = GetForums(forumIDs)
|
forums, _ = GetForums(forumIDs)
|
||||||
|
@ -154,6 +171,14 @@ func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]
|
||||||
thrs = append(thrs, thr)
|
thrs = append(thrs, thr)
|
||||||
} else {
|
} else {
|
||||||
log.Error("RecentPosts: didn't find thread ID %d in map!", rc.ThreadID)
|
log.Error("RecentPosts: didn't find thread ID %d in map!", rc.ThreadID)
|
||||||
|
|
||||||
|
// Create a dummy placeholder Thread (e.g.: the thread originator has been
|
||||||
|
// banned or disabled, but the thread summary is shown on the new comment view)
|
||||||
|
rc.Thread = &Thread{
|
||||||
|
Comment: Comment{
|
||||||
|
Message: "[unavailable]",
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f, ok := forums[rc.ForumID]; ok {
|
if f, ok := forums[rc.ForumID]; ok {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.nonshy.com/nonshy/website/pkg/log"
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
@ -246,6 +247,20 @@ func CountFriendRequests(userID uint64) (int64, error) {
|
||||||
return count, result.Error
|
return count, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountFriends gets a count of friends for the user.
|
||||||
|
func CountFriends(userID uint64) int64 {
|
||||||
|
var count int64
|
||||||
|
result := DB.Where(
|
||||||
|
"target_user_id = ? AND approved = ? AND EXISTS (SELECT 1 FROM users WHERE users.id = friends.source_user_id AND users.status = 'active')",
|
||||||
|
userID,
|
||||||
|
true,
|
||||||
|
).Model(&Friend{}).Count(&count)
|
||||||
|
if result.Error != nil {
|
||||||
|
log.Error("CountFriends(%d): %s", userID, result.Error)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
PaginateFriends gets a page of friends (or pending friend requests) as User objects ordered
|
PaginateFriends gets a page of friends (or pending friend requests) as User objects ordered
|
||||||
by friendship date.
|
by friendship date.
|
||||||
|
@ -302,6 +317,62 @@ func PaginateFriends(user *User, requests bool, sent bool, pager *Pagination) ([
|
||||||
return GetUsers(user, userIDs)
|
return GetUsers(user, userIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PaginateOtherUserFriends gets a page of friends from another user, for their profile page.
|
||||||
|
*/
|
||||||
|
func PaginateOtherUserFriends(currentUser *User, user *User, pager *Pagination) ([]*User, error) {
|
||||||
|
// We paginate over the Friend table.
|
||||||
|
var (
|
||||||
|
fs = []*Friend{}
|
||||||
|
blockedUserIDs = BlockedUserIDs(currentUser)
|
||||||
|
userIDs = []uint64{}
|
||||||
|
wheres = []string{}
|
||||||
|
placeholders = []interface{}{}
|
||||||
|
query = DB.Model(&Friend{})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get friends of the target user.
|
||||||
|
wheres = append(wheres, "source_user_id = ? AND approved = ?")
|
||||||
|
placeholders = append(placeholders, user.ID, true)
|
||||||
|
|
||||||
|
// Don't show our blocked users in the result.
|
||||||
|
if len(blockedUserIDs) > 0 {
|
||||||
|
wheres = append(wheres, "target_user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show disabled or banned users.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = friends.target_user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
query = query.Where(
|
||||||
|
strings.Join(wheres, " AND "),
|
||||||
|
placeholders...,
|
||||||
|
).Order(pager.Sort)
|
||||||
|
|
||||||
|
// Get the total count.
|
||||||
|
query.Count(&pager.Total)
|
||||||
|
|
||||||
|
// Get the page.
|
||||||
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&fs)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now of these friends get their User objects.
|
||||||
|
for _, friend := range fs {
|
||||||
|
userIDs = append(userIDs, friend.TargetUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUsers(user, userIDs)
|
||||||
|
}
|
||||||
|
|
||||||
// GetFriendRequests returns all pending friend requests for a user.
|
// GetFriendRequests returns all pending friend requests for a user.
|
||||||
func GetFriendRequests(userID uint64) ([]*Friend, error) {
|
func GetFriendRequests(userID uint64) ([]*Friend, error) {
|
||||||
var fs = []*Friend{}
|
var fs = []*Friend{}
|
||||||
|
|
|
@ -73,7 +73,7 @@ func WhoLikes(currentUser *User, tableName string, tableID uint64) ([]*User, int
|
||||||
remainder = total
|
remainder = total
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
placeholders = []interface{}{}
|
placeholders = []interface{}{}
|
||||||
blockedUserIDs = BlockedUserIDs(currentUser.ID)
|
blockedUserIDs = BlockedUserIDs(currentUser)
|
||||||
)
|
)
|
||||||
|
|
||||||
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
||||||
|
@ -117,7 +117,7 @@ func PaginateLikes(currentUser *User, tableName string, tableID uint64, pager *P
|
||||||
userIDs = []uint64{}
|
userIDs = []uint64{}
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
placeholders = []interface{}{}
|
placeholders = []interface{}{}
|
||||||
blockedUserIDs = BlockedUserIDs(currentUser.ID)
|
blockedUserIDs = BlockedUserIDs(currentUser)
|
||||||
)
|
)
|
||||||
|
|
||||||
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
||||||
|
|
|
@ -24,17 +24,17 @@ func GetMessage(id uint64) (*Message, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessages for a user.
|
// GetMessages for a user.
|
||||||
func GetMessages(userID uint64, sent bool, pager *Pagination) ([]*Message, error) {
|
func GetMessages(user *User, sent bool, pager *Pagination) ([]*Message, error) {
|
||||||
var (
|
var (
|
||||||
m = []*Message{}
|
m = []*Message{}
|
||||||
blockedUserIDs = BlockedUserIDs(userID)
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
where = []string{}
|
where = []string{}
|
||||||
placeholders = []interface{}{}
|
placeholders = []interface{}{}
|
||||||
)
|
)
|
||||||
|
|
||||||
if sent {
|
if sent {
|
||||||
where = append(where, "source_user_id = ?")
|
where = append(where, "source_user_id = ?")
|
||||||
placeholders = append(placeholders, userID)
|
placeholders = append(placeholders, user.ID)
|
||||||
|
|
||||||
if len(blockedUserIDs) > 0 {
|
if len(blockedUserIDs) > 0 {
|
||||||
where = append(where, "target_user_id NOT IN ?")
|
where = append(where, "target_user_id NOT IN ?")
|
||||||
|
@ -42,7 +42,7 @@ func GetMessages(userID uint64, sent bool, pager *Pagination) ([]*Message, error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
where = append(where, "target_user_id = ?")
|
where = append(where, "target_user_id = ?")
|
||||||
placeholders = append(placeholders, userID)
|
placeholders = append(placeholders, user.ID)
|
||||||
|
|
||||||
if len(blockedUserIDs) > 0 {
|
if len(blockedUserIDs) > 0 {
|
||||||
where = append(where, "source_user_id NOT IN ?")
|
where = append(where, "source_user_id NOT IN ?")
|
||||||
|
@ -50,6 +50,16 @@ func GetMessages(userID uint64, sent bool, pager *Pagination) ([]*Message, error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't show messages from banned or disabled accounts.
|
||||||
|
where = append(where, `
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id IN (messages.target_user_id, messages.source_user_id)
|
||||||
|
AND users.status <> 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
query := DB.Where(
|
query := DB.Where(
|
||||||
strings.Join(where, " AND "),
|
strings.Join(where, " AND "),
|
||||||
placeholders...,
|
placeholders...,
|
||||||
|
@ -100,11 +110,36 @@ func DeleteMessageThread(message *Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountUnreadMessages gets the count of unread messages for a user.
|
// CountUnreadMessages gets the count of unread messages for a user.
|
||||||
func CountUnreadMessages(userID uint64) (int64, error) {
|
func CountUnreadMessages(user *User) (int64, error) {
|
||||||
|
var (
|
||||||
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
|
where = []string{
|
||||||
|
"target_user_id = ? AND read = ?",
|
||||||
|
}
|
||||||
|
placeholders = []interface{}{
|
||||||
|
user.ID, false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Blocking user IDs?
|
||||||
|
if len(blockedUserIDs) > 0 {
|
||||||
|
where = append(where, "source_user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show messages from banned or disabled accounts.
|
||||||
|
where = append(where, `
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id IN (messages.target_user_id, messages.source_user_id)
|
||||||
|
AND users.status <> 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
query := DB.Where(
|
query := DB.Where(
|
||||||
"target_user_id = ? AND read = ?",
|
strings.Join(where, " AND "),
|
||||||
userID,
|
placeholders...,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var count int64
|
var count int64
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.nonshy.com/nonshy/website/pkg/log"
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
@ -139,11 +140,36 @@ func ClearAllNotifications(user *User) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountUnreadNotifications gets the count of unread Notifications for a user.
|
// CountUnreadNotifications gets the count of unread Notifications for a user.
|
||||||
func CountUnreadNotifications(userID uint64) (int64, error) {
|
func CountUnreadNotifications(user *User) (int64, error) {
|
||||||
|
var (
|
||||||
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
|
where = []string{
|
||||||
|
"user_id = ? AND read = ?",
|
||||||
|
}
|
||||||
|
placeholders = []interface{}{
|
||||||
|
user.ID, false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Blocking user IDs?
|
||||||
|
if len(blockedUserIDs) > 0 {
|
||||||
|
where = append(where, "about_user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show messages from banned or disabled accounts.
|
||||||
|
where = append(where, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = notifications.about_user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
query := DB.Where(
|
query := DB.Where(
|
||||||
"user_id = ? AND read = ?",
|
strings.Join(where, " AND "),
|
||||||
userID,
|
placeholders...,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var count int64
|
var count int64
|
||||||
|
@ -153,11 +179,36 @@ func CountUnreadNotifications(userID uint64) (int64, error) {
|
||||||
|
|
||||||
// PaginateNotifications returns the user's notifications.
|
// PaginateNotifications returns the user's notifications.
|
||||||
func PaginateNotifications(user *User, pager *Pagination) ([]*Notification, error) {
|
func PaginateNotifications(user *User, pager *Pagination) ([]*Notification, error) {
|
||||||
var ns = []*Notification{}
|
var (
|
||||||
|
ns = []*Notification{}
|
||||||
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
|
where = []string{
|
||||||
|
"user_id = ?",
|
||||||
|
}
|
||||||
|
placeholders = []interface{}{
|
||||||
|
user.ID,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Suppress historic notifications about blocked users.
|
||||||
|
if len(blockedUserIDs) > 0 {
|
||||||
|
where = append(where, "about_user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show notifications from banned or disabled accounts.
|
||||||
|
where = append(where, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = notifications.about_user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
query := (&Notification{}).Preload().Where(
|
query := (&Notification{}).Preload().Where(
|
||||||
"user_id = ?",
|
strings.Join(where, " AND "),
|
||||||
user.ID,
|
placeholders...,
|
||||||
).Order(
|
).Order(
|
||||||
pager.Sort,
|
pager.Sort,
|
||||||
)
|
)
|
||||||
|
@ -348,8 +399,6 @@ func (nm NotificationMap) mapNotificationComments(IDs []uint64) {
|
||||||
commentIDs = append(commentIDs, row.CommentID)
|
commentIDs = append(commentIDs, row.CommentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Error("MAP COMMENT IDS GOT: %+v", commentIDs)
|
|
||||||
|
|
||||||
// Load the comments.
|
// Load the comments.
|
||||||
if len(commentIDs) > 0 {
|
if len(commentIDs) > 0 {
|
||||||
if comments, err := GetComments(commentIDs); err != nil {
|
if comments, err := GetComments(commentIDs); err != nil {
|
||||||
|
|
|
@ -397,7 +397,7 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
||||||
userID = user.ID
|
userID = user.ID
|
||||||
explicitOK = user.Explicit // User opted-in for explicit content
|
explicitOK = user.Explicit // User opted-in for explicit content
|
||||||
|
|
||||||
blocklist = BlockedUserIDs(userID)
|
blocklist = BlockedUserIDs(user)
|
||||||
friendIDs = FriendIDs(userID)
|
friendIDs = FriendIDs(userID)
|
||||||
privateUserIDs = PrivateGrantedUserIDs(userID)
|
privateUserIDs = PrivateGrantedUserIDs(userID)
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
|
|
|
@ -45,11 +45,67 @@ func GetThread(id uint64) (*Thread, error) {
|
||||||
// GetThreads queries a set of thread IDs and returns them mapped.
|
// GetThreads queries a set of thread IDs and returns them mapped.
|
||||||
func GetThreads(IDs []uint64) (map[uint64]*Thread, error) {
|
func GetThreads(IDs []uint64) (map[uint64]*Thread, error) {
|
||||||
var (
|
var (
|
||||||
mt = map[uint64]*Thread{}
|
mt = map[uint64]*Thread{}
|
||||||
ts = []*Thread{}
|
ts = []*Thread{}
|
||||||
|
wheres = []string{"threads.id IN ?"}
|
||||||
|
placeholders = []interface{}{IDs}
|
||||||
)
|
)
|
||||||
|
|
||||||
result := (&Thread{}).Preload().Where("id IN ?", IDs).Find(&ts)
|
// Don't show threads from banned or disabled accounts.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = comments.user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
result := (&Thread{}).Preload().Joins(
|
||||||
|
"LEFT OUTER JOIN comments ON (comments.id = threads.comment_id)",
|
||||||
|
).Where(
|
||||||
|
strings.Join(wheres, " AND "),
|
||||||
|
placeholders...,
|
||||||
|
).Find(&ts)
|
||||||
|
for _, row := range ts {
|
||||||
|
mt[row.ID] = row
|
||||||
|
}
|
||||||
|
|
||||||
|
return mt, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThreadsAsUser queries a set of thread IDs and returns them mapped, taking blocklists into consideration.
|
||||||
|
func GetThreadsAsUser(currentUser *User, IDs []uint64) (map[uint64]*Thread, error) {
|
||||||
|
var (
|
||||||
|
mt = map[uint64]*Thread{}
|
||||||
|
ts = []*Thread{}
|
||||||
|
blockedUserIDs = BlockedUserIDs(currentUser)
|
||||||
|
wheres = []string{"threads.id IN ?"}
|
||||||
|
placeholders = []interface{}{IDs}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Blocked users?
|
||||||
|
if len(blockedUserIDs) > 0 {
|
||||||
|
wheres = append(wheres, "comments.user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, blockedUserIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show threads from banned or disabled accounts.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = comments.user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
result := (&Thread{}).Preload().Joins(
|
||||||
|
"LEFT OUTER JOIN comments ON (comments.id = threads.comment_id)",
|
||||||
|
).Where(
|
||||||
|
strings.Join(wheres, " AND "),
|
||||||
|
placeholders...,
|
||||||
|
).Find(&ts)
|
||||||
for _, row := range ts {
|
for _, row := range ts {
|
||||||
mt[row.ID] = row
|
mt[row.ID] = row
|
||||||
}
|
}
|
||||||
|
@ -170,7 +226,19 @@ func PaginateThreads(user *User, forum *Forum, pager *Pagination) ([]*Thread, er
|
||||||
wheres = append(wheres, "explicit IS NOT TRUE")
|
wheres = append(wheres, "explicit IS NOT TRUE")
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where(
|
// Don't show threads from banned or disabled accounts.
|
||||||
|
wheres = append(wheres, `
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE users.id = comments.user_id
|
||||||
|
AND users.status = 'active'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
query = query.Joins(
|
||||||
|
"LEFT OUTER JOIN comments ON (comments.id = threads.comment_id)",
|
||||||
|
).Where(
|
||||||
strings.Join(wheres, " AND "),
|
strings.Join(wheres, " AND "),
|
||||||
placeholders...,
|
placeholders...,
|
||||||
).Order(pager.Sort)
|
).Order(pager.Sort)
|
||||||
|
|
|
@ -43,7 +43,8 @@ type User struct {
|
||||||
UserRelationship UserRelationship `gorm:"-"`
|
UserRelationship UserRelationship `gorm:"-"`
|
||||||
|
|
||||||
// Caches
|
// Caches
|
||||||
cachePhotoTypes map[PhotoVisibility]struct{}
|
cachePhotoTypes map[PhotoVisibility]struct{}
|
||||||
|
cacheBlockedUserIDs []uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserVisibility string
|
type UserVisibility string
|
||||||
|
@ -231,7 +232,7 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
|
||||||
joins string // GPS location join.
|
joins string // GPS location join.
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
placeholders = []interface{}{}
|
placeholders = []interface{}{}
|
||||||
blockedUserIDs = BlockedUserIDs(user.ID)
|
blockedUserIDs = BlockedUserIDs(user)
|
||||||
myLocation = GetUserLocation(user.ID)
|
myLocation = GetUserLocation(user.ID)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@ func New() http.Handler {
|
||||||
mux.Handle("/settings/age-gate", middleware.LoginRequired(account.AgeGate()))
|
mux.Handle("/settings/age-gate", middleware.LoginRequired(account.AgeGate()))
|
||||||
mux.Handle("/account/two-factor/setup", middleware.LoginRequired(account.Setup2FA()))
|
mux.Handle("/account/two-factor/setup", middleware.LoginRequired(account.Setup2FA()))
|
||||||
mux.Handle("/account/delete", middleware.LoginRequired(account.Delete()))
|
mux.Handle("/account/delete", middleware.LoginRequired(account.Delete()))
|
||||||
|
mux.Handle("/account/deactivate", middleware.LoginRequired(account.Deactivate()))
|
||||||
|
mux.Handle("/account/reactivate", middleware.LoginRequired(account.Reactivate()))
|
||||||
mux.Handle("/u/", account.Profile()) // public access OK
|
mux.Handle("/u/", account.Profile()) // public access OK
|
||||||
mux.Handle("/photo/upload", middleware.LoginRequired(photo.Upload()))
|
mux.Handle("/photo/upload", middleware.LoginRequired(photo.Upload()))
|
||||||
mux.Handle("/photo/u/", middleware.LoginRequired(photo.UserPhotos()))
|
mux.Handle("/photo/u/", middleware.LoginRequired(photo.UserPhotos()))
|
||||||
|
@ -63,6 +65,7 @@ func New() http.Handler {
|
||||||
mux.Handle("/messages/delete", middleware.LoginRequired(inbox.Delete()))
|
mux.Handle("/messages/delete", middleware.LoginRequired(inbox.Delete()))
|
||||||
mux.Handle("/friends", middleware.LoginRequired(friend.Friends()))
|
mux.Handle("/friends", middleware.LoginRequired(friend.Friends()))
|
||||||
mux.Handle("/friends/add", middleware.LoginRequired(friend.AddFriend()))
|
mux.Handle("/friends/add", middleware.LoginRequired(friend.AddFriend()))
|
||||||
|
mux.Handle("/friends/u/", middleware.CertRequired(account.UserFriends()))
|
||||||
mux.Handle("/users/block", middleware.LoginRequired(block.BlockUser()))
|
mux.Handle("/users/block", middleware.LoginRequired(block.BlockUser()))
|
||||||
mux.Handle("/users/blocked", middleware.LoginRequired(block.Blocked()))
|
mux.Handle("/users/blocked", middleware.LoginRequired(block.Blocked()))
|
||||||
mux.Handle("/users/blocklist/add", middleware.LoginRequired(block.AddUser()))
|
mux.Handle("/users/blocklist/add", middleware.LoginRequired(block.AddUser()))
|
||||||
|
|
|
@ -77,7 +77,7 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get unread message count.
|
// Get unread message count.
|
||||||
if count, err := models.CountUnreadMessages(user.ID); err == nil {
|
if count, err := models.CountUnreadMessages(user); err == nil {
|
||||||
m["NavUnreadMessages"] = count
|
m["NavUnreadMessages"] = count
|
||||||
countMessages = count
|
countMessages = count
|
||||||
} else {
|
} else {
|
||||||
|
@ -93,7 +93,7 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count other notifications.
|
// Count other notifications.
|
||||||
if count, err := models.CountUnreadNotifications(user.ID); err == nil {
|
if count, err := models.CountUnreadNotifications(user); err == nil {
|
||||||
m["NavUnreadNotifications"] = count
|
m["NavUnreadNotifications"] = count
|
||||||
countNotifications = count
|
countNotifications = count
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -236,7 +236,7 @@
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
<li>
|
<li>
|
||||||
<a href="/account/delete">
|
<a href="/settings#deactivate">
|
||||||
<span class="icon"><i class="fa fa-trash"></i></span>
|
<span class="icon"><i class="fa fa-trash"></i></span>
|
||||||
Delete account
|
Delete account
|
||||||
</a>
|
</a>
|
||||||
|
|
86
web/templates/account/deactivate.html
Normal file
86
web/templates/account/deactivate.html
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
{{define "title"}}Deactivate Account{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero is-info is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
Deactivate Account
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="block p-4">
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="card" style="max-width: 512px">
|
||||||
|
<header class="card-header has-background-danger">
|
||||||
|
<p class="card-header-title has-text-light">
|
||||||
|
<span class="icon"><i class="fa fa-trash"></i></span>
|
||||||
|
Deactivate My Account
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<form method="POST" action="/account/deactivate">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<div class="block content">
|
||||||
|
<p>
|
||||||
|
On this page you may <strong>temporarily deactivate your account</strong>, which
|
||||||
|
will hide your profile from everywhere on the website (as if your account were
|
||||||
|
deleted), but in a recoverable way where you may log in and re-activate your
|
||||||
|
account in the future, should you decide to come back.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When you deactivate your account:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Your profile will be hidden from everywhere on the website: for example
|
||||||
|
you will not be searchable on the Member Directory.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
People you had exchanged Messages with will no longer see your conversations
|
||||||
|
in their inbox or outbox page.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
All comments and forum posts you made will be hidden from everybody on
|
||||||
|
the website.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
You will be signed out of your {{PrettyTitle}} account. If you wish to
|
||||||
|
re-activate your account in the future, you may sign back in and the only
|
||||||
|
options you will be given will be to re-activate your account, delete it
|
||||||
|
permanently, or log out.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To confirm deactivation of your account, please enter your current account
|
||||||
|
password into the box below.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="password">Your current password:</label>
|
||||||
|
<input type="password" class="input"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="Password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block has-text-center">
|
||||||
|
<button type="submit" class="button is-danger">Deactivate My Account</button>
|
||||||
|
<a href="/me" class="button is-success">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
159
web/templates/account/friends.html
Normal file
159
web/templates/account/friends.html
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
{{define "title"}}Friends of {{.User.Username}}{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
{{$Root := .}}
|
||||||
|
<section class="hero is-link is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
<i class="fa fa-user-group mr-2"></i>
|
||||||
|
Friends of {{.User.Username}}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="block p-4">
|
||||||
|
<div class="tabs is-boxed mb-0">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-user"></i>
|
||||||
|
</span>
|
||||||
|
<span>Profile</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/photo/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-image"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Photos
|
||||||
|
{{if .PhotoCount}}<span class="tag is-link is-light ml-1">{{.PhotoCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/notes/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-pen-to-square"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Notes
|
||||||
|
{{if .NoteCount}}<span class="tag is-link is-light ml-1">{{.NoteCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="/friends/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-user-group"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Friends
|
||||||
|
{{if .FriendCount}}<span class="tag is-link is-light ml-1">{{.FriendCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
Found {{.Pager.Total}} friend{{Pluralize64 .Pager.Total}}
|
||||||
|
(page {{.Pager.Page}} of {{.Pager.Pages}}).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
{{SimplePager .Pager}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
|
||||||
|
{{range .Friends}}
|
||||||
|
<div class="column is-half-tablet is-one-third-desktop">
|
||||||
|
|
||||||
|
<form action="/friends/add" method="POST">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<input type="hidden" name="username" value="{{.Username}}">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="media block">
|
||||||
|
<div class="media-left">
|
||||||
|
{{template "avatar-64x64" .}}
|
||||||
|
|
||||||
|
<!-- Friendship badge -->
|
||||||
|
{{if $Root.FriendMap.Get .ID}}
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<span class="is-size-7 has-text-warning-dark">
|
||||||
|
<i class="fa fa-user-group" title="Friends"></i>
|
||||||
|
Friends
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<p class="title is-4">
|
||||||
|
<a href="/u/{{.Username}}" class="has-text-dark">
|
||||||
|
{{if ne .Status "active"}}
|
||||||
|
<del>{{.NameOrUsername}}</del>
|
||||||
|
{{else}}
|
||||||
|
{{.NameOrUsername}}
|
||||||
|
{{end}}
|
||||||
|
{{if and $Root.CurrentUser.IsInnerCircle .InnerCircle}}
|
||||||
|
<img src="/static/img/circle-16.png">
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
{{if eq .Visibility "private"}}
|
||||||
|
<sup class="fa fa-mask is-size-7" title="Private Profile"></sup>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
<p class="subtitle is-6 mb-2">
|
||||||
|
<span class="icon"><i class="fa fa-user"></i></span>
|
||||||
|
<a href="/u/{{.Username}}">{{.Username}}</a>
|
||||||
|
</p>
|
||||||
|
{{if .GetProfileField "city"}}
|
||||||
|
<p class="subtitle is-6 mb-2">
|
||||||
|
{{.GetProfileField "city"}}
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
<p class="subtitle is-7 mb-2">
|
||||||
|
{{if or (ne .GetDisplayAge "n/a")}}
|
||||||
|
<span class="mr-2">{{.GetDisplayAge}}</span>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GetProfileField "gender"}}
|
||||||
|
<span class="mr-2">{{.GetProfileField "gender"}}</span>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GetProfileField "pronouns"}}
|
||||||
|
<span class="mr-2">{{.GetProfileField "pronouns"}}</span>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GetProfileField "orientation"}}
|
||||||
|
<span class="mr-2">{{.GetProfileField "orientation"}}</span>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div><!-- media-block -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}<!-- range .Friends -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
{{SimplePager .Pager}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -295,6 +295,17 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/friends/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-user-group"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Friends
|
||||||
|
{{if .FriendCount}}<span class="tag is-link is-light ml-1">{{.FriendCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,16 @@
|
||||||
<a href="/settings#account" class="nonshy-tab-button">
|
<a href="/settings#account" class="nonshy-tab-button">
|
||||||
<strong><i class="fa fa-user mr-1"></i> Account Settings</strong>
|
<strong><i class="fa fa-user mr-1"></i> Account Settings</strong>
|
||||||
<p class="help">
|
<p class="help">
|
||||||
Change password or e-mail; Two-factor auth (2FA); delete account.
|
Change password or e-mail; set up Two-Factor Authentication (2FA).
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/settings#deactivate" class="nonshy-tab-button">
|
||||||
|
<strong><i class="fa fa-exclamation-triangle mr-1"></i> Deactivate Account</strong>
|
||||||
|
<p class="help">
|
||||||
|
Temporarily deactivate or permanently delete my account.
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -744,13 +753,41 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deactivate or Delete Account -->
|
||||||
|
<div id="deactivate">
|
||||||
|
<div class="card mb-5">
|
||||||
|
<header class="card-header has-background-warning">
|
||||||
|
<p class="card-header-title has-text-dark-dark">
|
||||||
|
<i class="fa fa-lock pr-2"></i>
|
||||||
|
Deactivate My Account
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="card-content content">
|
||||||
|
<p>
|
||||||
|
If you'd like to take a break from {{PrettyTitle}} but think you may want to
|
||||||
|
come back later, you may <strong>temporarily deactivate your account</strong>
|
||||||
|
which will mark your profile as hidden from everywhere on the website (as if
|
||||||
|
it were deleted), but in a way that you can recover your account and reactivate
|
||||||
|
it again in the future.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/account/deactivate" class="button is-primary">
|
||||||
|
Temporarily Deactivate My Account
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Delete Account -->
|
<!-- Delete Account -->
|
||||||
<div class="card mb-5">
|
<div class="card mb-5">
|
||||||
<header class="card-header has-background-danger">
|
<header class="card-header has-background-danger">
|
||||||
<p class="card-header-title has-text-light">
|
<p class="card-header-title has-text-light">
|
||||||
<i class="fa fa-exclamation-triangle pr-2"></i>
|
<i class="fa fa-exclamation-triangle pr-2"></i>
|
||||||
Delete Account
|
Permanently Delete My Account
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -762,7 +799,7 @@
|
||||||
|
|
||||||
<p class="block">
|
<p class="block">
|
||||||
<a href="/account/delete" class="button is-danger">
|
<a href="/account/delete" class="button is-danger">
|
||||||
Delete My Account
|
Permanently Delete My Account
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -782,7 +819,8 @@ window.addEventListener("DOMContentLoaded", (event) => {
|
||||||
$prefs = document.querySelector("#prefs"),
|
$prefs = document.querySelector("#prefs"),
|
||||||
$location = document.querySelector("#location"),
|
$location = document.querySelector("#location"),
|
||||||
$privacy = document.querySelector("#privacy"),
|
$privacy = document.querySelector("#privacy"),
|
||||||
$account = document.querySelector("#account"),
|
$account = document.querySelector("#account")
|
||||||
|
$deactivate = document.querySelector("#deactivate"),
|
||||||
buttons = Array.from(document.getElementsByClassName("nonshy-tab-button"));
|
buttons = Array.from(document.getElementsByClassName("nonshy-tab-button"));
|
||||||
|
|
||||||
// Hide all by default.
|
// Hide all by default.
|
||||||
|
@ -791,6 +829,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
|
||||||
$location.style.display = 'none';
|
$location.style.display = 'none';
|
||||||
$privacy.style.display = 'none';
|
$privacy.style.display = 'none';
|
||||||
$account.style.display = 'none';
|
$account.style.display = 'none';
|
||||||
|
$deactivate.style.display = 'none';
|
||||||
|
|
||||||
// Current tab to select by default.
|
// Current tab to select by default.
|
||||||
let $activeTab = $profile;
|
let $activeTab = $profile;
|
||||||
|
@ -813,6 +852,9 @@ window.addEventListener("DOMContentLoaded", (event) => {
|
||||||
case "account":
|
case "account":
|
||||||
$activeTab = $account;
|
$activeTab = $account;
|
||||||
break;
|
break;
|
||||||
|
case "deactivate":
|
||||||
|
$activeTab = $deactivate;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$activeTab = $profile;
|
$activeTab = $profile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,17 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/friends/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-user-group"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Friends
|
||||||
|
{{if .FriendCount}}<span class="tag is-link is-light ml-1">{{.FriendCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
57
web/templates/errors/disabled_account.html
Normal file
57
web/templates/errors/disabled_account.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{{define "title"}}Reactivate Your Account{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero block is-link is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">Welcome back!</h1>
|
||||||
|
<h2 class="subtitle">Reactivate your account?</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="block content p-4 mb-0">
|
||||||
|
<h1>Your account is currently deactivated</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Welcome back to {{PrettyTitle}}! You had requested to deactivate your account before, and you must make
|
||||||
|
a decision as to how you want to proceed from here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Reactivate Your Account?</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you wish to pick up where you left off, you may <strong>reactivate your account</strong> by clicking
|
||||||
|
on the button below. It will be as if you never left: your profile, photo gallery, friends, messages and
|
||||||
|
notifications will all be how you left them.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/account/reactivate" class="button is-success">Reactivate My Account</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Permanently Delete Your Account?</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you've decided you really <em>don't</em> want to have a {{PrettyTitle}} account anymore, you may
|
||||||
|
click the button below to <strong>permanently delete your account</strong> instead. We will delete your
|
||||||
|
profile, photos, and all the associated data we have about your account. This will be an irreversible
|
||||||
|
process!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/account/delete" class="button is-danger">Permanently Delete My Account</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Log Out</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you're not sure what to do, you may log out of your account and come back later.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/account/logout" class="button is-info">Log out of my account</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -51,7 +51,7 @@
|
||||||
<a href="/u/{{$User.Username}}">
|
<a href="/u/{{$User.Username}}">
|
||||||
{{template "avatar-96x96" $User}}
|
{{template "avatar-96x96" $User}}
|
||||||
<div>
|
<div>
|
||||||
<a href="/u/{{$User.Username}}" class="is-size-7">{{$User.Username}}</a>
|
<a href="/u/{{$User.Username}}" class="is-size-7">{{or $User.Username "[unavailable]"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -145,6 +145,17 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/friends/u/{{.User.Username}}">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-user-group"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Friends
|
||||||
|
{{if .FriendCount}}<span class="tag is-link is-light ml-1">{{.FriendCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user