Albirew/nyaa-pantsu
Albirew
/
nyaa-pantsu
Archivé
1
0
Bifurcation 0
Ce dépôt a été archivé le 2022-05-07. Vous pouvez voir ses fichiers ou le cloner, mais pas ouvrir de ticket ou de demandes d'ajout, ni soumettre de changements.
nyaa-pantsu/models/user.go

423 lignes
12 KiB
Go
Brut Vue normale Historique

package models
import (
2017-05-21 20:20:40 +02:00
"encoding/json"
2017-05-22 00:22:42 +02:00
"fmt"
2017-05-24 09:11:13 +02:00
"time"
2017-07-02 18:00:12 +02:00
"github.com/NyaaPantsu/nyaa/utils/log"
"github.com/fatih/structs"
2017-07-02 18:00:12 +02:00
"net/http"
"errors"
2017-07-22 01:14:16 +02:00
"math"
"github.com/NyaaPantsu/nyaa/config"
2017-07-02 18:00:12 +02:00
"github.com/NyaaPantsu/nyaa/utils/crypto"
)
const (
// UserStatusBanned : Int for User status banned
UserStatusBanned = -1
// UserStatusMember : Int for User status member
UserStatusMember = 0
// UserStatusTrusted : Int for User status trusted
UserStatusTrusted = 1
// UserStatusModerator : Int for User status moderator
UserStatusModerator = 2
2017-06-07 03:14:57 +02:00
// UserStatusScraped : Int for User status scrapped
UserStatusScraped = 3
)
// User model
type User struct {
ID uint `gorm:"column:user_id;primary_key"`
Username string `gorm:"column:username;unique"`
Password string `gorm:"column:password"`
Email string `gorm:"column:email;unique"`
Status int `gorm:"column:status"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
APIToken string `gorm:"column:api_token"`
APITokenExpiry time.Time `gorm:"column:api_token_expiry"`
Language string `gorm:"column:language"`
Theme string `gorm:"column:theme"`
Mascot string `gorm:"column:mascot"`
MascotURL string `gorm:"column:mascot_url"`
2017-05-21 18:13:28 +02:00
UserSettings string `gorm:"column:settings"`
2017-07-22 01:14:16 +02:00
Pantsu float64 `gorm:"column:pantsu"`
2017-05-09 12:14:12 +02:00
// TODO: move this to PublicUser
Followers []User // Don't work `gorm:"foreignkey:user_id;associationforeignkey:follower_id;many2many:user_follows"`
Likings []User // Don't work `gorm:"foreignkey:follower_id;associationforeignkey:user_id;many2many:user_follows"`
2017-05-09 12:14:12 +02:00
2017-05-24 09:11:13 +02:00
MD5 string `json:"md5" gorm:"column:md5"` // Hash of email address, used for Gravatar
Torrents []Torrent `gorm:"ForeignKey:UploaderID"`
2017-05-21 18:13:28 +02:00
Notifications []Notification `gorm:"ForeignKey:UserID"`
2017-05-20 20:53:05 +02:00
2017-05-24 09:11:13 +02:00
UnreadNotifications int `gorm:"-"` // We don't want to loop every notifications when accessing user unread notif
Settings UserSettings `gorm:"-"` // We don't want to load settings everytime, stock it as a string, parse it when needed
2017-07-30 03:29:09 +02:00
Tags Tags `gorm:"-"` // We load tags only when viewing a torrent
2017-05-10 10:27:17 +02:00
}
// UserJSON : User model conversion in JSON
2017-05-10 21:09:37 +02:00
type UserJSON struct {
ID uint `json:"user_id"`
Username string `json:"username"`
Status int `json:"status"`
APIToken string `json:"token,omitempty"`
MD5 string `json:"md5"`
2017-05-10 21:09:37 +02:00
CreatedAt string `json:"created_at"`
LikingCount int `json:"liking_count"`
LikedCount int `json:"liked_count"`
}
// UserFollows association table : different users following eachother
type UserFollows struct {
UserID uint `gorm:"column:user_id"`
FollowerID uint `gorm:"column:following"`
}
// UserUploadsOld model : Is it deprecated?
type UserUploadsOld struct {
Username string `gorm:"column:username"`
TorrentID uint `gorm:"column:torrent_id"`
}
// UserSettings : Struct for user settings, not a model
type UserSettings struct {
Settings map[string]bool `json:"settings"`
}
/*
* User Model
*/
// Size : Returns the total size of memory recursively allocated for this struct
2017-05-10 10:27:17 +02:00
func (u User) Size() (s int) {
s += 4 + // ints
6*2 + // string pointers
4*3 + //time.Time
3*2 + // arrays
2017-07-30 03:29:09 +02:00
// string arrays
len(u.Username) + len(u.Password) + len(u.Email) + len(u.APIToken) + len(u.MD5) + len(u.Language) + len(u.Theme)
2017-05-10 10:27:17 +02:00
s *= 8
// Ignoring foreign key users. Fuck them.
return
}
// IsBanned : Return true if user is banned
func (u *User) IsBanned() bool {
return u.Status == UserStatusBanned
}
// IsMember : Return true if user is member
func (u *User) IsMember() bool {
return u.Status == UserStatusMember
}
// IsTrusted : Return true if user is tusted
func (u *User) IsTrusted() bool {
return u.Status == UserStatusTrusted
}
// IsModerator : Return true if user is moderator
func (u *User) IsModerator() bool {
return u.Status == UserStatusModerator
}
2017-06-07 03:14:57 +02:00
// IsScraped : Return true if user is a scrapped user
func (u *User) IsScraped() bool {
return u.Status == UserStatusScraped
}
// GetUnreadNotifications : Get unread notifications from a user
func (u *User) GetUnreadNotifications() int {
2017-05-24 09:11:13 +02:00
if u.UnreadNotifications == 0 {
2017-05-20 20:53:05 +02:00
for _, notif := range u.Notifications {
if !notif.Read {
u.UnreadNotifications++
}
}
}
return u.UnreadNotifications
}
// HasAdmin checks that user has an admin permission. Deprecated
func (u *User) HasAdmin() bool {
return u.IsModerator()
}
// CurrentOrAdmin check that user has admin permission or user is the current user.
func (u *User) CurrentOrAdmin(userID uint) bool {
Tag Search + Tests + Search slight refactor [DONE] (#1342) * Tag Search + Tests + Search slight refactor First commit improving search. Different struct have their own file with their tests. This way of separating struct by files is inspired by the go packages I've seen so far. Added new behaviour as discussed in #1334 * fix fallback to ES * Added some comments to explain PG fallback + log err moved * Refactored search Nearly fully covered WhereParams struct has disappeared for Query struct instead In DB model, we use an interface implementing Query struct methods * 1rst Refactor of Tags (WTF already?!) Prepare Tags for the refactored system. Now there will be descriptive tags for a particular release (ecchi, BDSM, ....) and typed tags. Typed tags are tags relevant to all torrents and can be limited to some input value. For example, video quality is a typed tag limited to some values (hd, full hd, sd, ...). In the same way, anidbid is also a typed tag but doesn't have default values. Furthermore, the location storage of tags have changed, now accepted descriptive tags are stored in the torrents table in the column "tags" and they are separated by commas. In the opposite, accepted typed tags can have have their own column in the torrents table. For example, anidbid, vndbid will populate the column DbID when accepted. On the other hand, videoquality will populate the same way as descriptive tags. This behaviour depends on the callbackOnType function in tag/helpers.go * fix for modtools :') * Added anidb, vndb, dlsite & vmdb id fields in torrent model. Tags don't have an accepted field anymore. Accepted Tags are in torrent.AcceptedTags and non-accepted ones in torrrent.Tags. New Helper + New Changelog for translation string. * New upload/edit form for torrent tags. Now the inputs are dynamically generated by the helper tag_form. No more modal window in those form, only inputs. Support of tags in API New translation string for the link to the modal on torrent view. More comments in the functions for tags * Improving how config for tags work. Adding a test on them with understandable messages. Config for tags have now a Field attribute which is linked to the Torrent model. For example anidbid tag type has now a AnidbID field in config which is the name of the field in torrent model (AnidbID). Every new tag type need to have a field attribute with its counterpart in torrent Model. Fixing some errors * Fix compile error + Tests Errors * Improve performance by caching the list of tags with an index Adding/removing tags works/tested New translation strings TODO: test/fix adding tag on upload/edit * Mini fix to display video quality + tags works/tested on modo edit * Fix editing tags on modpanel * Edit tags works * Add translation string * Add search backend for tags. ?tags=xxx,eee,ddd ?anidb=21 ?vndb=23 ?vgmdb=24 ?vq=full_hd * Fix Ajax tag Removal&Add * Added form for descriptive tags * Forgot to add the link between database and form for descriptive tags. * Adding the increase/decrease pantsu for descriptive tags * Fix #1370 * When you actually forgot to commit files after having forgotten commits
2017-08-22 03:48:10 +02:00
if userID == 0 && !u.IsModerator() {
2017-07-29 18:42:02 +02:00
return false
}
log.Debugf("user.ID == userID %d %d %s", u.ID, userID, u.ID == userID)
return (u.IsModerator() || u.ID == userID)
2017-05-09 07:21:14 +02:00
}
// CurrentUserIdentical check that userID is same as current user's ID.
// TODO: Inline this (won't go do this for us?)
func (u *User) CurrentUserIdentical(userID uint) bool {
return u.ID == userID
}
// NeedsCaptcha : Check if a user needs captcha
func (u *User) NeedsCaptcha() bool {
// Trusted members & Moderators don't
return !(u.IsTrusted() || u.IsModerator())
2017-05-21 18:13:28 +02:00
}
2017-07-02 18:00:12 +02:00
// CanUpload : Check if a user can upload or if upload is enabled in config
func (u *User) CanUpload() bool {
if config.Get().Torrents.UploadsDisabled {
if config.Get().Torrents.AdminsAreStillAllowedTo && u.IsModerator() {
2017-07-02 18:00:12 +02:00
return true
}
if config.Get().Torrents.TrustedUsersAreStillAllowedTo && u.IsTrusted() {
2017-07-02 18:00:12 +02:00
return true
}
return false
}
return true
}
// GetRole : Get the status/role of a user
func (u *User) GetRole() string {
switch u.Status {
case UserStatusBanned:
return "Banned"
case UserStatusMember:
return "Member"
case UserStatusScraped:
return "Member"
case UserStatusTrusted:
return "Trusted Member"
case UserStatusModerator:
return "Moderator"
}
return "Member"
}
// IsFollower : Check if a user is following another
func (follower *User) IsFollower(u *User) bool {
var likingUserCount int
ORM.Model(&UserFollows{}).Where("user_id = ? and following = ?", follower.ID, u.ID).Count(&likingUserCount)
return likingUserCount != 0
}
2017-05-10 21:09:37 +02:00
// ToJSON : Conversion of a user model to json
2017-05-10 21:09:37 +02:00
func (u *User) ToJSON() UserJSON {
json := UserJSON{
ID: u.ID,
Username: u.Username,
APIToken: u.APIToken,
MD5: u.MD5,
2017-05-10 21:09:37 +02:00
Status: u.Status,
CreatedAt: u.CreatedAt.Format(time.RFC3339),
LikingCount: len(u.Followers),
LikedCount: len(u.Likings),
2017-05-10 21:09:37 +02:00
}
return json
}
2017-05-21 18:13:28 +02:00
// GetLikings : Gets who is followed by the user
func (u *User) GetLikings() {
var liked []User
ORM.Joins("JOIN user_follows on user_follows.following=?", u.ID).Where("users.user_id = user_follows.user_id").Group("users.user_id").Find(&liked)
u.Likings = liked
}
// GetFollowers : Gets who is following the user
func (u *User) GetFollowers() {
var likings []User
ORM.Joins("JOIN user_follows on user_follows.user_id=?", u.ID).Where("users.user_id = user_follows.following").Group("users.user_id").Find(&likings)
u.Followers = likings
}
// SetFollow : Makes a user follow another
func (u *User) SetFollow(follower *User) {
if follower.ID > 0 && u.ID > 0 {
var userFollows = UserFollows{UserID: u.ID, FollowerID: follower.ID}
ORM.Create(&userFollows)
}
}
// RemoveFollow : Remove a user following another
func (u *User) RemoveFollow(follower *User) {
if follower.ID > 0 && u.ID > 0 {
var userFollows = UserFollows{UserID: u.ID, FollowerID: follower.ID}
ORM.Delete(&userFollows)
}
}
/*
* Old User
*/
// TableName : Return the name of OldComment table
func (c UserUploadsOld) TableName() string {
// is this needed here?
return config.Get().Models.UploadsOldTableName
}
/*
* User Settings
*/
2017-05-21 18:13:28 +02:00
// Get a user setting by keyname
2017-05-24 09:11:13 +02:00
func (s *UserSettings) Get(key string) bool {
if val, ok := s.Settings[key]; ok {
return val
2017-05-21 19:38:39 +02:00
}
return config.Get().Users.DefaultUserSettings[key]
2017-05-21 19:38:39 +02:00
}
// GetSettings : get all user settings
2017-05-22 00:22:42 +02:00
func (s *UserSettings) GetSettings() map[string]bool {
return s.Settings
2017-05-21 18:13:28 +02:00
}
// Set a user setting by keyname
2017-05-22 00:22:42 +02:00
func (s *UserSettings) Set(key string, val bool) {
if s.Settings == nil {
s.Settings = make(map[string]bool)
}
s.Settings[key] = val
}
// ToDefault : Set user settings to default
2017-05-22 00:22:42 +02:00
func (s *UserSettings) ToDefault() {
s.Settings = config.Get().Users.DefaultUserSettings
2017-05-21 18:13:28 +02:00
}
func (s *UserSettings) initialize() {
2017-05-22 00:22:42 +02:00
s.Settings = make(map[string]bool)
2017-05-21 18:13:28 +02:00
}
// SaveSettings : Format settings into a json string for preparing before user insertion
2017-05-22 00:22:42 +02:00
func (u *User) SaveSettings() {
byteArray, err := json.Marshal(u.Settings)
2017-05-24 09:11:13 +02:00
if err != nil {
2017-05-22 00:22:42 +02:00
fmt.Print(err)
}
u.UserSettings = string(byteArray)
2017-05-21 18:13:28 +02:00
}
// ParseSettings : Function to parse json string into usersettings struct, only parse if necessary
2017-05-22 00:22:42 +02:00
func (u *User) ParseSettings() {
2017-05-21 19:38:39 +02:00
if len(u.Settings.GetSettings()) == 0 && u.UserSettings != "" {
u.Settings.initialize()
2017-05-22 00:22:42 +02:00
json.Unmarshal([]byte(u.UserSettings), &u.Settings)
} else if len(u.Settings.GetSettings()) == 0 && u.UserSettings != "" {
u.Settings.initialize()
2017-05-22 00:22:42 +02:00
u.Settings.ToDefault()
2017-05-21 19:38:39 +02:00
}
2017-05-24 09:11:13 +02:00
}
2017-07-02 18:00:12 +02:00
// Update updates a user. (Applying the modifed data of user).
func (u *User) Update() (int, error) {
if u.Email == "" {
u.MD5 = ""
2017-07-02 18:00:12 +02:00
} else {
var err error
u.MD5, err = crypto.GenerateMD5Hash(u.Email)
2017-07-02 18:00:12 +02:00
if err != nil {
return http.StatusInternalServerError, err
}
}
u.UpdatedAt = time.Now()
err := ORM.Save(u).Error
2017-07-02 18:00:12 +02:00
if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
// UpdateRaw : Function to update a user without updating his associations model
func (u *User) UpdateRaw() (int, error) {
u.UpdatedAt = time.Now()
err := ORM.Model(u).UpdateColumn(u.toMap()).Error
2017-07-02 18:00:12 +02:00
if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
// Delete deletes a user.
func (u *User) Delete(currentUser *User) (int, error) {
if u.ID == 0 {
return http.StatusInternalServerError, errors.New("permission_delete_error")
}
err := ORM.Delete(u).Error
if err != nil {
return http.StatusInternalServerError, errors.New("user_not_deleted")
}
return http.StatusOK, nil
}
// toMap : convert the model to a map of interface
func (u *User) toMap() map[string]interface{} {
return structs.Map(u)
}
2017-07-20 13:33:50 +02:00
// Splice : get a subset of torrents
func (u *User) Splice(start int, length int) *User {
if (len(u.Torrents) <= length && start == 0) || len(u.Torrents) == 0 {
return u
}
if start > len(u.Torrents) {
u.Torrents = []Torrent{}
return u
}
if len(u.Torrents) < length {
length = len(u.Torrents)
}
u.Torrents = u.Torrents[start:length]
return u
}
// Filter : filter the hidden torrents
func (u *User) Filter() *User {
torrents := []Torrent{}
for _, t := range u.Torrents {
if !t.Hidden {
torrents = append(torrents, t)
}
}
u.Torrents = torrents
return u
}
2017-07-22 01:14:16 +02:00
// IncreasePantsu is a function that uses the formula to increase the Pantsu points of a user
func (u *User) IncreasePantsu() {
if u.Pantsu <= 0 {
u.Pantsu = 1 // Pantsu points should never be less or equal to 0. This would trigger a division by 0
}
u.Pantsu = u.Pantsu * (1 + 1/(math.Pow(math.Log(u.Pantsu+1), 5))) // First votes substancially increases the vote, further it increase slowly
}
// DecreasePantsu is a function that uses the formula to decrease the Pantsu points of a user
func (u *User) DecreasePantsu() {
u.Pantsu = 0.8 * u.Pantsu // You lose 20% of your pantsu points each wrong vote
}
2017-07-28 20:43:22 +02:00
func (u *User) LoadTags(torrent *Torrent) {
if u.ID == 0 {
return
}
if err := ORM.Where("torrent_id = ? AND user_id = ?", torrent.ID, u.ID).Find(&u.Tags).Error; err != nil {
log.CheckErrorWithMessage(err, "LOAD_TAGS_ERROR: Couldn't load tags!")
return
}
}