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

428 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"`
"Alternating Colors" option in settings (#1440) * Non-bold torrent name when using alternating background colors * Add alternating colors for g.css * Add alternating colors for classic.css * Add alternating colors for tomorreuw * Update classic.css * Add alternating colors to settings page * Add alt-colors class to tbody if enabled * Add GetAltColorsFromRequest() function to get AltColors cookie value * Create AltColors variable for usage in listing * Make settings handle altColors input * Add AltColors in user struct * Returned the wrong variable here * Turn function return value into bool * Condition checks variable like a bool * ditto * better color for g.css * even better color * better * Last change for g.css * Improvement for tomorrow * Update classic.css * add alt-colors class to upload preview * Add alt-colors to user profile * more vivid color for g * Fix typo * Remove browser outline for refine when clicked on * remove browser outline * Fix rules that didn't apply and category icon being way too small on user profile thanks to usage of percentage * Remove show-xs class for search inputs * Turn refine button back into position: absolute to pin at very bottom of refine * Alternating colors for g's trusted, remake & aplus * Update tomorrow.css * attempt at fixing travis * test (will have to rollback that change) * rollback * will have to rollback * will have to rollback * will have to rollback * will have to rollback * add AltColors variable to template test * rollback * rollback * rollback * rollback
2017-08-30 07:21:45 +02:00
AltColors string `gorm:"column:alt_colors"`
"No comments" message, avatar in comments, classic theme improvements, some other changes, Old Navigation (#1545) * Update view.jet.html * Update en-us.all.json * Update view.jet.html * Update CHANGELOG.md * fix travis * Update classic.css * Update view.jet.html * Update main.css * Update classic.css * Update classic.css * Update classic.css * add link to user page & avatar in comment * stylisation for avatar in main.css * Update classic.css * Fix wrongly placed ::before * Add rule list to terms of service * import rules on register * fix refine-container-2's inputs * Update base.jet.html * Update en-us.all.json * Update CHANGELOG.md * GenNav changes to constantly put HTML for nav arrows regardless of page * css changes for website nav etc etc * add OldNav global variable * Add OldNav to test.go * Update publicSettings.go * change OldNav's value type * Old navigation in settings * add OldNav in user variables * add oldNav input handler in settings.go * Change OldNav's default value into false * Create OldNav.jet.html * Update search.jet.html * remove character that had nothing to do here * fix wrong variable name * fix worng variable name and travis * Update classic.css * Add sort order & type to old nav * add toString() function in test * add toString() function * translation string for oldnav setting * Use translation string in settings.jet.html * fix few html errors * ditto * travis fix test * remove useless charset * remove useless things * add spaces before attributes * attempt at fixing travis 2 * fix wrong variable name * Update classic.css * fix travis plsss
2017-09-11 19:45:39 +02:00
OldNav string `gorm:"column:old_nav"`
Mascot string `gorm:"column:mascot"`
MascotURL string `gorm:"column:mascot_url"`
[WIP] Anidex, TT & Nyaa.si uploading (#1604) * Update main.css * Update tomorrow.css * Create upload_multiple.jet.html * Create upload.js * Update upload.jet.html * Update upload.jet.html * Update upload.jet.html * Update tomorrow.css * Update main.css * Update tomorrow.css * Update main.css * Update user.go * Update template_test.go * Update main.css * Update upload.jet.html * Update edit.jet.html * Update template_test.go * Update upload.jet.html * Update main.css * new design for user edit * Fix CRSF token * Update upload_multiple.jet.html * Update upload.jet.html * Update profile.jet.html * Update base.jet.html * Update edit.jet.html * Update forms.go * Update forms_test.go * Update forms_test.go * Update forms.go * Update forms_test.go * Update main.css * Fix 4+ torrent flags display on IE * Fix userprofile buttons on IE, remove fancy css * Fix torrent link being hidden on IE * Update listing.jet.html * Update main.css * Update listing.jet.html * Delete torrent_item_upload.jet.html * Update torrent_item.jet.html * Update main.css * Update classic.css * Update listing.jet.html * Update classic.css * Update profile.jet.html * Update upload.jet.html * Update classic.css * Update classic.css * Update main.css * Update user.go * Update upload.jet.html * Update upload.jet.html * Update edit.jet.html * Update template_test.go * Update forms.go * Update forms_test.go * Update main.css * Update classic.css * checkboxes next to language div * wooo responsive checkboxes * remove empty space * need an !important here * Fix flags being unaligned on IE * Fix some inputs being too big on classic.css, remove fancy shit * Update classic.css * fix travis * Update main.css * Update upload.jet.html * Update edit.jet.html
2017-09-30 15:10:47 +02:00
AnidexAPIToken string `gorm:"column:anidex_api_token"`
NyaasiAPIToken string `gorm:"column:nyaasi_api_token"`
TokyoTAPIToken string `gorm:"column:tokyotosho_api_token"`
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().Upload.UploadsDisabled {
if config.Get().Upload.AdminsAreStillAllowedTo && u.IsModerator() {
2017-07-02 18:00:12 +02:00
return true
}
if config.Get().Upload.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 "userstatus_banned"
case UserStatusMember:
return "userstatus_member"
case UserStatusScraped:
return "userstatus_scraped"
case UserStatusTrusted:
return "userstatus_trusted"
case UserStatusModerator:
return "userstatus_moderator"
}
return "userstatus_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
}
}