diff --git a/src/core/ChatProvider.js b/src/core/ChatProvider.js index 8268051..cac0e4d 100644 --- a/src/core/ChatProvider.js +++ b/src/core/ChatProvider.js @@ -394,7 +394,6 @@ export class ChatProvider { user.ipSub, country, ); - console.log(allowed, needProxycheck, name, id, country); if (allowed) { logger.info( `${name} / ${user.ip} tried to send chat message but is not allowed`, diff --git a/src/core/Ranks.js b/src/core/Ranks.js new file mode 100644 index 0000000..745ccc6 --- /dev/null +++ b/src/core/Ranks.js @@ -0,0 +1,111 @@ +/* + * timers and cron for account related actions + */ + +import Sequelize from 'sequelize'; +import RegUser from '../data/sql/RegUser'; +import { + getRanks, + resetDailyRanks, + saveDailyTop, + loadDailyTop, +} from '../data/redis/ranks'; +import socketEvents from '../socket/socketEvents'; +import logger from './logger'; + +import { MINUTE } from './constants'; +import { DailyCron } from '../utils/cron'; + +class Ranks { + ranks; // Array + + constructor() { + this.updateRanking = this.updateRanking.bind(this); + this.resetDailyRanking = this.resetDailyRanking.bind(this); + this.prevTop = []; + this.ranks = { + dailyRanking: [], + ranking: [], + }; + } + + async initialize() { + this.prevTop = await loadDailyTop(); + await this.updateRanking(); + setInterval(this.updateRanking, 10 * MINUTE); + DailyCron.hook(this.resetDailyRanking); + } + + /* + * take array of {useId: score} and resolve + * user informations + */ + static async populateRanking(rawRanks) { + if (!rawRanks.length) { + return rawRanks; + } + const uids = rawRanks.map((r) => r.id); + const userData = await RegUser.findAll({ + attributes: [ + 'id', + 'name', + [ + Sequelize.fn( + 'DATEDIFF', + Sequelize.literal('CURRENT_TIMESTAMP'), + Sequelize.col('createdAt'), + ), + 'age', + ], + ], + where: { + id: uids, + }, + raw: true, + }); + for (let i = 0; i < userData.length; i += 1) { + const { id, name, age } = userData[i]; + const dat = rawRanks.find((r) => r.id === id); + if (dat) { + dat.name = name; + dat.age = age; + } + } + return rawRanks; + } + + async updateRanking() { + if (socketEvents.amIImportant()) { + // TODO do this only in main shard + } + // populate dictionaries + const ranking = await Ranks.populateRanking( + await getRanks( + false, + 1, + 100, + )); + const dailyRanking = await Ranks.populateRanking( + await getRanks( + true, + 1, + 100, + )); + this.ranks.ranking = ranking; + this.ranks.dailyRanking = dailyRanking; + } + + async resetDailyRanking() { + if (!socketEvents.amIImportant()) { + return; + } + this.prevTop = await saveDailyTop(this.ranks.dailyRanking); + logger.info('Resetting Daily Ranking'); + await resetDailyRanks(); + await this.updateRanking(); + } +} + + +const rankings = new Ranks(); +export default rankings; diff --git a/src/core/draw.js b/src/core/draw.js index ce65e59..c6dfab5 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -12,7 +12,7 @@ import { setPixelByOffset, setPixelByCoords, } from './setPixel'; -import rankings from './ranking'; +import rankings from './Ranks'; import canvases from './canvases'; import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants'; @@ -144,6 +144,7 @@ export async function drawByOffsets( /* * validate pixels */ + let ranked = canvas.ranked && user.id && pcd; for (let u = 0; u < pixels.length; u += 1) { const [offset, color] = pixels[u]; pxlOffsets.push(offset); @@ -180,15 +181,18 @@ export async function drawByOffsets( throw new Error(8); } + /* dont rank antarctica */ // eslint-disable-next-line eqeqeq - if (canvas.ranked && (canvasId != 0 || y < 14450) && pcd) { - pixels[u].push(true); + if (canvasId == 0 && y > 14450) { + ranked = false; } } [retCode, pxlCnt, wait, coolDown, needProxycheck] = await allowPlace( ip, user.id, + user.country, + ranked, canvasId, i, j, clrIgnore, @@ -198,11 +202,12 @@ export async function drawByOffsets( ); for (let u = 0; u < pxlCnt; u += 1) { - const [offset, color, ranked] = pixels[u]; + const [offset, color] = pixels[u]; setPixelByOffset(canvasId, color, i, j, offset); - if (ranked) { - rankedPxlCnt += 1; - } + } + + if (ranked) { + rankedPxlCnt = pxlCnt; } const duration = Date.now() - startTime; @@ -219,10 +224,6 @@ export async function drawByOffsets( } } - if (rankedPxlCnt) { - user.incrementPixelcount(rankedPxlCnt); - } - if (retCode !== 13) { curReqIPs.delete(ip); } diff --git a/src/core/ranking.js b/src/core/ranking.js deleted file mode 100644 index d95e6d4..0000000 --- a/src/core/ranking.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - * timers and cron for account related actions - */ - -import Sequelize from 'sequelize'; -import sequelize from '../data/sql/sequelize'; -import RegUser from '../data/sql/RegUser'; -import { saveDailyTop, loadDailyTop } from '../data/redis/PrevDayTop'; -import socketEvents from '../socket/socketEvents'; -import logger from './logger'; - -import { MINUTE } from './constants'; -import { DailyCron } from '../utils/cron'; - -class Ranks { - ranks; // Array - - constructor() { - this.updateRanking = this.updateRanking.bind(this); - this.resetDailyRanking = this.resetDailyRanking.bind(this); - this.prevTop = []; - this.ranks = { - dailyRanking: [], - ranking: [], - }; - } - - async initialize() { - this.prevTop = await loadDailyTop(); - await this.updateRanking(); - setInterval(this.updateRanking, 5 * MINUTE); - DailyCron.hook(this.resetDailyRanking); - } - - async updateRanking() { - if (socketEvents.amIImportant()) { - logger.info('Update pixel rankings in SQL'); - // recalculate ranking column - await sequelize.query( - // eslint-disable-next-line max-len - 'SET @r=0; UPDATE Users SET ranking= @r:= (@r + 1) ORDER BY totalPixels DESC;', - ); - await sequelize.query( - // eslint-disable-next-line max-len - 'SET @r=0; UPDATE Users SET dailyRanking= @r:= (@r + 1) ORDER BY dailyTotalPixels DESC;', - ); - } else { - logger.info('Get pixel rankings from SQL'); - } - // populate dictionaries - const ranking = await RegUser.findAll({ - attributes: [ - 'id', - 'name', - 'totalPixels', - 'ranking', - 'dailyRanking', - 'dailyTotalPixels', - [ - Sequelize.fn( - 'DATEDIFF', - Sequelize.literal('CURRENT_TIMESTAMP'), - Sequelize.col('createdAt'), - ), - 'age', - ], - ], - limit: 100, - where: { - ranking: { [Sequelize.Op.not]: null }, - }, - order: ['ranking'], - raw: true, - }); - const dailyRanking = await RegUser.findAll({ - attributes: [ - 'id', - 'name', - 'totalPixels', - 'ranking', - 'dailyRanking', - 'dailyTotalPixels', - [ - Sequelize.fn( - 'DATEDIFF', - Sequelize.literal('CURRENT_TIMESTAMP'), - Sequelize.col('createdAt'), - ), - 'age', - ], - ], - limit: 100, - where: { - dailyRanking: { [Sequelize.Op.not]: null }, - }, - order: ['dailyRanking'], - raw: true, - }); - this.ranks.ranking = ranking; - this.ranks.dailyRanking = dailyRanking; - } - - async resetDailyRanking() { - if (!socketEvents.amIImportant()) { - return; - } - this.prevTop = await saveDailyTop(this.ranks.dailyRanking); - logger.info('Resetting Daily Ranking'); - await RegUser.update({ dailyTotalPixels: 0 }, { where: {} }); - await this.updateRanking(); - } -} - - -const rankings = new Ranks(); -export default rankings; diff --git a/src/data/User.js b/src/data/User.js index c254ed9..e985bd2 100644 --- a/src/data/User.js +++ b/src/data/User.js @@ -10,8 +10,8 @@ import { QueryTypes, Utils } from 'sequelize'; import sequelize from './sql/sequelize'; import { RegUser, Channel, UserBlock } from './sql'; -import { incrementPixelcount } from './sql/RegUser'; import { setCoolDown, getCoolDown } from './redis/cooldown'; +import { getUserRanks } from './redis/ranks'; import { getIPv6Subnet } from '../utils/ip'; import { ADMIN_IDS } from '../core/config'; @@ -50,7 +50,6 @@ export const regUserQueryInclude = [{ class User { id; // string ip; // string - wait; // ?number regUser; // Object channels; // Object blocked; // Array @@ -179,13 +178,6 @@ class User { return getCoolDown(this.ipSub, this.id, canvasId); } - incrementPixelcount(amount = 1) { - const { id } = this; - if (id) { - incrementPixelcount(id, amount); - } - } - async getTotalPixels() { const { id } = this; if (!id) return 0; @@ -210,6 +202,7 @@ class User { } async setCountry(country) { + this.country = country; if (this.regUser && this.regUser.flag !== country) { this.regUser.update({ flag: country, @@ -229,7 +222,7 @@ class User { return true; } - getUserData() { + async getUserData() { const { id, userlvl, @@ -248,23 +241,25 @@ class User { name: null, mailVerified: false, blockDm: false, - totalPixels: 0, - dailyTotalPixels: 0, - ranking: null, - dailyRanking: null, mailreg: false, }; } const { regUser } = this; + const [ + totalPixels, + dailyTotalPixels, + ranking, + dailyRanking, + ] = await getUserRanks(id); return { ...data, name: regUser.name, mailVerified: regUser.mailVerified, blockDm: regUser.blockDm, - totalPixels: regUser.totalPixels, - dailyTotalPixels: regUser.dailyTotalPixels, - ranking: regUser.ranking, - dailyRanking: regUser.dailyRanking, + totalPixels, + dailyTotalPixels, + ranking, + dailyRanking, mailreg: !!(regUser.password), }; } diff --git a/src/data/redis/PrevDayTop.js b/src/data/redis/PrevDayTop.js deleted file mode 100644 index d766ee4..0000000 --- a/src/data/redis/PrevDayTop.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * saving and loading the top 10 of the previous day - */ - -import client from './client'; -import logger from '../../core/logger'; - -const PREV_DAILY_TOP_KEY = 'prevtop'; - -/* - * saves the top 10 into redis - * @param dailyRanking Array of dailyRanking - */ -export async function saveDailyTop(dailyRanking) { - const top10 = dailyRanking.slice(0, 10).map((user) => user.id); - const jsonTop = JSON.stringify(top10); - logger.info(`Saving current daily top 10 into redis: ${jsonTop}`); - await client.set(PREV_DAILY_TOP_KEY, jsonTop); - return top10; -} - -/* - * load top10 from redis - * @return Promis Array of user IDs of the top 10 - */ -export async function loadDailyTop() { - const jsonTop = await client.get(PREV_DAILY_TOP_KEY); - logger.info(`Loaded current daily top 10 into redis: ${jsonTop}`); - return (jsonTop) ? JSON.parse(jsonTop) : []; -} diff --git a/src/data/redis/client.js b/src/data/redis/client.js index 7591b28..5749651 100644 --- a/src/data/redis/client.js +++ b/src/data/redis/client.js @@ -10,11 +10,12 @@ import { REDIS_URL, SHARD_NAME } from '../../core/config'; const scripts = { placePxl: defineScript({ - NUMBER_OF_KEYS: 5, + NUMBER_OF_KEYS: 8, SCRIPT: fs.readFileSync('./workers/lua/placePixel.lua'), transformArguments(...args) { return args.map((a) => ((typeof a === 'string') ? a : a.toString())); }, + transformReply(arr) { return arr.map((r) => Number(r)); }, }), allowedChat: defineScript({ NUMBER_OF_KEYS: 3, @@ -22,6 +23,26 @@ const scripts = { transformArguments(...args) { return args.map((a) => ((typeof a === 'string') ? a : a.toString())); }, + transformReply(arr) { return arr.map((r) => Number(r)); }, + }), + getUserRanks: defineScript({ + NUMBER_OF_KEYS: 2, + SCRIPT: fs.readFileSync('./workers/lua/getUserRanks.lua'), + transformArguments(...args) { + return args.map((a) => ((typeof a === 'string') ? a : a.toString())); + }, + transformReply(arr) { return arr.map((r) => Number(r)); }, + }), + zmRankRev: defineScript({ + NUMBER_OF_KEYS: 1, + SCRIPT: fs.readFileSync('./workers/lua/zmRankRev.lua'), + transformArguments(key, uids) { + return [ + key, + ...uids.map((a) => ((typeof a === 'string') ? a : a.toString())), + ]; + }, + transformReply(arr) { return arr.map((r) => Number(r)); }, }), }; diff --git a/src/data/redis/cooldown.js b/src/data/redis/cooldown.js index 999f758..27dbf9b 100644 --- a/src/data/redis/cooldown.js +++ b/src/data/redis/cooldown.js @@ -5,15 +5,18 @@ import client from './client'; import { PREFIX as CAPTCHA_PREFIX } from './captcha'; import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache'; +import { RANKED_KEY, DAILY_RANKED_KEY, DAILY_CRANKED_KEY } from './ranks'; import { CAPTCHA_TIME } from '../../core/config'; const PREFIX = 'cd'; /* * checks how many of the given pixels can be set, - * sets user cooldown and returns the number of pixels to set + * sets user cooldown, increments pixelcount + * and returns the number of pixels to set * @param ip ip of request * @param id userId + * @param ranked boolean if increasing rank * @param clrIgnore, bcd, pcd, cds incormations about canvas * @param i, j chunk coordinates * @param pxls Array with offsets of pixels @@ -22,6 +25,8 @@ const PREFIX = 'cd'; export default function allowPlace( ip, id, + country, + ranked, canvasId, i, j, clrIgnore, @@ -35,9 +40,13 @@ export default function allowPlace( const ipCdKey = `${PREFIX}:${canvasId}:ip:${ip}`; const idCdKey = (id) ? `${PREFIX}:${canvasId}:id:${id}` : 'nope'; const chunkKey = `ch:${canvasId}:${i}:${j}`; + const cc = country || 'xx'; + const rankset = (ranked) ? RANKED_KEY : 'nope'; + const dailyset = (ranked) ? DAILY_RANKED_KEY : 'nope'; return client.placePxl( - isalKey, captKey, ipCdKey, idCdKey, chunkKey, - clrIgnore, bcd, pcd, cds, + // eslint-disable-next-line max-len + isalKey, captKey, ipCdKey, idCdKey, chunkKey, rankset, dailyset, DAILY_CRANKED_KEY, + clrIgnore, bcd, pcd, cds, id, cc, ...pxls, ); } diff --git a/src/data/redis/lua/getUserRanks.lua b/src/data/redis/lua/getUserRanks.lua new file mode 100644 index 0000000..571a7b4 --- /dev/null +++ b/src/data/redis/lua/getUserRanks.lua @@ -0,0 +1,30 @@ +-- Get ranks of user +-- Currently only pixelcounts and rankings +-- Keys: +-- rankset: 'rank' +-- dailyset: 'rankd' +-- Args: +-- userId +-- Returns: +-- { +-- 1: totalPixels +-- 2: dailyPixels +-- 3: totalRanking +-- 4: dailyRanking +-- } +local ret = {0, 0, 0, 0} +-- get total pixels +local cnt = redis.call('zscore', KEYS[1], ARGV[1]) +if cnt then + ret[1] = cnt + -- get total rank + ret [3] = redis.call('zrevrank', KEYS[1], ARGV[1]) + 1 +end +-- get daily pixels +local dcnt = redis.call('zscore', KEYS[2], ARGV[1]) +if dcnt then + ret[2] = dcnt + -- get daily rank + ret [4] = redis.call('zrevrank', KEYS[2], ARGV[1]) + 1 +end +return ret diff --git a/src/data/redis/lua/placePixel.lua b/src/data/redis/lua/placePixel.lua index f81a5c0..5e08e15 100644 --- a/src/data/redis/lua/placePixel.lua +++ b/src/data/redis/lua/placePixel.lua @@ -1,18 +1,26 @@ --- Checking requirements and calculating cooldown of user wthin --- redis itself. Does not set pixels directly. Pixels are set in batches +-- Checking requirements for placing pixels, calculating cooldown +-- of user and incrementing pixel counts wthin redis itself. +-- Does not set pixels directly. Pixels are set in batches -- in RedisCanvas.js --- This script will get copied into the dist/workers directory from webpack -- Keys: -- isAlloweed: 'isal:ip' (proxycheck, blacklist, whitelist) --- isHuman 'human:ip' (captcha needed when expired) +-- isHuman 'human:ip' captcha needed when expired, +-- 'nope' if no captcha should be checked -- ipCD: 'cd:canvasId:ip:ip' -- uCD: 'cd:canvasId:id:userId' +-- 'nope' if not logged in -- chunk: 'ch:canvasId:i:j' +-- rankset: 'rank' sorted set of pixelcount +-- 'nope' if not increasing ranks +-- dailyset: 'rankd' sorted set of daily pixelcount +-- countryset: sorted set for country stats -- Args: -- clrIgnore: integer number of what colors are considered unset -- bcd: number baseColldown (fixed to cdFactor and 0 if admin) -- pcd: number set pixel cooldown (fixed to cdFactor and 0 if admin) -- cds: max cooldown of canvas +-- userId: '0' if not logged in +-- cc country code -- off1, chonk offset of first pixel -- off2, chonk offset of second pixel -- ..., infinie pixels possible @@ -21,7 +29,7 @@ -- 1: pixel return status code (check ui/placePixel.js) -- 2: amount of successfully set pixels -- 3: total cooldown of user --- 4: info about placed pixel cooldown (addition of last pixel) +-- 4: added cooldown of last pixel -- 5: if we have to update isAllowed( proxycheck) -- } local ret = {0, 0, 0, 0, 0} @@ -69,9 +77,8 @@ local cli = tonumber(ARGV[1]) local bcd = tonumber(ARGV[2]) local pcd = tonumber(ARGV[3]) local cds = tonumber(ARGV[4]) -for c = 5,#ARGV do +for c = 7,#ARGV do local off = tonumber(ARGV[c]) * 8 - local clr = tonumber(ARGV[c + 1]) -- get color of pixel on canvas local sclr = redis.call('bitfield', KEYS[5], 'get', 'u8', off) sclr = sclr[1] @@ -88,19 +95,31 @@ for c = 5,#ARGV do end cd = cd + pxlcd if cd > cds then + -- pixelstack used up + -- report difference as last cd cd = cd - pxlcd pxlcd = cds - cd - pxlcd - -- pixelstack used up ret[1] = 9 break end pxlcnt = pxlcnt + 1 end -if pxlcnt > 0 and cd > 0 then - redis.call('set', KEYS[3], '', 'px', cd) - if KEYS[4] ~= "nope" then - redis.call('set', KEYS[4], '', 'px', cd) +if pxlcnt > 0 then + -- set cooldown + if cd > 0 then + redis.call('set', KEYS[3], '', 'px', cd) + if KEYS[4] ~= "nope" then + redis.call('set', KEYS[4], '', 'px', cd) + end + end + -- increment pixelcount + if KEYS[6] ~= 'nope' then + redis.call('zincrby', KEYS[6], pxlcnt, ARGV[5]) + redis.call('zincrby', KEYS[7], pxlcnt, ARGV[5]) + if ARGV[6] ~= 'xx' then + redis.call('zincrby', KEYS[8], pxlcnt, ARGV[6]) + end end end diff --git a/src/data/redis/lua/zmRankRev.lua b/src/data/redis/lua/zmRankRev.lua new file mode 100644 index 0000000..bafa13a --- /dev/null +++ b/src/data/redis/lua/zmRankRev.lua @@ -0,0 +1,16 @@ +-- Get multiple ranks from sorted set +-- Keys: +-- set: sorted set +-- Args: +-- [member,...] dynamic amount of members to look for +-- return: +-- table with the ranks, 0 for an item with no rank +local ret = {} +for c = 1,#ARGV do + local rank = redis.call('zrevrank', KEYS[1], ARGV[c]) + if not rank then + rank = 0 + end + ret[c] = rank +end +return ret diff --git a/src/data/redis/ranks.js b/src/data/redis/ranks.js new file mode 100644 index 0000000..9c54a5d --- /dev/null +++ b/src/data/redis/ranks.js @@ -0,0 +1,138 @@ +/* + * counter for daily and total pixels and ranking + */ +import client from './client'; + +import logger from '../../core/logger'; + +export const RANKED_KEY = 'rank'; +export const DAILY_RANKED_KEY = 'rankd'; +export const DAILY_CRANKED_KEY = 'crankd'; +const PREV_DAY_TOP_KEY = 'prankd'; +const DAY_STATS_RANKS_KEY = 'ds'; +const CDAY_STATS_RANKS_KEY = 'cds'; +const PREV_DAILY_TOP_KEY = 'prevtop'; + +/* + * get pixelcount and ranking + * @param userId + * @return [ totalPixels, dailyPixels, totalRanking, dailyRanking ] + */ +export async function getUserRanks(userId) { + const ranks = await client.getUserRanks( + RANKED_KEY, DAILY_RANKED_KEY, + userId, + ); + return ranks.map((r) => Number(r)); +} + +/* + * get userIds by ranks + * @param daily integer if using daily or total score + * @param start, amount rank to start and end + * @return Array of objects with {userId, score, dailyScore} in given range + */ +export async function getRanks(daily, start, amount) { + start -= 1; + amount -= 1; + let key; + let valueName; + let rankName; + let oKey; + let oValueName; + let oRankName; + if (daily) { + key = DAILY_RANKED_KEY; + valueName = 'dailyTotalPixels'; + rankName = 'dailyRanking'; + oKey = RANKED_KEY; + oValueName = 'totalPixels'; + oRankName = 'ranking'; + } else { + key = RANKED_KEY; + valueName = 'totalPixels'; + rankName = 'ranking'; + oKey = DAILY_RANKED_KEY; + oValueName = 'dailyTotalPixels'; + oRankName = 'dailyRanking'; + } + /* returns { value: uid, score: pixelCnt } */ + const ranks = await client.zRangeWithScores(key, start, start + amount, { + REV: true, + }); + const uids = ranks.map((r) => r.value); + if (!uids.length) { + return uids; + } + const oScores = await client.zmScore(oKey, uids); + /* impolemented with lua, which blocks :( */ + const oRanks = await client.zmRankRev(oKey, uids); + const ret = []; + for (let i = 0; i < ranks.length; i += 1) { + const uob = { + id: Number(uids[i]), + [valueName]: ranks[i].score, + [rankName]: i + 1, + [oValueName]: oScores[i], + [oRankName]: oRanks[i] + 1, + }; + ret.push(uob); + } + return ret; +} + +/* + * reset daily ranks + * @return boolean for success + */ +export async function resetDailyRanks() { + // store top 10 + console.log('DAILYREDIS SAVE TOP 10'); + await client.zRangeWithScores(PREV_DAY_TOP_KEY, DAILY_RANKED_KEY, 0, 9, { + REV: true, + WITHSCORES: true, + }); + // store day + const yesterday = new Date(Date.now() - 1000 * 3600 * 24); + let day = yesterday.getUTCDate(); + if (day < 10) day = `0${day}`; + let month = yesterday.getUTCMonth() + 1; + if (month < 10) month = `0${month}`; + const year = yesterday.getUTCFullYear(); + const dateKey = `${year}${month}${day}`; + console.log('DAILYREDIS', dateKey); + await client.zUnionStore( + `${DAY_STATS_RANKS_KEY}:${dateKey}`, + DAILY_RANKED_KEY, + ); + await client.zUnionStore( + `${CDAY_STATS_RANKS_KEY}:${dateKey}`, + DAILY_CRANKED_KEY, + ); + // reset daily counter + console.log('DAILYREDIS RESET'); + await client.del(DAILY_RANKED_KEY); + await client.del(DAILY_CRANKED_KEY); +} + +/* + * saves the top 10 into redis + * @param dailyRanking Array of dailyRanking + */ +export async function saveDailyTop(dailyRanking) { + const top10 = dailyRanking.slice(0, 10).map((user) => user.id); + const jsonTop = JSON.stringify(top10); + logger.info(`Saving current daily top 10 into redis: ${jsonTop}`); + await client.set(PREV_DAILY_TOP_KEY, jsonTop); + return top10; +} + +/* + * load top10 from redis + * @return Promis Array of user IDs of the top 10 + */ +export async function loadDailyTop() { + const jsonTop = await client.get(PREV_DAILY_TOP_KEY); + logger.info(`Loaded current daily top 10 into redis: ${jsonTop}`); + return (jsonTop) ? JSON.parse(jsonTop) : []; +} diff --git a/src/data/sql/RegUser.js b/src/data/sql/RegUser.js index 23176b4..ba79508 100644 --- a/src/data/sql/RegUser.js +++ b/src/data/sql/RegUser.js @@ -7,7 +7,6 @@ import { DataTypes, QueryTypes } from 'sequelize'; -import logger from '../../core/logger'; import sequelize from './sequelize'; import { generateHash } from '../../utils/hash'; @@ -42,28 +41,6 @@ const RegUser = sequelize.define('User', { allowNull: true, }, - totalPixels: { - type: DataTypes.INTEGER.UNSIGNED, - allowNull: false, - defaultValue: 0, - }, - - dailyTotalPixels: { - type: DataTypes.INTEGER.UNSIGNED, - allowNull: false, - defaultValue: 0, - }, - - ranking: { - type: DataTypes.INTEGER.UNSIGNED, - allowNull: true, - }, - - dailyRanking: { - type: DataTypes.INTEGER.UNSIGNED, - allowNull: true, - }, - // mail and Minecraft verified verified: { type: DataTypes.TINYINT, @@ -204,59 +181,4 @@ export async function getNamesToIds(ids) { return idToNameMap; } -/* - * increment user pixelcount in batches sequentially - * Queue directly accesses queuedPxlIncrement in user object - */ -let incrementQueue = {}; -let pushLoop = null; -const incrementLoop = async () => { - const idKeys = Object.keys(incrementQueue); - if (!idKeys.length) { - pushLoop = null; - return; - } - let queue = incrementQueue; - incrementQueue = {}; - const orderedCnts = {}; - idKeys.forEach((id) => { - const cnt = queue[id]; - const cntArr = orderedCnts[cnt]; - if (cntArr) { - cntArr.push(id); - } else { - orderedCnts[cnt] = [id]; - } - }); - queue = Object.keys(orderedCnts); - for (let u = 0; u < queue.length; u += 1) { - const by = queue[u]; - const id = orderedCnts[by]; - try { - // eslint-disable-next-line no-await-in-loop - await RegUser.increment(['totalPixels', 'dailyTotalPixels'], { - by, - where: { - id, - }, - silent: true, - raw: true, - }); - } catch (err) { - logger.warn(`Error on pixel increment: ${err.message}`); - } - } - pushLoop = setTimeout(incrementLoop, 250); -}; -export async function incrementPixelcount(id, by) { - if (incrementQueue[id]) { - incrementQueue[id] += by; - } else { - incrementQueue[id] = by; - } - if (!pushLoop) { - pushLoop = setTimeout(incrementLoop, 0); - } -} - export default RegUser; diff --git a/src/routes/ranking.js b/src/routes/ranking.js index c893357..feb5485 100644 --- a/src/routes/ranking.js +++ b/src/routes/ranking.js @@ -2,7 +2,7 @@ * send global ranking */ -import rankings from '../core/ranking'; +import rankings from '../core/Ranks'; export default (req, res) => { res.json(rankings.ranks); diff --git a/src/server.js b/src/server.js index 5a4847c..1600945 100644 --- a/src/server.js +++ b/src/server.js @@ -9,7 +9,7 @@ import http from 'http'; import forceGC from './core/forceGC'; import logger from './core/logger'; -import rankings from './core/ranking'; +import rankings from './core/Ranks'; import sequelize from './data/sql/sequelize'; import { connect as connectRedis } from './data/redis/client'; import routes from './routes'; diff --git a/src/store/reducers/ranks.js b/src/store/reducers/ranks.js index e19c4af..5b523bc 100644 --- a/src/store/reducers/ranks.js +++ b/src/store/reducers/ranks.js @@ -51,6 +51,9 @@ export default function ranks( case 's/REC_ME': case 's/LOGIN': { + if (!action.totalPixels) { + return state; + } const { totalPixels, dailyTotalPixels,