website/pkg/models/shy_accounts.go
Noah Petherbridge 4f04323d5a Public Avatar Consent Page
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.
2024-06-29 16:44:18 -07:00

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
}