2017-08-02 21:24:01 +00:00
import path from 'path'
import cprog from 'child_process'
import crypto from 'crypto'
2017-08-02 22:35:10 +00:00
import notp from 'notp'
import base32 from 'thirty-two'
2020-05-28 18:30:21 +00:00
import { v1 as uuidV1 } from 'uuid'
2017-11-30 21:13:14 +00:00
import fs from 'fs-extra'
2017-08-02 21:24:01 +00:00
2017-12-01 11:35:47 +00:00
import config from '../../scripts/load-config'
2020-12-13 14:36:07 +00:00
import { httpPOST } from '../../scripts/http'
import * as models from './models'
import { pushMail } from './emailer'
2017-12-01 11:35:47 +00:00
2017-08-02 21:24:01 +00:00
const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
2017-09-10 09:42:12 +00:00
// Fork a bcrypt process to hash and compare passwords
2017-08-02 21:24:01 +00:00
function bcryptTask ( data ) {
return new Promise ( ( resolve , reject ) => {
2020-05-28 18:30:21 +00:00
const proc = cprog . fork ( path . join ( _ _dirname , '../../scripts' , 'bcrypt.js' ) )
2017-08-02 21:24:01 +00:00
let done = false
proc . send ( data . task + ' ' + JSON . stringify ( data ) )
proc . on ( 'message' , ( chunk ) => {
if ( chunk == null ) return reject ( new Error ( 'No response' ) )
2020-05-28 18:30:21 +00:00
const line = chunk . toString ( ) . trim ( )
2017-08-02 21:24:01 +00:00
done = true
if ( line === 'error' ) {
return reject ( new Error ( line ) )
}
if ( line === 'true' || line === 'false' ) {
return resolve ( line === 'true' )
}
resolve ( line )
} )
proc . on ( 'exit' , ( ) => {
if ( ! done ) {
reject ( new Error ( 'No response' ) )
}
} )
} )
}
2017-09-10 09:42:12 +00:00
// Make sure an object contains the keys specified in `required`
2017-08-02 22:35:10 +00:00
function keysAvailable ( object , required ) {
let found = true
2020-05-28 18:30:21 +00:00
for ( const i in required ) {
const key = required [ i ]
2017-08-02 22:35:10 +00:00
if ( object [ key ] == null ) {
found = false
}
}
return found
}
2017-09-10 09:42:12 +00:00
// Clean up the donation responses for ease of use
2017-08-31 17:24:38 +00:00
async function cleanUpDonation ( obj , mcOnly , timeframe ) {
if ( timeframe && new Date ( obj . created _at ) . getTime ( ) < timeframe ) {
return null
}
let user
if ( obj . user _id ) {
2020-12-13 14:36:07 +00:00
user = await User . get ( obj . user _id )
2017-08-31 17:24:38 +00:00
}
2020-05-28 18:30:21 +00:00
const result = {
2017-08-31 17:24:38 +00:00
trackId : obj . id ,
amount : obj . amount ,
donated : obj . created _at
}
if ( user ) {
result . name = user . display _name
}
2020-05-28 18:30:21 +00:00
const sources = obj . source . split ( ',' )
for ( const i in sources ) {
2017-08-31 17:24:38 +00:00
if ( sources [ i ] . indexOf ( 'mcu:' ) === 0 ) {
2020-05-28 18:30:21 +00:00
const mcu = sources [ i ] . split ( ':' ) [ 1 ]
2017-08-31 17:24:38 +00:00
if ( mcu . match ( /^([\w_]{2,16})$/i ) ) {
result . minecraft _username = mcu
}
}
}
if ( ! result . minecraft _username && mcOnly ) return null
return result
}
2020-05-28 18:30:21 +00:00
const txnStore = [ ]
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
export function Hash ( len ) {
return crypto . randomBytes ( len ) . toString ( 'hex' )
}
2021-02-09 16:46:04 +00:00
/* ppp - Posts Per Page; dcount - Post Count; page - number of current page */
2020-12-13 14:36:07 +00:00
export function Pagination ( ppp , dcount , page ) {
if ( ! ppp ) ppp = 5
if ( ! dcount ) return null
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
const pageCount = Math . ceil ( dcount / ppp )
if ( page > pageCount ) page = pageCount
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
const offset = ( page - 1 ) * ppp
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
return {
page : page ,
perPage : ppp ,
pages : pageCount ,
offset : offset ,
total : dcount
}
}
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
export class Login {
static async password ( user , password ) {
user = await User . ensureObject ( user , [ 'password' ] )
if ( ! user . password ) return false
return bcryptTask ( { task : 'compare' , password : password , hash : user . password } )
}
2017-08-24 18:36:40 +00:00
2020-12-13 14:36:07 +00:00
static async activationToken ( token ) {
let getToken = await models . Token . query ( ) . where ( 'token' , token ) . andWhere ( 'type' , 1 )
if ( ! getToken || ! getToken . length ) return false
2017-08-24 18:36:40 +00:00
2020-12-13 14:36:07 +00:00
getToken = getToken [ 0 ]
2017-08-24 18:36:40 +00:00
2020-12-13 14:36:07 +00:00
if ( getToken . expires _at && new Date ( getToken . expires _at ) . getTime ( ) < Date . now ( ) ) return false
const user = await User . get ( getToken . user _id )
if ( ! user ) return false
await models . User . query ( ) . patchAndFetchById ( user . id , { activated : 1 } )
await models . Token . query ( ) . delete ( ) . where ( 'id' , getToken . id )
return true
}
static async totpTokenRequired ( user ) {
const getToken = await models . TotpToken . query ( ) . where ( 'user_id' , user . id )
if ( ! getToken || ! getToken . length ) return false
if ( getToken [ 0 ] . activated !== 1 ) return false
return true
}
static async totpCheck ( user , code , emerg ) {
user = await User . ensureObject ( user )
let getToken = await models . TotpToken . query ( ) . where ( 'user_id' , user . id )
if ( ! getToken || ! getToken . length ) return false
getToken = getToken [ 0 ]
2017-08-25 16:42:30 +00:00
2020-12-13 14:36:07 +00:00
if ( emerg ) {
if ( emerg === getToken . recovery _code ) {
return true
2017-08-25 16:42:30 +00:00
}
2020-12-13 14:36:07 +00:00
return false
}
2017-08-25 16:42:30 +00:00
2020-12-13 14:36:07 +00:00
const login = notp . totp . verify ( code , getToken . token , { } )
if ( login ) {
if ( login . delta !== 0 ) {
return false
2017-08-25 16:42:30 +00:00
}
2020-12-13 14:36:07 +00:00
if ( getToken . activated !== 1 ) {
// TODO: Send an email including the recovery code to the user
await models . TotpToken . query ( ) . patchAndFetchById ( getToken . id , { activated : true } )
2017-08-27 12:41:44 +00:00
}
2020-12-13 14:36:07 +00:00
return true
}
2017-08-27 12:41:44 +00:00
2020-12-13 14:36:07 +00:00
return false
}
2017-08-27 12:41:44 +00:00
2020-12-13 14:36:07 +00:00
static async purgeTotp ( user , password ) {
user = await User . ensureObject ( user , [ 'password' ] )
2020-12-13 14:50:19 +00:00
const pwmatch = await Login . password ( user , password )
2020-12-13 14:36:07 +00:00
if ( ! pwmatch ) return false
2017-08-27 12:41:44 +00:00
2020-12-13 14:36:07 +00:00
// TODO: Inform user via email
await models . TotpToken . query ( ) . delete ( ) . where ( 'user_id' , user . id )
2017-08-27 12:41:44 +00:00
2020-12-13 14:36:07 +00:00
return true
}
2017-08-27 12:41:44 +00:00
2020-12-13 14:36:07 +00:00
static async totpAquire ( user ) {
user = await User . ensureObject ( user , [ 'password' ] )
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
// Do not allow totp for users who have registered using an external service
if ( ! user . password || user . password === '' ) return null
2017-11-30 21:26:41 +00:00
2020-12-13 14:36:07 +00:00
// Get existing tokens for the user and delete them if found
const getToken = await models . TotpToken . query ( ) . where ( 'user_id' , user . id )
if ( getToken && getToken . length ) {
await models . TotpToken . query ( ) . delete ( ) . where ( 'user_id' , user . id )
}
2017-11-30 21:26:41 +00:00
2020-12-13 14:36:07 +00:00
const newToken = {
user _id : user . id ,
token : Hash ( 16 ) ,
recovery _code : Hash ( 8 ) ,
activated : 0 ,
created _at : new Date ( )
}
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
const hashed = base32 . encode ( newToken . token )
const domain = 'icynet.eu'
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
await models . TotpToken . query ( ) . insert ( newToken )
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
const uri = encodeURIComponent ( 'otpauth://totp/' + user . username + '@' + domain + '?secret=' + hashed )
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
return uri
}
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
export class OAuth2 {
static async getUserAuthorizations ( user ) {
user = await User . ensureObject ( user )
const auths = await models . OAuth2AuthorizedClient . query ( ) . where ( 'user_id' , user . id )
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
const nicelist = [ ]
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
for ( const i in auths ) {
const auth = auths [ i ]
let client = await models . OAuth2Client . query ( ) . where ( 'id' , auth . client _id )
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
if ( ! client . length ) continue
client = client [ 0 ]
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
const obj = {
id : client . id ,
title : client . title ,
description : client . description ,
url : client . url ,
icon : client . icon ,
scope : client . scope . split ( ' ' ) ,
created _at : auth . created _at ,
expires _at : auth . expires _at
}
nicelist . push ( obj )
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
return nicelist
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
static async removeUserAuthorization ( user , clientId ) {
user = await User . ensureObject ( user )
const auth = await models . OAuth2AuthorizedClient . query ( ) . where ( 'user_id' , user . id ) . andWhere ( 'client_id' , clientId )
if ( ! auth . length ) return false
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
await models . OAuth2AccessToken . query ( ) . delete ( ) . where ( 'client_id' , clientId ) . andWhere ( 'user_id' , user . id )
await models . OAuth2RefreshToken . query ( ) . delete ( ) . where ( 'client_id' , clientId ) . andWhere ( 'user_id' , user . id )
2017-08-03 12:57:17 +00:00
2020-12-13 14:36:07 +00:00
for ( const i in auth ) {
await models . OAuth2AuthorizedClient . query ( ) . delete ( ) . where ( 'id' , auth [ i ] . id )
}
2017-08-03 12:57:17 +00:00
2020-12-13 14:36:07 +00:00
return true
}
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
export class Register {
static async hashPassword ( password ) {
return bcryptTask ( { task : 'hash' , password : password } )
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
static validateEmail ( email ) {
return emailRe . test ( email )
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
static async newAccount ( regdata ) {
const email = config . email && config . email . enabled
const data = Object . assign ( regdata , {
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
uuid : uuidV1 ( ) ,
activated : email ? 0 : 1
} )
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
const userTest = await User . get ( regdata . username )
if ( userTest ) {
throw new Error ( 'This username is already taken!' )
}
2017-08-02 22:35:10 +00:00
2020-12-13 14:36:07 +00:00
const emailTest = await User . get ( regdata . email )
if ( emailTest ) {
throw new Error ( 'This email address is already registered!' )
}
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
// Create user
const user = await models . User . query ( ) . insert ( data )
2017-08-02 21:24:01 +00:00
2020-12-13 14:36:07 +00:00
if ( email ) {
2020-12-13 14:50:19 +00:00
await Register . activationEmail ( user , true )
2020-12-13 14:36:07 +00:00
}
2017-12-07 15:37:36 +00:00
2020-12-13 14:36:07 +00:00
return user
}
2017-12-07 15:37:36 +00:00
2020-12-13 14:36:07 +00:00
static async activationEmail ( user , deleteOnFail = false ) {
// Activation token
const activationToken = Hash ( 16 )
2017-12-07 15:37:36 +00:00
2020-12-13 14:36:07 +00:00
await models . Token . query ( ) . insert ( {
expires _at : new Date ( Date . now ( ) + 86400000 ) , // 1 day
token : activationToken ,
user _id : user . id ,
type : 1
} )
2017-12-07 15:37:36 +00:00
2020-12-13 14:36:07 +00:00
console . debug ( 'Activation token:' , activationToken )
2017-08-24 13:42:57 +00:00
2020-12-13 14:36:07 +00:00
// Send Activation Email
try {
const em = await pushMail ( 'activate' , user . email , {
domain : config . server . domain ,
display _name : user . display _name ,
activation _token : activationToken
} )
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
console . debug ( em )
} catch ( e ) {
console . error ( e )
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
if ( deleteOnFail ) {
await models . User . query ( ) . delete ( ) . where ( 'id' , user . id )
}
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
throw new Error ( 'Invalid email address!' )
}
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
return true
}
}
2017-11-30 21:26:41 +00:00
2020-12-13 14:36:07 +00:00
export class Reset {
static async reset ( email , passRequired = true ) {
const emailEnabled = config . email && config . email . enabled
2017-11-30 21:26:41 +00:00
2020-12-13 14:36:07 +00:00
if ( ! emailEnabled ) throw new Error ( 'Cannot reset password.' )
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
const user = await User . get ( email )
if ( ! user ) throw new Error ( 'This email address does not match any user in our database.' )
if ( ! user . password && passRequired ) throw new Error ( 'The user associated with this email address has used an external website to log in, thus the password cannot be reset.' )
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
const recentTokens = await models . Token . query ( ) . where ( 'user_id' , user . id ) . andWhere ( 'expires_at' , '>' , new Date ( ) ) . andWhere ( 'type' , 2 )
if ( recentTokens . length >= 2 ) {
throw new Error ( 'You\'ve made too many reset requests recently. Please slow down.' )
}
2017-11-30 21:13:14 +00:00
2020-12-13 14:36:07 +00:00
const resetToken = Hash ( 16 )
await models . Token . query ( ) . insert ( {
expires _at : new Date ( Date . now ( ) + 86400000 ) , // 1 day
token : resetToken ,
user _id : user . id ,
type : 2
} )
2017-08-26 09:47:37 +00:00
2020-12-13 14:36:07 +00:00
// Send Reset Email
console . debug ( 'Reset token:' , resetToken )
if ( email ) {
try {
const em = await pushMail ( 'reset_password' , user . email , {
domain : config . server . domain ,
display _name : user . display _name ,
reset _token : resetToken
} )
console . debug ( em )
} catch ( e ) {
console . error ( e )
throw new Error ( 'Invalid email address!' )
2017-08-26 09:47:37 +00:00
}
2017-08-02 21:24:01 +00:00
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
return resetToken
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
static async resetToken ( token ) {
let getToken = await models . Token . query ( ) . where ( 'token' , token ) . andWhere ( 'type' , 2 )
if ( ! getToken || ! getToken . length ) return null
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
getToken = getToken [ 0 ]
if ( getToken . expires _at && new Date ( getToken . expires _at ) . getTime ( ) < Date . now ( ) ) return null
const user = await User . get ( getToken . user _id )
if ( ! user ) return null
return user
}
static async changePassword ( user , password , token ) {
2020-12-13 14:50:19 +00:00
const hashed = await Register . hashPassword ( password )
2020-12-13 14:36:07 +00:00
await models . User . query ( ) . patchAndFetchById ( user . id , { password : hashed , updated _at : new Date ( ) } )
await models . Token . query ( ) . delete ( ) . where ( 'token' , token )
return true
}
}
2017-08-27 17:59:41 +00:00
2020-12-13 14:36:07 +00:00
export class User {
static async get ( identifier ) {
let scope = 'id'
if ( typeof identifier === 'string' ) {
scope = 'username'
if ( identifier . indexOf ( '@' ) !== - 1 ) {
scope = 'email'
} else if ( identifier . length === 36 && identifier . match ( /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i ) ) {
scope = 'uuid'
}
} else if ( typeof identifier === 'object' ) {
if ( identifier . id != null ) {
identifier = identifier . id
} else if ( identifier . username ) {
scope = 'username'
identifier = identifier . username
2017-08-27 17:47:52 +00:00
} else {
2020-12-13 14:36:07 +00:00
return null
2017-08-27 17:47:52 +00:00
}
2020-12-13 14:36:07 +00:00
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
const user = await models . User . query ( ) . where ( scope , identifier )
if ( ! user . length ) return null
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
return user [ 0 ]
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
static async ensureObject ( user , fieldsPresent = [ 'id' ] ) {
if ( typeof user !== 'object' || ! keysAvailable ( user , fieldsPresent ) ) {
return User . get ( user )
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
if ( user . id ) {
return user
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
return null
}
static async socialStatus ( user ) {
user = await User . ensureObject ( user , [ 'password' ] )
if ( ! user ) return null
const external = await models . External . query ( ) . orderBy ( 'created_at' , 'asc' ) . where ( 'user_id' , user . id )
const enabled = { }
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
for ( const i in external ) {
const ext = external [ i ]
enabled [ ext . service ] = true
}
const accountSourceIsExternal = user . password === null || user . password === ''
const obj = {
enabled : enabled ,
password : ! accountSourceIsExternal
}
if ( accountSourceIsExternal ) {
obj . source = external [ 0 ] . service
}
return obj
}
static async update ( user , data ) {
user = await User . ensureObject ( user )
if ( ! user ) throw new Error ( 'No such user.' )
data = Object . assign ( {
updated _at : new Date ( )
} , data )
return models . User . query ( ) . patchAndFetchById ( user . id , data )
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
static async changeAvatar ( user , fileName ) {
user = await User . ensureObject ( user , [ 'avatar_file' ] )
const uploadsDir = path . join ( _ _dirname , '../../' , 'usercontent' , 'images' )
const pathOf = path . join ( uploadsDir , fileName )
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
if ( ! await fs . pathExists ( pathOf ) ) {
throw new Error ( 'No such file' )
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
// Delete previous upload
if ( user . avatar _file != null ) {
const file = path . join ( uploadsDir , user . avatar _file )
if ( await fs . pathExists ( file ) ) {
await fs . unlink ( file )
2017-08-31 17:24:38 +00:00
}
2020-12-13 14:36:07 +00:00
}
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
await User . update ( user , { avatar _file : fileName } )
return fileName
}
static async removeAvatar ( user ) {
user = await User . ensureObject ( user , [ 'avatar_file' ] )
const uploadsDir = path . join ( _ _dirname , '../../' , 'usercontent' , 'images' )
if ( ! user . avatar _file ) return { }
2017-08-27 17:47:52 +00:00
2020-12-13 14:36:07 +00:00
const file = path . join ( uploadsDir , user . avatar _file )
if ( await fs . pathExists ( file ) ) {
await fs . unlink ( file )
}
return User . update ( user , { avatar _file : null } )
}
static async getBanStatus ( field , ip = false ) {
let bans
if ( ip === true ) {
bans = await models . Ban . query ( ) . where ( 'associated_ip' , field )
} else {
bans = await models . Ban . query ( ) . where ( 'user_id' , field )
}
const bansActive = [ ]
for ( const i in bans ) {
const ban = bans [ i ]
// Check expiry
if ( ban . expires _at && new Date ( ban . expires _at ) . getTime ( ) < Date . now ( ) ) continue
const banInfo = {
banned : ban . created _at ,
reason : ban . reason ,
expiry : ban . expires _at
2017-08-27 17:47:52 +00:00
}
2020-12-13 14:36:07 +00:00
bansActive . push ( banInfo )
2017-08-27 17:47:52 +00:00
}
2020-12-13 14:36:07 +00:00
return bansActive
2017-08-02 21:24:01 +00:00
}
}
2021-02-09 16:46:04 +00:00
export class Payment {
2020-12-13 14:36:07 +00:00
static async handleIPN ( body ) {
const sandboxed = body . test _ipn === '1'
const url = 'https://ipnpb.' + ( sandboxed ? 'sandbox.' : '' ) + 'paypal.com/cgi-bin/webscr'
console . debug ( 'Incoming payment' )
const verification = await httpPOST ( url , { } , Object . assign ( {
cmd : '_notify-validate'
} , body ) )
if ( verification !== 'VERIFIED' ) return null
// Ignore the adding of non-on-site donations
if ( body . item _name && config . donations . name && body . item _name !== config . donations . name ) {
return true
}
if ( sandboxed ) {
console . debug ( 'Sandboxed payment:' , body )
} else {
console . debug ( 'IPN Verified Notification:' , body )
}
if ( body . txn _id ) {
if ( txnStore . indexOf ( body . txn _id ) !== - 1 ) return true
txnStore . push ( body . txn _id )
}
let user
const source = [ ]
if ( sandboxed ) {
source . push ( 'virtual' )
}
// TODO: add hooks
const custom = body . custom . split ( ',' )
for ( const i in custom ) {
const str = custom [ i ]
if ( str . indexOf ( 'userid:' ) === 0 ) {
body . user _id = parseInt ( str . split ( ':' ) [ 1 ] )
} else if ( str . indexOf ( 'mcu:' ) === 0 ) {
source . push ( 'mcu:' + str . split ( ':' ) [ 1 ] )
}
}
if ( body . user _id != null ) {
user = await User . get ( body . user _id )
} else if ( body . payer _email != null ) {
user = await User . get ( body . payer _email )
}
const donation = {
user _id : user ? user . id : null ,
amount : ( body . mc _gross || body . payment _gross || 'Unknown' ) + ' ' + ( body . mc _currency || 'EUR' ) ,
source : source . join ( ',' ) ,
note : body . memo || '' ,
read : 0 ,
created _at : new Date ( body . payment _date )
}
console . log ( 'Server receieved a successful PayPal IPN message.' )
return models . Donation . query ( ) . insert ( donation )
}
static async userContributions ( user ) {
user = await User . ensureObject ( user )
const dbq = await models . Donation . query ( ) . orderBy ( 'created_at' , 'desc' ) . where ( 'user_id' , user . id )
const contribs = [ ]
for ( const i in dbq ) {
contribs . push ( await cleanUpDonation ( dbq [ i ] ) )
}
return contribs
}
static async allContributions ( count , mcOnly , timeframe = 0 ) {
const dbq = await models . Donation . query ( ) . orderBy ( 'created_at' , 'desc' ) . limit ( count )
const contribs = [ ]
for ( const i in dbq ) {
const obj = await cleanUpDonation ( dbq [ i ] , mcOnly , timeframe )
if ( ! obj ) continue
contribs . push ( obj )
}
return contribs
}
}