Migrate to new follows storage

This commit is contained in:
Eduard Urbach 2019-11-19 13:51:54 +09:00
parent 99f5ae1b4f
commit 803d303420
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
14 changed files with 237 additions and 241 deletions

View File

@ -46,11 +46,9 @@ func (item *AnimeListItem) Edit(ctx aero.Context, key string, value reflect.Valu
// Broadcast event to all users so they can reload the activity page if needed.
for receiver := range StreamUsers() {
receiverIsFollowing := Contains(receiver.Follows().Items, user.ID)
receiver.BroadcastEvent(&aero.Event{
Name: "activity",
Data: receiverIsFollowing,
Data: receiver.IsFollowing(user.ID),
})
}
}

View File

@ -20,17 +20,10 @@ import (
gravatar "github.com/ungerik/go-gravatar"
)
var setNickMutex sync.Mutex
var setEmailMutex sync.Mutex
// Register data lists.
func init() {
DataLists["genders"] = []*Option{
// &Option{"", "Prefer not to say"},
{"male", "Male"},
{"female", "Female"},
}
}
var (
setNickMutex sync.Mutex
setEmailMutex sync.Mutex
)
// UserID represents a user ID.
type UserID = ID
@ -62,6 +55,7 @@ type User struct {
Browser UserBrowser `json:"browser" private:"true"`
OS UserOS `json:"os" private:"true"`
Location *Location `json:"location" private:"true"`
FollowIDs []UserID `json:"follows"`
hasPosts
@ -124,9 +118,6 @@ func RegisterUser(user *User) {
Items: []*PushSubscription{},
})
// Add empty follow list
NewUserFollows(user.ID).Save()
// Add empty notifications list
NewUserNotifications(user.ID).Save()

21
arn/User.init.go Normal file
View File

@ -0,0 +1,21 @@
package arn
import "github.com/aerogo/api"
// Register data lists.
func init() {
DataLists["genders"] = []*Option{
// &Option{"", "Prefer not to say"},
{"male", "Male"},
{"female", "Female"},
}
// Actions
API.RegisterActions("User", []*api.Action{
// Add follow
FollowAction(),
// Remove follow
UnfollowAction(),
})
}

View File

