4f04323d5a
The nonshy website is changing the policy on profile pictures. From August 30, the square cropped avatar images will need to be publicly viewable to everyone. This implements the first pass of the rollout: * Add the Public Avatar Consent Page which explains the change to users and asks for their acknowledgement. The link is available from their User Settings page, near their Certification Photo link. * When users (with non-public avatars) accept the change: their square cropped avatar will become visible to everybody, instead of showing a placeholder avatar. * Users can change their mind and opt back out, which will again show the placeholder avatar. * The Certification Required middleware will automatically enforce the consent page once the scheduled go-live date arrives. Next steps are: 1. Post an announcement on the forum about the upcoming change and link users to the consent form if they want to check it out early. 2. Update the nonshy site to add banners to places like the User Dashboard for users who will be affected by the change, to link them to the forum post and the consent page.
134 lines
3.1 KiB
Go
134 lines
3.1 KiB
Go
package models
|
|
|
|
// Supplementary functions to do with Shy Accounts and PublicAvatar consent.
|
|
|
|
import "code.nonshy.com/nonshy/website/pkg/log"
|
|
|
|
// IsShy returns whether the user might have an "empty" profile from the perspective of anybody.
|
|
//
|
|
// An empty profile means their profile is Private or else ALL of their photos are non-public; so that
|
|
// somebody viewing their page might see nothing at all from them and consider them a "blank" profile.
|
|
func (u *User) IsShy() bool {
|
|
// NOTE: if you change the logic for Shy Accounts, also align your changes
|
|
// in the functions: WhereClauseShyAccounts.
|
|
|
|
// Non-certified users are considered empty.
|
|
if !u.Certified {
|
|
return true
|
|
}
|
|
|
|
// Private profile automatically applies.
|
|
if u.Visibility == UserVisibilityPrivate {
|
|
return true
|
|
}
|
|
|
|
// If ALL of our photos are non-public, that counts too.
|
|
var photoTypes = u.DistinctPhotoTypes()
|
|
if _, ok := photoTypes[PhotoPublic]; !ok {
|
|
log.Info("IsEmptyProfile: true because visibilities %+v did not include public", photoTypes)
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// WhereClauseShyAccounts returns SQL query fragments when running User table queries
|
|
// that will filter for shy accounts.
|
|
//
|
|
// This is used by SearchUsers and MapShyAccounts.
|
|
func WhereClauseShyAccounts() (where string, placeholders []interface{}) {
|
|
where = `(
|
|
certified IS NOT true
|
|
OR visibility = ?
|
|
OR NOT EXISTS (
|
|
SELECT 1 FROM photos
|
|
WHERE user_id = users.id
|
|
AND visibility = ?
|
|
)
|
|
)`
|
|
placeholders = []interface{}{
|
|
UserVisibilityPrivate,
|
|
PhotoPublic,
|
|
}
|
|
return
|
|
}
|
|
|
|
// ShyMap maps user IDs to Shy Account status in bulk queries.
|
|
type ShyMap map[uint64]bool
|
|
|
|
// MapShyAccounts looks up a set of user IDs in bulk and returns a ShyMap suitable for templates.
|
|
func MapShyAccounts(users []*User) ShyMap {
|
|
var (
|
|
usermap = ShyMap{}
|
|
set = map[uint64]interface{}{}
|
|
distinct = []uint64{}
|
|
)
|
|
|
|
// Uniqueify users.
|
|
for _, user := range users {
|
|
if _, ok := set[user.ID]; ok {
|
|
continue
|
|
}
|
|
set[user.ID] = nil
|
|
distinct = append(distinct, user.ID)
|
|
}
|
|
|
|
var (
|
|
matched = []*User{}
|
|
where, placeholders = WhereClauseShyAccounts()
|
|
result = (&User{}).
|
|
Preload().
|
|
Where("id IN ?", distinct).
|
|
Where(where, placeholders...).
|
|
Find(&matched)
|
|
)
|
|
|
|
if result.Error == nil {
|
|
for _, row := range matched {
|
|
usermap[row.ID] = true
|
|
}
|
|
}
|
|
|
|
return usermap
|
|
}
|
|
|
|
// Get a user from the ShyMap.
|
|
func (um ShyMap) Get(id uint64) bool {
|
|
return um[id]
|
|
}
|
|
|
|
// MapPublicAvatarConsent checks a set of users if they have answered the public avatar consent form.
|
|
func MapPublicAvatarConsent(users []*User) (map[uint64]bool, error) {
|
|
var (
|
|
userIDs = []uint64{}
|
|
result = map[uint64]bool{}
|
|
)
|
|
for _, user := range users {
|
|
userIDs = append(userIDs, user.ID)
|
|
result[user.ID] = false
|
|
}
|
|
|
|
type record struct {
|
|
UserID uint64
|
|
Value string
|
|
}
|
|
var rows = []record{}
|
|
|
|
if res := DB.Table(
|
|
"profile_fields",
|
|
).Select(
|
|
"user_id, value",
|
|
).Where(
|
|
"user_id IN ? AND name='public_avatar_consent'",
|
|
userIDs,
|
|
).Scan(&rows); res.Error != nil {
|
|
return nil, res.Error
|
|
}
|
|
|
|
for _, row := range rows {
|
|
result[row.UserID] = row.Value == "true"
|
|
}
|
|
|
|
return result, nil
|
|
}
|