From c911480b1faa2b2da6da0f963b13063e155eb2f0 Mon Sep 17 00:00:00 2001 From: Chongyoon Nah Date: Sat, 24 Oct 2020 14:32:05 -0400 Subject: [PATCH] add rate limiting for mentor requests --- .../endpoints/slack/actions/mentor_request.py | 33 +++++++++++++++++-- .../slack/message_templates/mentor_request.py | 28 ++++++++++++++-- pybot/plugins/airtable/api.py | 21 ++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/pybot/endpoints/slack/actions/mentor_request.py b/pybot/endpoints/slack/actions/mentor_request.py index de2e757..0e8bf49 100644 --- a/pybot/endpoints/slack/actions/mentor_request.py +++ b/pybot/endpoints/slack/actions/mentor_request.py @@ -1,5 +1,6 @@ import json import logging +from datetime import date, timedelta from sirbot import SirBot from slack import methods @@ -27,13 +28,41 @@ async def mentor_request_submit(action: Action, app: SirBot): username = action["user"]["name"] user_info = await slack.query(methods.USERS_INFO, {"user": action["user"]["id"]}) email = user_info["user"]["profile"]["email"] + remaining_requests = 2 + request_cycle_start_date = date.today().isoformat() + + recent_requests = await airtable.find_recent_requests("Mentor Request", "Email", email) + if len(recent_requests) != 0: + """ + filter the recent requests by the value of most recent request cycle start date + if the user has already made 3 requests in the last 31 days, show them an error message + Otherwise, proceed to submit request + """ + request_cycle_start_date = recent_requests[0]["fields"]["start date"] + filtered_requests = [ + request + for request in recent_requests + if request["fields"]["start date"] == request_cycle_start_date + ] + remaining_requests -= len(filtered_requests) + if len(filtered_requests) == 3: + new_request_cycle_start_date = date.fromisoformat(request_cycle_start_date) + timedelta(days=31) + await request.submit_request_rate_limit_exceeded_error( + new_request_cycle_start_date.isoformat(), slack + ) + return - airtable_response = await request.submit_request(username, email, airtable) + airtable_response = await request.submit_request( + username, email, request_cycle_start_date, airtable + ) if "error" in airtable_response: await request.submission_error(airtable_response, slack) else: - await request.submission_complete(slack) + request_cycle_end_date = date.fromisoformat(request_cycle_start_date) + timedelta(days=30) + await request.submission_complete( + remaining_requests, request_cycle_end_date.isoformat(), slack + ) async def mentor_details_submit(action: Action, app: SirBot): diff --git a/pybot/endpoints/slack/message_templates/mentor_request.py b/pybot/endpoints/slack/message_templates/mentor_request.py index 6506286..aa1d600 100644 --- a/pybot/endpoints/slack/message_templates/mentor_request.py +++ b/pybot/endpoints/slack/message_templates/mentor_request.py @@ -93,12 +93,15 @@ def add_errors(self) -> None: } self.attachments = [submit_attachment] - async def submit_request(self, username: str, email: str, airtable: AirtableAPI): + async def submit_request( + self, username: str, email: str, start_date: str, airtable: AirtableAPI + ): params = {"Slack User": username, "Email": email, "Status": "Available"} if self.skillsets: params["Skillsets"] = self.skillsets if self.details: params["Additional Details"] = self.details + params["start date"] = start_date service_records = await airtable.find_records("Services", "Name", self.service) params["Service"] = [service_records[0]["id"]] @@ -118,11 +121,30 @@ def submission_error( self.attachments = [error_attachment] return self.update_message(slack) - def submission_complete(self, slack: SlackAPI) -> Coroutine[Any, Any, dict]: + def submit_request_rate_limit_exceeded_error( + self, new_start_date: str, slack: SlackAPI + ) -> Coroutine[Any, Any, dict]: + error_attachment = { + "text": ( + f"You have exceeded mentor request limit.\n" + f"New request can be made starting on {new_start_date}" + ), + "color": "danger", + } + self.attachments = [error_attachment] + return self.update_message(slack) + + def submission_complete( + self, remaining_requests: int, current_end_date: str, slack: SlackAPI + ) -> Coroutine[Any, Any, dict]: done_block = { "type": "section", "block_id": "submission", - "text": {"type": "mrkdwn", "text": "Request Submitted Successfully!"}, + "text": { + "type": "mrkdwn", + "text": f"Request Submitted Successfully!\n" + f"You have now {remaining_requests} mentor requests remaining until {current_end_date}.", + }, "accessory": { "type": "button", "action_id": "cancel_btn", diff --git a/pybot/plugins/airtable/api.py b/pybot/plugins/airtable/api.py index 74db32a..93ad18b 100644 --- a/pybot/plugins/airtable/api.py +++ b/pybot/plugins/airtable/api.py @@ -136,6 +136,27 @@ async def find_records(self, table_name: str, field: str, value: str) -> list: ) return [] + async def find_recent_requests( + self, table_name: str, field: str, value: str + ) -> list: + url = self.table_url(table_name) + + params = { + "filterByFormula": f"AND(FIND(LOWER('{value}'), LOWER({{{field}}})), " + "{start date} != '', DATETIME_DIFF(NOW(), {start date}, 'days') <= 31)", + "maxRecords": 3, + "view": "Test Filter View", + } + + try: + response = await self.get(url, params=params) + return response["records"] + except Exception as ex: + logger.exception( + f"Exception when attempting to get {field} from {table_name}.", ex + ) + return [] + async def update_request(self, request_record, mentor_id): url = self.table_url("Mentor Request", request_record) data = {"fields": {"Mentor Assigned": [mentor_id] if mentor_id else None}}