@ -1,8 +1,6 @@
package arn
import (
"errors"
"github.com/aerogo/nano"
)
@ -12,130 +10,6 @@ type UserFollows struct {
Items []string `json:"items"`
}
// NewUserFollows creates a new UserFollows list.
func NewUserFollows(userID UserID) *UserFollows {
return &UserFollows{
UserID: userID,
Items: []string{},
}
}
// Add adds an user to the list if it hasn't been added yet.
func (list *UserFollows) Add(userID UserID) error {
if userID == list.UserID {
return errors.New("You can't follow yourself")
}
if list.Contains(userID) {
return errors.New("User " + userID + " has already been added")
}
list.Items = append(list.Items, userID)
// Send notification
user, err := GetUser(userID)
if err == nil {
if !user.Settings().Notification.NewFollowers {
return nil
}
follower, err := GetUser(list.UserID)
if err == nil {
user.SendNotification(&PushNotification{
Title: "You have a new follower!",
Message: follower.Nick + " started following you.",
Icon: "https:" + follower.AvatarLink("large"),
Link: "https://notify.moe" + follower.Link(),
Type: NotificationTypeFollow,
})
}
}
return nil
}
// Remove removes the user ID from the list.
func (list *UserFollows) Remove(userID UserID) bool {
for index, item := range list.Items {
if item == userID {
list.Items = append(list.Items[:index], list.Items[index+1:]...)
return true
}
}
return false
}
// Contains checks if the list contains the user ID already.
func (list *UserFollows) Contains(userID UserID) bool {
for _, item := range list.Items {
if item == userID {
return true
}
}
return false
}
// Users returns a slice of all the users you are following.
func (list *UserFollows) Users() []*User {
followsObj := DB.GetMany("User", list.Items)
follows := make([]*User, len(followsObj))
for i, obj := range followsObj {
follows[i] = obj.(*User)
}
return follows
}
// UsersWhoFollowBack returns a slice of all the users you are following that also follow you.
func (list *UserFollows) UsersWhoFollowBack() []*User {
followsObj := DB.GetMany("User", list.Items)
friends := make([]*User, 0, len(followsObj))
for _, obj := range followsObj {
friend := obj.(*User)
if Contains(friend.Follows().Items, list.UserID) {
friends = append(friends, friend)
}
}
return friends
}
// GetID returns the ID.
func (list *UserFollows) GetID() string {
return list.UserID
}
// UserFollowerCountMap returns a map of user ID keys and their corresping number of followers as the value.
func UserFollowerCountMap() map[string]int {
followCount := map[string]int{}
for list := range StreamUserFollows() {
for _, followUserID := range list.Items {
followCount[followUserID]++
}
}
return followCount
}
// GetUserFollows ...
func GetUserFollows(id UserID) (*UserFollows, error) {
obj, err := DB.Get("UserFollows", id)
if err != nil {
return nil, err
}
return obj.(*UserFollows), nil
}
// StreamUserFollows returns a stream of all user follows.
func StreamUserFollows() <-chan *UserFollows {
channel := make(chan *UserFollows, nano.ChannelBufferSize)
@ -150,14 +24,3 @@ func StreamUserFollows() <-chan *UserFollows {
return channel
}
// AllUserFollows returns a slice of all user follows.
func AllUserFollows() ([]*UserFollows, error) {
all := make([]*UserFollows, 0, DB.Collection("UserFollows").Count())
for obj := range StreamUserFollows() {
all = append(all, obj)
}
return all, nil
}

180
arn/UserFollows2.go Normal file
View File

@ -0,0 +1,180 @@
package arn
import (
"errors"
"github.com/aerogo/aero"
"github.com/aerogo/api"
)
// Add adds an user to the user if it hasn't been added yet.
func (user *User) Follow(followUserID UserID) error {
if followUserID == user.ID {
return errors.New("You can't follow yourself")
}
if user.IsFollowing(followUserID) {
return errors.New("User " + followUserID + " has already been added")
}
user.FollowIDs = append(user.FollowIDs, followUserID)
// Send notification
user, err := GetUser(followUserID)
if err == nil {
if !user.Settings().Notification.NewFollowers {
return nil
}
follower, err := GetUser(user.ID)
if err == nil {
user.SendNotification(&PushNotification{
Title: "You have a new follower!",
Message: follower.Nick + " started following you.",
Icon: "https:" + follower.AvatarLink("large"),
Link: "https://notify.moe" + follower.Link(),
Type: NotificationTypeFollow,
})
}
}
return nil
}
// Unfollow removes the user ID from the follow list.
func (user *User) Unfollow(userID UserID) bool {
for index, item := range user.FollowIDs {
if item == userID {
user.FollowIDs = append(user.FollowIDs[:index], user.FollowIDs[index+1:]...)
return true
}
}
return false
}
// IsFollowing checks if the object follows the user ID.
func (user *User) IsFollowing(userID UserID) bool {
for _, item := range user.FollowIDs {
if item == userID {
return true
}
}
return false
}
// Follows returns a slice of all the users you are following.
func (user *User) Follows() []*User {
followsObj := DB.GetMany("User", user.FollowIDs)
follows := make([]*User, len(followsObj))
for i, user := range followsObj {
follows[i] = user.(*User)
}
return follows
}
// Friends returns a slice of all the users you are following that also follow you.
func (user *User) Friends() []*User {
followsObj := DB.GetMany("User", user.FollowIDs)
friends := make([]*User, 0, len(followsObj))
for _, friendObj := range followsObj {
friend := friendObj.(*User)
if friend.IsFollowing(user.ID) {
friends = append(friends, friend)
}
}
return friends
}
// Followers returns the users who follow the user.
func (user *User) Followers() []*User {
var followerIDs []string
for follower := range StreamUsers() {
if follower.IsFollowing(user.ID) {
followerIDs = append(followerIDs, follower.ID)
}
}
usersObj := DB.GetMany("User", followerIDs)
users := make([]*User, len(usersObj))
for i, obj := range usersObj {
users[i] = obj.(*User)
}
return users
}
// FollowersCount returns how many followers the user has.
func (user *User) FollowersCount() int {
count := 0
for follower := range StreamUsers() {
if follower.IsFollowing(user.ID) {
count++
}
}
return count
}
// UserFollowerCountMap returns a map of user ID keys and their corresping number of followers as the value.
func UserFollowerCountMap() map[string]int {
followCount := map[string]int{}
for user := range StreamUsers() {
for _, followUserID := range user.FollowIDs {
followCount[followUserID]++
}
}
return followCount
}
// FollowAction returns an API action that adds a user ID to the follow list.
func FollowAction() *api.Action {
return &api.Action{
Name: "follow",
Route: "/follow/:follow-id",
Run: func(obj interface{}, ctx aero.Context) error {
user := obj.(*User)
followID := ctx.Get("follow-id")
err := user.Follow(followID)
if err != nil {
return err
}
user.Save()
return nil
},
}
}
// UnfollowAction returns an API action that removes a user ID from the follow list.
func UnfollowAction() *api.Action {
return &api.Action{
Name: "unfollow",
Route: "/unfollow/:unfollow-id",
Run: func(obj interface{}, ctx aero.Context) error {
user := obj.(*User)
unfollowID := ctx.Get("unfollow-id")
if !user.Unfollow(unfollowID) {
return errors.New("This item does not exist in the list")
}
user.Save()
return nil
},
}
}

View File

@ -1,34 +0,0 @@
package arn
import (
"github.com/aerogo/aero"
"github.com/aerogo/api"
)
// Force interface implementations
var (
_ Identifiable = (*UserFollows)(nil)
_ IDCollection = (*UserFollows)(nil)
_ api.Editable = (*UserFollows)(nil)
)
// Actions
func init() {
API.RegisterActions("UserFollows", []*api.Action{
// Add follow
AddAction(),
// Remove follow
RemoveAction(),
})
}
// Authorize returns an error if the given API request is not authorized.
func (list *UserFollows) Authorize(ctx aero.Context, action string) error {
return AuthorizeIfLoggedInAndOwnData(ctx, "id")
}
// Save saves the follow list in the database.
func (list *UserFollows) Save() {
DB.Set("UserFollows", list.UserID, list)
}

View File

@ -42,51 +42,12 @@ func (user *User) Inventory() *Inventory {
return inventory
}
// Follows returns the list of user follows.
func (user *User) Follows() *UserFollows {
follows, _ := GetUserFollows(user.ID)
return follows
}
// Notifications returns the list of user notifications.
func (user *User) Notifications() *UserNotifications {
notifications, _ := GetUserNotifications(user.ID)
return notifications
}
// Followers ...
func (user *User) Followers() []*User {
var followerIDs []string
for list := range StreamUserFollows() {
if list.Contains(user.ID) {
followerIDs = append(followerIDs, list.UserID)
}
}
usersObj := DB.GetMany("User", followerIDs)
users := make([]*User, len(usersObj))
for i, obj := range usersObj {
users[i] = obj.(*User)
}
return users
}
// FollowersCount ...
func (user *User) FollowersCount() int {
count := 0
for list := range StreamUserFollows() {
if list.Contains(user.ID) {
count++
}
}
return count
}
// DraftIndex ...
func (user *User) DraftIndex() *DraftIndex {
draftIndex, _ := GetDraftIndex(user.ID)

View File

@ -21,14 +21,8 @@ func Followed(ctx aero.Context) error {
// fetchActivities filters the activities by the given filters.
func fetchActivities(user *arn.User, followedOnly bool) []arn.Activity {
var followedUserIDs []string
if followedOnly && user != nil {
followedUserIDs = user.Follows().Items
}
activities := arn.FilterActivityCreates(func(activity arn.Activity) bool {
if followedOnly && !arn.Contains(followedUserIDs, activity.GetCreatedBy()) {
if followedOnly && user != nil && !user.IsFollowing(activity.GetCreatedBy()) {
return false
}

View File

@ -47,7 +47,7 @@ func Get(ctx aero.Context) error {
episodeToFriends := map[int][]*arn.User{}
if user != nil {
friends = user.Follows().Users()
friends = user.Follows()
deleted := 0
if animeListItem != nil {

View File

@ -23,7 +23,7 @@ func Episodes(ctx aero.Context) error {
episodeToFriends[ownListItem.Episodes] = append(episodeToFriends[ownListItem.Episodes], user)
}
for _, friend := range user.Follows().Users() {
for _, friend := range user.Follows() {
friendAnimeList := friend.AnimeList()
friendAnimeListItem := friendAnimeList.Find(anime.ID)

View File

@ -126,7 +126,7 @@ func Get(ctx aero.Context) error {
var friends []*arn.User
if user != nil {
friendIDs := utils.Intersection(character.Likes, user.Follows().Items)
friendIDs := utils.Intersection(character.Likes, user.FollowIDs)
friendObjects := arn.DB.GetMany("User", friendIDs)
for _, obj := range friendObjects {

View File

@ -120,7 +120,7 @@ func Profile(ctx aero.Context, viewUser *arn.User) error {
}
// Friends
friends := viewUser.Follows().UsersWhoFollowBack()
friends := viewUser.Friends()
arn.SortUsersFollowers(friends)

View File

@ -141,12 +141,12 @@ component ProfileHead(viewUser *arn.User, animeList *arn.AnimeList, user *arn.Us
.profile-actions
if user != nil && user.ID != viewUser.ID
if !user.Follows().Contains(viewUser.ID)
button.profile-action.action.mountable.never-unmount(data-action="followUser", data-trigger="click", data-api="/api/userfollows/" + user.ID + "/add/" + viewUser.ID)
if !user.IsFollowing(viewUser.ID)
button.profile-action.action.mountable.never-unmount(data-action="followUser", data-trigger="click", data-api="/api/user/" + user.ID + "/follow/" + viewUser.ID)
Icon("user-plus")
span Follow
else
button.profile-action.action.mountable.never-unmount(data-action="unfollowUser", data-trigger="click", data-api="/api/userfollows/" + user.ID + "/remove/" + viewUser.ID)
button.profile-action.action.mountable.never-unmount(data-action="unfollowUser", data-trigger="click", data-api="/api/user/" + user.ID + "/unfollow/" + viewUser.ID)
Icon("user-times")
span Unfollow

View File

@ -0,0 +1,22 @@
package main
import (
"github.com/akyoto/color"
"github.com/animenotifier/notify.moe/arn"
)
func main() {
defer arn.Node.Close()
for follows := range arn.StreamUserFollows() {
user, err := arn.GetUser(follows.UserID)
if err != nil {
color.Red(err.Error())
continue
}
user.FollowIDs = follows.Items
user.Save()
}
}