280 lines
6.5 KiB
Go
280 lines
6.5 KiB
Go
package models
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
)
|
|
|
|
// Like table.
|
|
type Like struct {
|
|
ID uint64 `gorm:"primaryKey"`
|
|
UserID uint64 `gorm:"index"` // who it belongs to
|
|
TableName string `gorm:"index:idx_likes_composite"`
|
|
TableID uint64 `gorm:"index:idx_likes_composite"`
|
|
CreatedAt time.Time `gorm:"index"`
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// LikeableTables are the set of table names that allow likes (used by the JSON API).
|
|
var LikeableTables = map[string]interface{}{
|
|
"photos": nil,
|
|
"users": nil,
|
|
"comments": nil,
|
|
}
|
|
|
|
// AddLike to something.
|
|
func AddLike(user *User, tableName string, tableID uint64) error {
|
|
// Already has a like?
|
|
var like = &Like{}
|
|
exist := DB.Model(like).Where(
|
|
"user_id = ? AND table_name = ? AND table_id = ?",
|
|
user.ID, tableName, tableID,
|
|
).First(&like)
|
|
if exist.Error == nil {
|
|
return nil
|
|
}
|
|
|
|
// Create it.
|
|
like = &Like{
|
|
UserID: user.ID,
|
|
TableName: tableName,
|
|
TableID: tableID,
|
|
}
|
|
return DB.Create(like).Error
|
|
}
|
|
|
|
// Unlike something.
|
|
func Unlike(user *User, tableName string, tableID uint64) error {
|
|
result := DB.Where(
|
|
"user_id = ? AND table_name = ? AND table_id = ?",
|
|
user.ID, tableName, tableID,
|
|
).Delete(&Like{})
|
|
return result.Error
|
|
}
|
|
|
|
// CountLikes on something.
|
|
func CountLikes(tableName string, tableID uint64) int64 {
|
|
var count int64
|
|
DB.Model(&Like{}).Where(
|
|
"table_name = ? AND table_id = ?",
|
|
tableName, tableID,
|
|
).Count(&count)
|
|
return count
|
|
}
|
|
|
|
// CountLikesGiven by a user.
|
|
func CountLikesGiven(user *User) int64 {
|
|
var count int64
|
|
DB.Model(&Like{}).Where(
|
|
"user_id = ?",
|
|
user.ID,
|
|
).Count(&count)
|
|
return count
|
|
}
|
|
|
|
// CountLikesReceived by a user.
|
|
func CountLikesReceived(user *User) int64 {
|
|
var count int64
|
|
|
|
// Do a UNION query as it's more efficient than joining Likes to all the other tables.
|
|
result := DB.Raw(`
|
|
SELECT sum(c) FROM (
|
|
SELECT count(*) AS c
|
|
FROM likes
|
|
WHERE likes.table_name = 'users' AND likes.table_id = ?
|
|
|
|
UNION
|
|
|
|
SELECT count(*) AS c
|
|
FROM likes
|
|
JOIN photos ON (likes.table_name='photos' AND likes.table_id=photos.id)
|
|
WHERE photos.user_id = ?
|
|
|
|
UNION
|
|
|
|
SELECT count(*) AS c
|
|
FROM likes
|
|
JOIN comments ON (likes.table_name = 'comments' AND likes.table_id=comments.id)
|
|
WHERE comments.user_id = ?
|
|
) AS c
|
|
`, user.ID, user.ID, user.ID).Scan(&count)
|
|
|
|
if result.Error != nil {
|
|
log.Error("CountLikesReceived: %s", result.Error)
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// WhoLikes something. Returns the first couple users and a count of the remainder.
|
|
func WhoLikes(currentUser *User, tableName string, tableID uint64) ([]*User, int64, error) {
|
|
var (
|
|
userIDs = []uint64{}
|
|
likes = []*Like{}
|
|
total = CountLikes(tableName, tableID)
|
|
remainder = total
|
|
wheres = []string{}
|
|
placeholders = []interface{}{}
|
|
blockedUserIDs = BlockedUserIDs(currentUser)
|
|
)
|
|
|
|
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
|
placeholders = append(placeholders, tableName, tableID)
|
|
|
|
if len(blockedUserIDs) > 0 {
|
|
wheres = append(wheres, "user_id NOT IN ?")
|
|
placeholders = append(placeholders, blockedUserIDs)
|
|
}
|
|
|
|
res := DB.Model(&Like{}).Where(
|
|
strings.Join(wheres, " AND "),
|
|
placeholders...,
|
|
).Order("created_at DESC").Limit(2).Scan(&likes)
|
|
|
|
if res.Error != nil {
|
|
return nil, 0, res.Error
|
|
}
|
|
|
|
// Subtract the (up to two) likes from the total.
|
|
remainder -= int64(len(likes))
|
|
|
|
// Collect the user IDs to look up.
|
|
for _, row := range likes {
|
|
userIDs = append(userIDs, row.UserID)
|
|
}
|
|
|
|
// Look up the users and return the remainder.
|
|
users, err := GetUsers(nil, userIDs)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return users, remainder, nil
|
|
}
|
|
|
|
// PaginateLikes returns a paged view of users who've liked something.
|
|
func PaginateLikes(currentUser *User, tableName string, tableID uint64, pager *Pagination) ([]*User, error) {
|
|
var (
|
|
l = []*Like{}
|
|
userIDs = []uint64{}
|
|
wheres = []string{}
|
|
placeholders = []interface{}{}
|
|
blockedUserIDs = BlockedUserIDs(currentUser)
|
|
)
|
|
|
|
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
|
placeholders = append(placeholders, tableName, tableID)
|
|
|
|
if len(blockedUserIDs) > 0 {
|
|
wheres = append(wheres, "user_id NOT IN ?")
|
|
placeholders = append(placeholders, blockedUserIDs)
|
|
}
|
|
|
|
query := DB.Where(
|
|
strings.Join(wheres, " AND "),
|
|
placeholders...,
|
|
).Order(
|
|
pager.Sort,
|
|
)
|
|
|
|
// Get the total count.
|
|
query.Model(&Like{}).Count(&pager.Total)
|
|
|
|
// Get the page of likes.
|
|
result := query.Offset(
|
|
pager.GetOffset(),
|
|
).Limit(pager.PerPage).Find(&l)
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
|
|
// Map the user IDs in.
|
|
for _, like := range l {
|
|
userIDs = append(userIDs, like.UserID)
|
|
}
|
|
return GetUsers(currentUser, userIDs)
|
|
}
|
|
|
|
// LikedIDs filters a set of table IDs to ones the user likes.
|
|
func LikedIDs(user *User, tableName string, tableIDs []uint64) ([]uint64, error) {
|
|
var result = []uint64{}
|
|
if r := DB.Table(
|
|
"likes",
|
|
).Select(
|
|
"table_id",
|
|
).Where(
|
|
"user_id = ? AND table_name = ? AND table_id IN ?",
|
|
user.ID, tableName, tableIDs,
|
|
).Scan(&result); r.Error != nil {
|
|
return result, r.Error
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// LikeMap maps table IDs to Likes metadata.
|
|
type LikeMap map[uint64]*LikeStats
|
|
|
|
// Get like stats from the map.
|
|
func (lm LikeMap) Get(id uint64) *LikeStats {
|
|
if stats, ok := lm[id]; ok {
|
|
return stats
|
|
}
|
|
return &LikeStats{}
|
|
}
|
|
|
|
// LikeStats holds mapped statistics about liked objects.
|
|
type LikeStats struct {
|
|
Count int64 // how many total
|
|
UserLikes bool // current user likes it
|
|
}
|
|
|
|
// MapLikes over a set of table IDs.
|
|
func MapLikes(user *User, tableName string, tableIDs []uint64) LikeMap {
|
|
var result = LikeMap{}
|
|
|
|
// Initialize the result set.
|
|
for _, id := range tableIDs {
|
|
result[id] = &LikeStats{}
|
|
}
|
|
|
|
// Hold the result of the grouped count query.
|
|
type group struct {
|
|
ID uint64
|
|
Likes int64
|
|
}
|
|
var groups = []group{}
|
|
|
|
// Map the counts of likes to each of these IDs.
|
|
if res := DB.Table(
|
|
"likes",
|
|
).Select(
|
|
"table_id AS id, count(id) AS likes",
|
|
).Where(
|
|
"table_name = ? AND table_id IN ?",
|
|
tableName, tableIDs,
|
|
).Group("table_id").Scan(&groups); res.Error != nil {
|
|
log.Error("MapLikes: count query: %s", res.Error)
|
|
}
|
|
|
|
// Map the counts back in.
|
|
for _, row := range groups {
|
|
if stats, ok := result[row.ID]; ok {
|
|
stats.Count = row.Likes
|
|
}
|
|
}
|
|
|
|
// Does the CURRENT USER like any of these IDs?
|
|
if likedIDs, err := LikedIDs(user, tableName, tableIDs); err == nil {
|
|
for _, id := range likedIDs {
|
|
if stats, ok := result[id]; ok {
|
|
stats.UserLikes = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|