2019-05-06 01:40:41 +03:00
package models
2021-04-25 15:15:18 +03:00
import (
2021-10-14 13:01:06 +03:00
"crypto/md5"
"fmt"
2022-12-29 13:54:14 +03:00
conf "github.com/muety/wakapi/config"
2023-01-02 17:14:49 +03:00
"github.com/muety/wakapi/utils"
2021-04-25 15:15:18 +03:00
"regexp"
2021-10-14 13:01:06 +03:00
"strings"
2021-04-25 15:15:18 +03:00
"time"
)
2021-02-21 15:02:11 +03:00
func init ( ) {
mailRegex = regexp . MustCompile ( MailPattern )
}
2019-05-06 01:40:41 +03:00
type User struct {
2023-02-19 21:37:03 +03:00
ID string ` json:"id" gorm:"primary_key" `
ApiKey string ` json:"api_key" gorm:"unique; default:NULL" `
Email string ` json:"email" gorm:"index:idx_user_email; size:255" `
Location string ` json:"location" `
Password string ` json:"-" `
CreatedAt CustomTime ` gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000" `
LastLoggedInAt CustomTime ` gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000" `
ShareDataMaxDays int ` json:"-" `
ShareEditors bool ` json:"-" gorm:"default:false; type:bool" `
ShareLanguages bool ` json:"-" gorm:"default:false; type:bool" `
ShareProjects bool ` json:"-" gorm:"default:false; type:bool" `
ShareOSs bool ` json:"-" gorm:"default:false; type:bool; column:share_oss" `
ShareMachines bool ` json:"-" gorm:"default:false; type:bool" `
ShareLabels bool ` json:"-" gorm:"default:false; type:bool" `
IsAdmin bool ` json:"-" gorm:"default:false; type:bool" `
HasData bool ` json:"-" gorm:"default:false; type:bool" `
WakatimeApiKey string ` json:"-" ` // for relay middleware and imports
WakatimeApiUrl string ` json:"-" ` // for relay middleware and imports
ResetToken string ` json:"-" `
ReportsWeekly bool ` json:"-" gorm:"default:false; type:bool" `
PublicLeaderboard bool ` json:"-" gorm:"default:false; type:bool" `
SubscribedUntil * CustomTime ` json:"-" gorm:"type:timestamp" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000" `
SubscriptionRenewal * CustomTime ` json:"-" gorm:"type:timestamp" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000" `
StripeCustomerId string ` json:"-" `
2019-05-06 01:40:41 +03:00
}
2020-05-24 14:41:19 +03:00
type Login struct {
2020-05-24 17:34:32 +03:00
Username string ` schema:"username" `
Password string ` schema:"password" `
}
type Signup struct {
Username string ` schema:"username" `
2021-02-21 15:02:11 +03:00
Email string ` schema:"email" `
2020-05-24 17:34:32 +03:00
Password string ` schema:"password" `
PasswordRepeat string ` schema:"password_repeat" `
2021-04-25 21:02:45 +03:00
Location string ` schema:"location" `
2020-05-24 14:41:19 +03:00
}
2020-05-24 22:42:15 +03:00
2021-04-05 23:57:57 +03:00
type SetPasswordRequest struct {
Password string ` schema:"password" `
PasswordRepeat string ` schema:"password_repeat" `
Token string ` schema:"token" `
}
type ResetPasswordRequest struct {
Email string ` schema:"email" `
}
2020-06-07 20:28:32 +03:00
type CredentialsReset struct {
PasswordOld string ` schema:"password_old" `
PasswordNew string ` schema:"password_new" `
PasswordRepeat string ` schema:"password_repeat" `
}
2021-02-21 15:02:11 +03:00
type UserDataUpdate struct {
2022-10-02 11:13:39 +03:00
Email string ` schema:"email" `
Location string ` schema:"location" `
ReportsWeekly bool ` schema:"reports_weekly" `
PublicLeaderboard bool ` schema:"public_leaderboard" `
2021-02-21 15:02:11 +03:00
}
2020-11-07 14:01:35 +03:00
type TimeByUser struct {
User string
2021-02-07 14:37:51 +03:00
Time CustomTime
2020-11-07 14:01:35 +03:00
}
2021-02-13 13:23:58 +03:00
type CountByUser struct {
User string
Count int64
}
2022-12-29 13:54:14 +03:00
func ( u * User ) Identity ( ) string {
return u . ID
}
2021-04-25 15:15:18 +03:00
func ( u * User ) TZ ( ) * time . Location {
if u . Location == "" {
u . Location = "Local"
}
tz , err := time . LoadLocation ( u . Location )
if err != nil {
return time . Local
}
return tz
}
2022-01-02 22:25:07 +03:00
// TZOffset returns the time difference between the user's current time zone and UTC
// TODO: is this actually working??
2021-04-25 15:15:18 +03:00
func ( u * User ) TZOffset ( ) time . Duration {
_ , offset := time . Now ( ) . In ( u . TZ ( ) ) . Zone ( )
return time . Duration ( offset * int ( time . Second ) )
}
2021-10-14 13:01:06 +03:00
func ( u * User ) AvatarURL ( urlTemplate string ) string {
urlTemplate = strings . ReplaceAll ( urlTemplate , "{username}" , u . ID )
urlTemplate = strings . ReplaceAll ( urlTemplate , "{email}" , u . Email )
if strings . Contains ( urlTemplate , "{username_hash}" ) {
urlTemplate = strings . ReplaceAll ( urlTemplate , "{username_hash}" , fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( u . ID ) ) ) )
}
if strings . Contains ( urlTemplate , "{email_hash}" ) {
urlTemplate = strings . ReplaceAll ( urlTemplate , "{email_hash}" , fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( u . Email ) ) ) )
}
return urlTemplate
}
2022-01-21 14:35:05 +03:00
// WakaTimeURL returns the user's effective WakaTime URL, i.e. a custom one (which could also point to another Wakapi instance) or fallback if not specified otherwise.
func ( u * User ) WakaTimeURL ( fallback string ) string {
if u . WakatimeApiUrl != "" {
return strings . TrimSuffix ( u . WakatimeApiUrl , "/" )
}
return fallback
}
2022-12-29 14:06:54 +03:00
// HasActiveSubscription returns true if subscriptions are enabled on the server and the user has got one
2022-12-23 00:43:41 +03:00
func ( u * User ) HasActiveSubscription ( ) bool {
2022-12-29 14:06:54 +03:00
return conf . Get ( ) . Subscriptions . Enabled && u . HasActiveSubscriptionStrict ( )
}
func ( u * User ) HasActiveSubscriptionStrict ( ) bool {
2022-12-23 00:43:41 +03:00
return u . SubscribedUntil != nil && u . SubscribedUntil . T ( ) . After ( time . Now ( ) )
}
2022-12-29 19:12:34 +03:00
// SubscriptionExpiredSince returns if a user's subscription has expiration and the duration since when that happened.
// Returns (false, <negative duration>), if subscription hasn't expired, yet.
// Returns (false, 0), if subscriptions are not enabled in the first place.
// Returns (true, <very long duration>), if the user has never had a subscription.
func ( u * User ) SubscriptionExpiredSince ( ) ( bool , time . Duration ) {
cfg := conf . Get ( )
if ! cfg . Subscriptions . Enabled {
return false , 0
}
if u . SubscribedUntil == nil {
return true , 99 * 365 * 24 * time . Hour
}
diff := time . Now ( ) . Sub ( u . SubscribedUntil . T ( ) )
return diff >= 0 , diff
}
2022-12-29 13:54:14 +03:00
func ( u * User ) MinDataAge ( ) time . Time {
retentionMonths := conf . Get ( ) . App . DataRetentionMonths
if retentionMonths <= 0 || u . HasActiveSubscription ( ) {
return time . Time { }
}
// this is not exactly precise, because of summer / winter time, etc.
return time . Now ( ) . AddDate ( 0 , - retentionMonths , 0 )
}
2020-06-07 20:28:32 +03:00
func ( c * CredentialsReset ) IsValid ( ) bool {
2021-02-21 15:02:11 +03:00
return ValidatePassword ( c . PasswordNew ) &&
2020-06-07 20:28:32 +03:00
c . PasswordNew == c . PasswordRepeat
}
2021-04-05 23:57:57 +03:00
func ( c * SetPasswordRequest ) IsValid ( ) bool {
return ValidatePassword ( c . Password ) &&
c . Password == c . PasswordRepeat
}
2020-05-24 22:42:15 +03:00
func ( s * Signup ) IsValid ( ) bool {
2021-02-21 15:02:11 +03:00
return ValidateUsername ( s . Username ) &&
ValidateEmail ( s . Email ) &&
ValidatePassword ( s . Password ) &&
2020-05-24 22:42:15 +03:00
s . Password == s . PasswordRepeat
}
2020-06-07 20:28:32 +03:00
2021-02-21 15:02:11 +03:00
func ( r * UserDataUpdate ) IsValid ( ) bool {
2021-04-25 15:15:18 +03:00
return ValidateEmail ( r . Email ) && ValidateTimezone ( r . Location )
2021-02-21 15:02:11 +03:00
}
func ValidateUsername ( username string ) bool {
2023-02-19 21:41:20 +03:00
return len ( username ) >= 1 && username != "current" && ! strings . Contains ( username , " " )
2020-06-07 20:28:32 +03:00
}
2021-02-21 15:02:11 +03:00
func ValidatePassword ( password string ) bool {
2020-06-07 20:28:32 +03:00
return len ( password ) >= 6
}
2021-02-21 15:02:11 +03:00
2023-01-02 17:14:49 +03:00
// ValidateEmail checks that, if an email address is given, it has proper syntax and (if not in dev mode) an MX record exists for the domain
2021-02-21 15:02:11 +03:00
func ValidateEmail ( email string ) bool {
2023-01-02 17:14:49 +03:00
return email == "" || ( mailRegex . Match ( [ ] byte ( email ) ) && ( conf . Get ( ) . IsDev ( ) || utils . CheckEmailMX ( email ) ) )
2021-02-21 15:02:11 +03:00
}
2021-04-25 15:15:18 +03:00
func ValidateTimezone ( tz string ) bool {
_ , err := time . LoadLocation ( tz )
return err == nil
}