From 88605a7eaf2ae33a88693ba9ad1d8c8cd2cec454 Mon Sep 17 00:00:00 2001 From: Samuel Williamson Date: Wed, 7 Oct 2026 23:13:01 -0400 Subject: [PATCH 1/4] active, frosh, meetings group sync when alum is made active, a user is added to floor, and users are cleared for new year --- conditional/blueprints/housing.py | 10 ++++ conditional/blueprints/member_management.py | 27 ++++++++- conditional/util/ldap.py | 7 +++ conditional/util/slack.py | 63 +++++++++++++++++++++ requirements.in | 1 + requirements.txt | 14 ++++- 6 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 conditional/util/slack.py diff --git a/conditional/blueprints/housing.py b/conditional/blueprints/housing.py index 0cbc9cdd..f0ce0e3d 100644 --- a/conditional/blueprints/housing.py +++ b/conditional/blueprints/housing.py @@ -10,8 +10,10 @@ from conditional.util.ldap import ldap_get_current_students from conditional.util.ldap import ldap_get_member from conditional.util.ldap import ldap_get_roomnumber +from conditional.util.ldap import ldap_get_slack_uid from conditional.util.ldap import ldap_is_eval_director from conditional.util.ldap import ldap_set_active +from conditional.util.slack import add_active_usergroup_user, add_meetings_usergroup_user from conditional.util.user_dict import user_dict_is_eval_director logger = structlog.get_logger() @@ -114,6 +116,14 @@ def change_room_numbers(rmnumber, user_dict=None): log.info(f'{occupant} assigned to room {rmnumber}') ldap_set_active(account) log.info(f'{occupant} marked as active because of room assignment') + slack_uid = ldap_get_slack_uid(account) + try: + add_active_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @active group") + add_meetings_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @meetings group") + except Exception as e: + print("change_room_numbers error:", e) # Delete any old occupants that are no longer in room. for old_occupant in [account for account in current_students if ldap_get_roomnumber(account) == str(rmnumber) diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 7af8d02b..a68345a4 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -39,6 +39,7 @@ from conditional.util.ldap import ldap_get_active_members from conditional.util.ldap import ldap_get_member from conditional.util.ldap import ldap_get_current_students +from conditional.util.ldap import ldap_get_slack_uid from conditional.util.ldap import _ldap_add_member_to_group as ldap_add_member_to_group from conditional.util.ldap import _ldap_remove_member_from_group as ldap_remove_member_from_group @@ -47,6 +48,8 @@ from conditional.util.user_dict import user_dict_is_active, user_dict_is_bad_standing, user_dict_is_current_student, \ user_dict_is_eval_director, user_dict_is_financial_director +from conditional.util.slack import add_active_usergroup_user, add_meetings_usergroup_user, purge_active_usergroup, purge_frosh_usergroup, purge_meetings_usergroup + logger = structlog.get_logger() member_management_bp = Blueprint('member_management_bp', __name__) @@ -526,10 +529,20 @@ def member_management_make_user_active(user_dict=None): or user_dict_is_active(user_dict) \ or user_dict_is_bad_standing(user_dict): return "must be current student, not in bad standing and not active", 403 - + ldap_set_active(user_dict['account']) log.info(f"Make user {user_dict['username']} active") + try: + account = user_dict['account'] + slack_uid = ldap_get_slack_uid(account) + add_active_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @active group") + add_meetings_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @meetings group") + except Exception as e: + print("make_user_active error:", e) + clear_members_cache() return jsonify({"success": True}), 200 @@ -570,6 +583,18 @@ def clear_active_members(user_dict=None): if account.uid != user_dict['username']: log.info(f'Remove {account.uid} from Active Status') ldap_set_inactive(account) + + try: + # Clear the @active, @meetings, and @frosh Slack group + account = user_dict['account'] + slack_uid = ldap_get_slack_uid(account) + purge_active_usergroup(slack_uid) + purge_meetings_usergroup(slack_uid) + purge_frosh_usergroup(slack_uid) + log.info(f"Purged users from @active, @meetings, and @frosh for the new year") + except Exception as e: + print("clear_active_members error:", e) + return jsonify({"success": True}), 200 diff --git a/conditional/util/ldap.py b/conditional/util/ldap.py index a34ad086..bcefa58c 100644 --- a/conditional/util/ldap.py +++ b/conditional/util/ldap.py @@ -71,6 +71,13 @@ def ldap_get_roomnumber(account) -> str: except AttributeError: return "" +@service_cache(maxsize=128) +def ldap_get_slack_uid(account) -> str: + try: + return account.get("slackUID")[0] + except KeyError: + return "" + @service_cache(maxsize=128) def ldap_is_active(account) -> bool: return _ldap_is_member_of_group(account, 'active') diff --git a/conditional/util/slack.py b/conditional/util/slack.py new file mode 100644 index 00000000..8fa280ec --- /dev/null +++ b/conditional/util/slack.py @@ -0,0 +1,63 @@ +from slack_sdk import WebClient +from slack_sdk.errors import SlackRequestError + +from conditional import app + +client = WebClient(token=app.config['SLACK_APP_TOKEN']) +active_usergroup_id = "S0C7AK4AXEV" # should figure out a better way to do this rather than hardcoding +frosh_usergroup_id = "S0C7CBNRPC4" +meetings_usergroup_id = "S0C78GDRLG2" + +def get_usergroup_users(usergroup_id): + response = client.usergroups_users_list( + usergroup=usergroup_id + ) + + users = response['users'] + response_code = response['ok'] + if response_code == 'false': + raise SlackRequestError(response["error"]) + + return users + +def add_usergroup_user(usergroup_id, slack_uid): + group_users = get_usergroup_users(usergroup_id) + group_users.append(slack_uid) + + response = client.usergroups_users_update( + usergroup=usergroup_id, + users=group_users + ) + + response_code = response["ok"] + if response_code == 'false': + raise SlackRequestError(response["error"]) + + return response_code + +def add_active_usergroup_user(slack_uid): + return add_usergroup_user(active_usergroup_id, slack_uid) + +def add_meetings_usergroup_user(slack_uid): + return add_usergroup_user(meetings_usergroup_id, slack_uid) + +def purge_usergroup_users(usergroup_id, slack_uid): + response = client.usergroups_users_update( + usergroup=usergroup_id, + users=slack_uid + ) + + response_code = response["ok"] + if response_code == 'false': + raise SlackRequestError(response["error"]) + + return response_code + +def purge_active_usergroup(slack_uid): + return purge_usergroup_users(active_usergroup_id, slack_uid) + +def purge_frosh_usergroup(slack_uid): + return purge_usergroup_users(frosh_usergroup_id, slack_uid) + +def purge_meetings_usergroup(slack_uid): + return purge_usergroup_users(meetings_usergroup_id, slack_uid) \ No newline at end of file diff --git a/requirements.in b/requirements.in index 9abff33b..afa76de4 100644 --- a/requirements.in +++ b/requirements.in @@ -23,6 +23,7 @@ pip-tools~=7.5.3 psycopg2-binary~=2.9.3 pylint~=3.3.6 python-editor~=1.0.3 +slack-sdk~=3.45.0 sentry-sdk[flask]~=2.63.0 six~=1.17.0 SQLAlchemy~=2.0.48 diff --git a/requirements.txt b/requirements.txt index 739840c4..715216a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,9 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile requirements.in +# +# This file is autogenerated by pip-compile with Python 3.14 +# by the following command: +# +# pip-compile requirements.in +# alembic==1.18.5 # via # -r requirements.in @@ -175,6 +179,8 @@ six==1.17.0 # -r requirements.in # pyjwkest # python-dateutil +slack-sdk==3.45.0 + # via -r requirements.in sqlalchemy==2.0.54 # via # -r requirements.in @@ -210,3 +216,7 @@ wheel==0.48.0 # via pip-tools wrapt==2.1.2 # via -r requirements.in + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools From ebee4227b2798543412e2af240c8f4e0213d8bc5 Mon Sep 17 00:00:00 2001 From: Samuel Williamson Date: Wed, 7 Oct 2026 23:20:18 -0400 Subject: [PATCH 2/4] uhhhhhh i'm dumb and forgot to remove some try except statements --- conditional/blueprints/housing.py | 13 ++++----- conditional/blueprints/member_management.py | 32 +++++++++------------ 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/conditional/blueprints/housing.py b/conditional/blueprints/housing.py index f0ce0e3d..ced48ea0 100644 --- a/conditional/blueprints/housing.py +++ b/conditional/blueprints/housing.py @@ -117,14 +117,11 @@ def change_room_numbers(rmnumber, user_dict=None): ldap_set_active(account) log.info(f'{occupant} marked as active because of room assignment') slack_uid = ldap_get_slack_uid(account) - try: - add_active_usergroup_user(slack_uid) - log.info(f"Add user {user_dict['username']} to @active group") - add_meetings_usergroup_user(slack_uid) - log.info(f"Add user {user_dict['username']} to @meetings group") - except Exception as e: - print("change_room_numbers error:", e) - # Delete any old occupants that are no longer in room. + add_active_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @active group") + add_meetings_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @meetings group") + # Delete any old occupants that are no longer in room. for old_occupant in [account for account in current_students if ldap_get_roomnumber(account) == str(rmnumber) and account.uid not in update["occupants"]]: diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index a68345a4..4f4a2e22 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -533,15 +533,12 @@ def member_management_make_user_active(user_dict=None): ldap_set_active(user_dict['account']) log.info(f"Make user {user_dict['username']} active") - try: - account = user_dict['account'] - slack_uid = ldap_get_slack_uid(account) - add_active_usergroup_user(slack_uid) - log.info(f"Add user {user_dict['username']} to @active group") - add_meetings_usergroup_user(slack_uid) - log.info(f"Add user {user_dict['username']} to @meetings group") - except Exception as e: - print("make_user_active error:", e) + account = user_dict['account'] + slack_uid = ldap_get_slack_uid(account) + add_active_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @active group") + add_meetings_usergroup_user(slack_uid) + log.info(f"Add user {user_dict['username']} to @meetings group") clear_members_cache() return jsonify({"success": True}), 200 @@ -584,16 +581,13 @@ def clear_active_members(user_dict=None): log.info(f'Remove {account.uid} from Active Status') ldap_set_inactive(account) - try: - # Clear the @active, @meetings, and @frosh Slack group - account = user_dict['account'] - slack_uid = ldap_get_slack_uid(account) - purge_active_usergroup(slack_uid) - purge_meetings_usergroup(slack_uid) - purge_frosh_usergroup(slack_uid) - log.info(f"Purged users from @active, @meetings, and @frosh for the new year") - except Exception as e: - print("clear_active_members error:", e) + # Clear the @active, @meetings, and @frosh Slack group + account = user_dict['account'] + slack_uid = ldap_get_slack_uid(account) + purge_active_usergroup(slack_uid) + purge_meetings_usergroup(slack_uid) + purge_frosh_usergroup(slack_uid) + log.info(f"Purged users from @active, @meetings, and @frosh for the new year") return jsonify({"success": True}), 200 From 28622044974ac1d46d9273f7e1a622ad8ce499de Mon Sep 17 00:00:00 2001 From: Samuel Williamson Date: Wed, 7 Oct 2026 23:43:59 -0400 Subject: [PATCH 3/4] added config.env.py changes --- config.env.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config.env.py b/config.env.py index 1142a2c8..34cde152 100644 --- a/config.env.py +++ b/config.env.py @@ -17,6 +17,7 @@ WEBHOOK_URL = env.get("CONDITIONAL_WEBHOOK_URL", "INSERT URL HERE") DEV_DISABLE_SLACK_PING = env.get("DEV_DISABLE_SLACK_PING", "false") == "true" PROFILING = env.get("CONDITIONAL_PROFILING", "false").lower() == "true" +SLACK_APP_TOKEN = env.get("CONDITIONAL_SLACK_APP_TOKEN", "INSERT TOKEN HERE") # DB Info SQLALCHEMY_DATABASE_URI = env.get("SQLALCHEMY_DATABASE_URI", "postgresql://conditional:fancypantspassword@conditional-postgres:5432/conditional") From 2a2785436c00193910cfde6b76f071d0c9a4d24e Mon Sep 17 00:00:00 2001 From: sywilliamson Date: Thu, 8 Oct 2026 15:25:48 -0400 Subject: [PATCH 4/4] Newline moment :( Co-authored-by: Noah Hanford --- conditional/util/slack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conditional/util/slack.py b/conditional/util/slack.py index 8fa280ec..bbe7eb14 100644 --- a/conditional/util/slack.py +++ b/conditional/util/slack.py @@ -60,4 +60,5 @@ def purge_frosh_usergroup(slack_uid): return purge_usergroup_users(frosh_usergroup_id, slack_uid) def purge_meetings_usergroup(slack_uid): - return purge_usergroup_users(meetings_usergroup_id, slack_uid) \ No newline at end of file + return purge_usergroup_users(meetings_usergroup_id, slack_uid) + \ No newline at end of file