Compare commits

..

5 Commits

Author SHA1 Message Date
Luna d44541748e guild.mod: refactor guild prune list in pure sql
close #54
2022-08-14 15:36:19 -03:00
Luna 6608db7bf1 tests: add test for guild pruning 2022-08-14 15:34:13 -03:00
Luna cf63a58a63 guild.mod: make execute prune blockable on prune result 2022-08-14 15:34:00 -03:00
Luna c523c297fc guild.mod: fix off-by-one error on role counting 2022-08-14 15:33:46 -03:00
Luna c93b0f1a7b guild.mod: fix prune validate() inputs 2022-08-14 15:33:16 -03:00
2 changed files with 67 additions and 23 deletions

View File

@ -133,36 +133,29 @@ async def get_prune(guild_id: int, days: int) -> list:
- did not login in ``days`` days. - did not login in ``days`` days.
- don't have any roles. - don't have any roles.
""" """
# a good solution would be in pure sql.
# @everyone role is always counted as a role in the db
# (its not implicit to every member as one would think)
# (that is probably the best solution in the future)
# (but we live with what we got)
member_ids = await app.db.fetch( member_ids = await app.db.fetch(
f""" f"""
SELECT id SELECT id
FROM users FROM users
JOIN members JOIN members
ON members.guild_id = $1 AND members.user_id = users.id ON members.guild_id = $1 AND members.user_id = users.id
WHERE users.last_session < (now() - (interval '{days} days')) WHERE
users.last_session < (now() - (interval '{days} days'))
AND (
SELECT COUNT(member_roles.role_id)
FROM member_roles
WHERE member_roles.user_id = members.user_id
) <= 1
""", """,
guild_id, guild_id,
) )
member_ids = [r["id"] for r in member_ids] return [r["id"] for r in member_ids]
members = []
for member_id in member_ids:
role_count = await app.db.fetchval(
"""
SELECT COUNT(*)
FROM member_roles
WHERE guild_id = $1 AND user_id = $2
""",
guild_id,
member_id,
)
if role_count == 0:
members.append(member_id)
return members
@bp.route("/<int:guild_id>/prune", methods=["GET"]) @bp.route("/<int:guild_id>/prune", methods=["GET"])
@ -171,7 +164,7 @@ async def get_guild_prune_count(guild_id):
await guild_perm_check(user_id, guild_id, "kick_members") await guild_perm_check(user_id, guild_id, "kick_members")
j = validate(request.args, GUILD_PRUNE) j = validate(dict(request.args), GUILD_PRUNE)
days = j["days"] days = j["days"]
member_ids = await get_prune(guild_id, days) member_ids = await get_prune(guild_id, days)
@ -197,9 +190,12 @@ async def begin_guild_prune(guild_id):
await guild_perm_check(user_id, guild_id, "kick_members") await guild_perm_check(user_id, guild_id, "kick_members")
j = validate(request.args, GUILD_PRUNE) j = validate(dict(request.args), GUILD_PRUNE)
days = j["days"] days = j["days"]
member_ids = await get_prune(guild_id, days) member_ids = await get_prune(guild_id, days)
app.sched.spawn(prune_members(user_id, guild_id, member_ids)) # TODO move this job to the background scheduler in a way
# that the test can fetch the job and wait on it before
# asserting test result state
await prune_members(user_id, guild_id, member_ids)
return jsonify({"pruned": len(member_ids)}) return jsonify({"pruned": len(member_ids)})

View File

@ -17,9 +17,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
import secrets import secrets
import datetime
import pytest import pytest
from litecord.common.guilds import add_member
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_guild_create(test_cli_user): async def test_guild_create(test_cli_user):
@ -102,3 +105,48 @@ async def test_guild_nickname(test_cli_user):
assert fetched_guild["id"] == str(guild.id) assert fetched_guild["id"] == str(guild.id)
assert fetched_guild["members"][0]["nick"] == NEW_NICKNAME assert fetched_guild["members"][0]["nick"] == NEW_NICKNAME
async def test_prune_guild(test_cli_user):
guild = await test_cli_user.create_guild()
user = await test_cli_user.create_user()
async with test_cli_user.app.app_context():
await add_member(guild.id, user.id)
# assert setup went well
added_member_guild = await guild.refetch()
assert added_member_guild.member_count == 2
resp = await test_cli_user.get(
f"/api/v6/guilds/{guild.id}/prune", query_string={"days": 7}
)
assert resp.status_code == 200
rjson = await resp.json
assert rjson["pruned"] == 0
# set joined_at to the beginning of the universe
await test_cli_user.app.db.execute(
"UPDATE users SET last_session = $1 WHERE id = $2",
datetime.datetime(year=2010, month=1, day=1),
user.id,
)
# execute compute prune, must return 1
resp = await test_cli_user.get(
f"/api/v6/guilds/{guild.id}/prune", query_string={"days": 7}
)
assert resp.status_code == 200
rjson = await resp.json
assert rjson["pruned"] == 1
# execute prune, member count should go to 1
resp = await test_cli_user.post(
f"/api/v6/guilds/{guild.id}/prune",
query_string={"days": 7},
)
assert resp.status_code == 200
rjson = await resp.json
assert rjson["pruned"] == 1
pruned_member_guild = await guild.refetch()
assert pruned_member_guild.member_count == 1