Skip to content

Commit

Permalink
Merge branch 'master' into new-sandbag-watch
Browse files Browse the repository at this point in the history
* master:
  index puzzle path regen script - closes lichess-org#8199
  scalafmt swiss BsonHandlers
  tweak activity bus subs
  invalidate team cache
  tweak team bus subs
  fix security store delete query
  yarn run format
  • Loading branch information
ornicar committed Feb 17, 2021
2 parents d275f0f + ea63ff2 commit 9704ddc
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 33 deletions.
236 changes: 236 additions & 0 deletions bin/mongodb/puzzle-regen-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/* Generates and saves a new generation of puzzle paths.
* Drops the previous generation.
*
* mongo <IP>:<PORT>/<DB> mongodb-puzzle-regen-paths.js
*
* Must run on the puzzle database.
* Should run every 60 minutes.
* Should complete within 3 minutes.
* OK to run many times in a row.
* OK to skip runs.
* NOT OK to run concurrently.
*
* might require this mongodb config: (https://jira.mongodb.org/browse/SERVER-44174)
* setParameter:
* internalQueryMaxPushBytes: 314572800
*/

const puzzleColl = db.puzzle2_puzzle;
const pathColl = db.puzzle2_path;
const verbose = false;
const maxRatingBuckets = 12;
const mixRatingBuckets = 20;
const maxPathLength = 500;
const maxPuzzlesPerTheme = 2 * 1000 * 1000; // reduce to 500000 to avoid memory restrictions in some envs (!?)

const generation = Date.now();

const tiers = [
['top', 20 / 100],
['good', 50 / 100],
['all', 95 / 100],
];

const themes = db.puzzle2_puzzle.distinct('themes', {});

function chunkify(a, n) {
let len = a.length,
out = [],
i = 0,
size;
if (len % n === 0) {
size = Math.floor(len / n);
while (i < len) {
out.push(a.slice(i, (i += size)));
}
} else
while (i < len) {
size = Math.ceil((len - i) / n--);
out.push(a.slice(i, (i += size)));
}
return out;
}
const padRating = r => (r < 1000 ? '0' : '') + r;

themes.concat(['mix']).forEach(theme => {
const selector = {
themes:
theme == 'mix'
? {
$ne: 'equality',
}
: theme == 'equality'
? 'equality'
: {
$eq: theme,
$ne: 'equality',
},
};

const nbPuzzles = puzzleColl.count(selector);

if (!nbPuzzles) return [];

const themeMaxPathLength = Math.max(10, Math.min(maxPathLength, Math.round(nbPuzzles / 200)));
const nbRatingBuckets =
theme == 'mix'
? mixRatingBuckets
: Math.max(3, Math.min(maxRatingBuckets, Math.round(nbPuzzles / themeMaxPathLength / 20)));

if (verbose)
print(
`theme: ${theme}, puzzles: ${nbPuzzles}, path length: ${themeMaxPathLength}, rating buckets: ${nbRatingBuckets}`
);

let bucketIndex = 0;

db.puzzle2_puzzle
.aggregate(
[
{
$match: selector,
},
{
$limit: maxPuzzlesPerTheme,
},
{
$bucketAuto: {
buckets: nbRatingBuckets,
groupBy: '$glicko.r',
output: {
puzzle: {
$push: {
id: '$_id',
vote: '$vote',
},
},
},
},
},
{
$unwind: '$puzzle',
},
{
$sort: {
'puzzle.vote': -1,
},
},
{
$group: {
_id: '$_id',
total: {
$sum: 1,
},
puzzles: {
$push: '$puzzle.id',
},
},
},
{
$facet: tiers.reduce(
(facets, [name, ratio]) => ({
...facets,
...{
[name]: [
{
$project: {
total: 1,
puzzles: {
$slice: [
'$puzzles',
{
$round: {
$multiply: ['$total', ratio],
},
},
],
},
},
},
{
$unwind: '$puzzles',
},
{
$sample: {
// shuffle
size: 9999999,
},
},
{
$group: {
_id: '$_id',
puzzles: {
$addToSet: '$puzzles',
},
},
},
{
$sort: {
'_id.min': 1,
},
},
{
$addFields: {
tier: name,
},
},
],
},
}),
{}
),
},
{
$project: {
bucket: {
$concatArrays: tiers.map(t => '$' + t[0]),
},
},
},
{
$unwind: '$bucket',
},
{
$replaceRoot: {
newRoot: '$bucket',
},
},
],
{
allowDiskUse: true,
comment: 'regen-paths',
}
)
.forEach(bucket => {
const isFirstOfTier = bucketIndex % nbRatingBuckets == 0;
const isLastOfTier = bucketIndex % nbRatingBuckets == nbRatingBuckets - 1;
const pathLength = Math.max(10, Math.min(maxPathLength, Math.round(bucket.puzzles.length / 30)));
const ratingMin = isFirstOfTier ? 100 : Math.ceil(bucket._id.min);
const ratingMax = isLastOfTier ? 9999 : Math.floor(bucket._id.max);
const nbPaths = Math.max(1, Math.floor(bucket.puzzles.length / pathLength));
const paths = chunkify(bucket.puzzles, nbPaths);
// print(` ${theme} ${bucket.tier} ${ratingMin}->${ratingMax} puzzles: ${bucket.puzzles.length} pathLength: ${pathLength} paths: ${paths.length}`);

pathColl.insert(
paths.map((ids, j) => ({
_id: `${theme}_${bucket.tier}_${padRating(ratingMin)}-${padRating(ratingMax)}_${generation}_${j}`,
min: `${theme}_${bucket.tier}_${padRating(ratingMin)}`,
max: `${theme}_${bucket.tier}_${padRating(ratingMax)}`,
ids,
tier: bucket.tier,
theme: theme,
gen: generation,
})),
{
ordered: false,
}
);
bucketIndex++;
});
});

pathColl.remove({
gen: {
$ne: generation,
},
});
33 changes: 20 additions & 13 deletions modules/activity/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ final class Env(

lazy val jsonView = wire[JsonView]

lila.common.Bus.subscribeFuns(
"finishGame" -> {
case lila.game.actorApi.FinishGame(game, _, _) if !game.aborted => write.game(game).unit
},
"finishPuzzle" -> { case res: lila.puzzle.Puzzle.UserResult =>
write.puzzle(res).unit
},
"stormRun" -> { case lila.hub.actorApi.storm.StormRun(userId, score) =>
write.storm(userId, score).unit
}
)

lila.common.Bus.subscribeFun(
"finishGame",
"forumPost",
"finishPuzzle",
"finishPractice",
"team",
"startSimul",
Expand All @@ -42,23 +52,20 @@ final class Env(
"relation",
"startStudy",
"streamStart",
"stormRun"
"gdprErase"
) {
case lila.game.actorApi.FinishGame(game, _, _) if !game.aborted => write.game(game).unit
case lila.forum.actorApi.CreatePost(post) => write.forumPost(post).unit
case res: lila.puzzle.Puzzle.UserResult => write.puzzle(res).unit
case prog: lila.practice.PracticeProgress.OnComplete => write.practice(prog).unit
case lila.simul.Simul.OnStart(simul) => write.simul(simul).unit
case CorresMoveEvent(move, Some(userId), _, _, false) => write.corresMove(move.gameId, userId).unit
case lila.hub.actorApi.plan.MonthInc(userId, months) => write.plan(userId, months).unit
case lila.hub.actorApi.relation.Follow(from, to) => write.follow(from, to).unit
case lila.study.actorApi.StartStudy(id) =>
case lila.forum.actorApi.CreatePost(post) => write.forumPost(post).unit
case prog: lila.practice.PracticeProgress.OnComplete => write.practice(prog).unit
case lila.simul.Simul.OnStart(simul) => write.simul(simul).unit
case CorresMoveEvent(move, Some(userId), _, _, false) => write.corresMove(move.gameId, userId).unit
case lila.hub.actorApi.plan.MonthInc(userId, months) => write.plan(userId, months).unit
case lila.hub.actorApi.relation.Follow(from, to) => write.follow(from, to).unit
case lila.study.actorApi.StartStudy(id) =>
// wait some time in case the study turns private
system.scheduler.scheduleOnce(5 minutes) { write.study(id).unit }.unit
case lila.hub.actorApi.team.CreateTeam(id, _, userId) => write.team(id, userId).unit
case lila.hub.actorApi.team.JoinTeam(id, userId) => write.team(id, userId).unit
case lila.hub.actorApi.streamer.StreamStart(userId) => write.streamStart(userId).unit
case lila.hub.actorApi.storm.StormRun(userId, score) => write.storm(userId, score).unit
case lila.user.User.GDPRErase(user) => write.erase(user).unit
}
}
2 changes: 1 addition & 1 deletion modules/security/src/main/Store.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ final class Store(val coll: Coll, cacheApi: lila.memo.CacheApi, localIp: IpAddre
.void >>- uncache(sessionId)

def definitelyEraseAllUserInfo(user: User): Funit =
coll.delete.one($doc("user" -> user)).void
coll.delete.one($doc("user" -> user.id)).void

def closeUserAndSessionId(userId: User.ID, sessionId: String): Funit =
coll.update
Expand Down
16 changes: 8 additions & 8 deletions modules/swiss/src/main/BsonHandlers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ private object BsonHandlers {
)
def writes(w: BSON.Writer, s: Swiss.Settings) =
$doc(
"n" -> s.nbRounds,
"r" -> (!s.rated).option(false),
"d" -> s.description,
"f" -> s.position,
"c" -> (s.chatFor != Swiss.ChatFor.default).option(s.chatFor),
"i" -> s.roundInterval.toSeconds.toInt,
"p" -> s.password,
"o" -> s.conditions.ifNonEmpty,
"n" -> s.nbRounds,
"r" -> (!s.rated).option(false),
"d" -> s.description,
"f" -> s.position,
"c" -> (s.chatFor != Swiss.ChatFor.default).option(s.chatFor),
"i" -> s.roundInterval.toSeconds.toInt,
"p" -> s.password,
"o" -> s.conditions.ifNonEmpty,
"fp" -> s.forbiddenPairings.some.filter(_.nonEmpty)
)
}
Expand Down
19 changes: 12 additions & 7 deletions modules/team/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,18 @@ final class Env(

lazy val getTeamName = new GetTeamName(cached.blockingTeamName)

lila.common.Bus.subscribeFun("shadowban", "teamIsLeader", "teamJoinedBy", "teamIsLeaderOf") {
case lila.hub.actorApi.mod.Shadowban(userId, true) => api.deleteRequestsByUserId(userId).unit
case lila.hub.actorApi.team.IsLeader(teamId, userId, promise) =>
lila.common.Bus.subscribeFuns(
"shadowban" -> { case lila.hub.actorApi.mod.Shadowban(userId, true) =>
api.deleteRequestsByUserId(userId).unit
},
"teamIsLeader" -> { case lila.hub.actorApi.team.IsLeader(teamId, userId, promise) =>
promise completeWith cached.isLeader(teamId, userId)
case lila.hub.actorApi.team.IsLeaderOf(leaderId, memberId, promise) =>
promise completeWith api.isLeaderOf(leaderId, memberId)
case lila.hub.actorApi.team.TeamIdsJoinedBy(userId, promise) =>
},
"teamJoinedBy" -> { case lila.hub.actorApi.team.TeamIdsJoinedBy(userId, promise) =>
promise completeWith cached.teamIdsList(userId)
}
},
"teamIsLeaderOf" -> { case lila.hub.actorApi.team.IsLeaderOf(leaderId, memberId, promise) =>
promise completeWith api.isLeaderOf(leaderId, memberId)
}
)
}
3 changes: 2 additions & 1 deletion modules/team/src/main/TeamApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ final class TeamApi(
def quitAll(userId: User.ID): Fu[List[Team.ID]] =
cached.teamIdsList(userId) flatMap { teamIds =>
memberRepo.removeByUser(userId) >>
teamIds.map { teamRepo.incMembers(_, -1) }.sequenceFu.void inject teamIds
teamIds.map { teamRepo.incMembers(_, -1) }.sequenceFu.void >>-
cached.invalidateTeamIds(userId) inject teamIds
}

def kick(team: Team, userId: User.ID, me: User): Funit =
Expand Down
6 changes: 3 additions & 3 deletions ui/site/css/team/_list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@
background: $c-primary;
color: $c-primary-over;
text-transform: uppercase;
font-size: .9rem;
font-size: 0.9rem;
margin-left: 1em;
}
}

.team__quit {
opacity: 0;
@include transition(.1s);
@include transition(0.1s);
}

tr:hover .team__quit {
opacity: 1;
@include transition(.6s);
@include transition(0.6s);
}

.info {
Expand Down

0 comments on commit 9704ddc

Please sign in to comment.