diff --git a/app.py b/app.py index ec0c1ee..596f237 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,7 @@ from starlette.requests import Request from starlette.responses import JSONResponse -from isabelle.endpoints import HomeEndpoint +from isabelle.endpoints import HomeEndpoint,rsvp_endpoint from isabelle.piccolo_app import APP_CONFIG from isabelle.tables import Event from slack_bolt.adapter.starlette.async_handler import AsyncSlackRequestHandler @@ -71,7 +71,11 @@ async def lifespan(app: Starlette): async def endpoint(req: Request): - return await app_handler.handle(req) + try: + return await app_handler.handle(req) + except Exception as e: + logging.error(f"Error handling Slack request: {e}") + return JSONResponse({"error": str(e)},status_code=500) api = Starlette( routes=[ @@ -86,7 +90,8 @@ async def endpoint(req: Request): Mount("/static/", StaticFiles(directory="static")), Mount("/events/", PiccoloCRUD(table=Event,read_only=True,page_size=1000)), Route("/slack/events",endpoint=endpoint,methods=["POST"]), - Route("/health",endpoint=health,methods=["GET"]) + Route("/health",endpoint=health,methods=["GET"]), + Route("/api/rsvp",endpoint=rsvp_endpoint,methods=["POST"]) ], lifespan=lifespan, ) diff --git a/isabelle/endpoints.py b/isabelle/endpoints.py index 9842835..b540bf8 100644 --- a/isabelle/endpoints.py +++ b/isabelle/endpoints.py @@ -2,7 +2,11 @@ from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse - +import json +import logging +from isabelle.utils.env import env +from starlette.responses import JSONResponse +from starlette.requests import Request class HomeEndpoint(HTTPEndpoint): @@ -10,3 +14,36 @@ async def get(self, request): return PlainTextResponse("Hello! Isabelle (REST API) here. https://hack.club/gh/isabelle") + +async def rsvp_endpoint(request: Request): + if request.method !="POST": + return JSONResponse({"error": "Method not allowed"},status_code=405) + try: + body = await request.json() + except json.JSONDecodeError: + return JSONResponse({"error": "Invalid JSON"},status_code=400) + + event_id = body.get("event_id") + user_id = body.get("user_id") + + if not event_id: + return JSONResponse({"error":"Missing event_id"},status_code=400) + if not user_id: + return JSONResponse({"error": "Internal server error"},status_code=400) + try: + event = await env.database.toggle_user_interest(event_id,user_id) + except Exception as e: + logging.error(f"Error toggling RSVP: {e}") + return JSONResponse({"error": "Internal server error"},status_code=500) + if not event: + return JSONResponse({"error": "Event not found"},status_code=404) + + interested_users = list(event.InterestedUsers or []) + is_interested = user_id in interested_users + + return JSONResponse({ + "event_id": str(event.id), + "user_id": user_id, + "is_interested": is_interested, + "interest_count": event.InterestCount, + }) diff --git a/isabelle/events/reaction_added.py b/isabelle/events/reaction_added.py index fc7f498..fecdddc 100644 --- a/isabelle/events/reaction_added.py +++ b/isabelle/events/reaction_added.py @@ -37,7 +37,7 @@ async def handle_reaction_added(body, client: AsyncWebClient): text='Error RSVPing to the event. :(' ) return - if str(body["event"]["user"]) not in event.get("InterestedUsers", []): + if str(body["event"]["user"]) not in event.get("InterestedUsers",[]): try: await client.chat_postEphemeral( channel=body["event"]["item"]["channel"], diff --git a/isabelle/events/reaction_removed.py b/isabelle/events/reaction_removed.py index 6105ff1..1eb5000 100644 --- a/isabelle/events/reaction_removed.py +++ b/isabelle/events/reaction_removed.py @@ -37,7 +37,7 @@ async def handle_reaction_removed(body, client: AsyncWebClient): text='Error disabling RSVPing to the event. :(' ) return - if str(body["event"]["user"]) not in event.get("InterestedUsers", []): + if str(body["event"]["user"]) not in event.get("InterestedUsers",[]): try: await client.chat_postEphemeral( channel=body["event"]["item"]["channel"], diff --git a/isabelle/events/views/create_event.py b/isabelle/events/views/create_event.py index 0b6f5ad..896912e 100644 --- a/isabelle/events/views/create_event.py +++ b/isabelle/events/views/create_event.py @@ -13,14 +13,13 @@ from slack_gfm import rich_text_to_gfm from isabelle.views.app_home import get_home -from datetime import datetime async def handle_create_event_view(ack: Callable, body: dict[str, Any], client: AsyncWebClient): await ack() view = body["view"] values = view["state"]["values"] - title = (values["title"]["title"]["value"],) + title = (values["title"]["title"]["value"]) description = values["description"]["description"]["rich_text_value"]["elements"] md = rich_text_to_md(description) start_time = datetime.fromtimestamp(values["start_time"]["start_time"]["selected_date_time"]) @@ -60,7 +59,7 @@ async def handle_create_event_view(ack: Callable, body: dict[str, Any], client: await client.chat_postEphemeral( user=body["user"]["id"], channel=body["user"]["id"], - text=f'An error occurred whilst creating the event "{title[0]}".', + text=(f'Could not create "{title[0]}\n' f'An event with the same title and start time may already exist\n' f'Please check exiting events or edit them instead') ) return diff --git a/isabelle/events/views/rsvp_msg_set_response.py b/isabelle/events/views/rsvp_msg_set_response.py index 73fc402..610450e 100644 --- a/isabelle/events/views/rsvp_msg_set_response.py +++ b/isabelle/events/views/rsvp_msg_set_response.py @@ -9,14 +9,15 @@ async def handle_rsvp_msg_set_response(ack: callable, body, view, client: AsyncW emoji_name = extract_emoji_name(view) chosen_event_id = view["state"]["values"]["chosen_event"]["event_select"]["selected_option"]["value"] (message_ts, channel_id) = tuple(view["private_metadata"].split("-")) - try: - await client.reactions_add( - channel=channel_id, - timestamp=message_ts, - name=emoji_name - ) - except Exception: - logging.warning("Error adding reaction in handle_rsvp_msg_set_response", exc_info=True) + if emoji_name: + try: + await client.reactions_add( + channel=channel_id, + timestamp=message_ts, + name=emoji_name + ) + except Exception: + logging.warning("Error adding reaction in handle_rsvp_msg_set_response", exc_info=True) ev = await env.database.set_rsvp_msg(chosen_event_id, message_ts, channel_id, emoji_name) @@ -61,12 +62,12 @@ async def rsvp_previous_reactions(client: AsyncWebClient, message_ts: str, chann if not res.get("ok"): return - reactions: list = res.get("message").get("reactions") # Holy ternary shenanigans. I'm so sorry for this, seems like the python way + reactions: list = res.get("message",{}).get("reactions") or [] reactions = [i for i in reactions if i.get("name") == reaction_name] if reaction_name else reactions - users = [user for reaction in reactions for user in reaction["users"]] + users = [user for reaction in reactions for user in reaction.get["users",[]]] users = set(users) diff --git a/isabelle/utils/database.py b/isabelle/utils/database.py index c0c3d62..21211f1 100644 --- a/isabelle/utils/database.py +++ b/isabelle/utils/database.py @@ -159,7 +159,7 @@ async def toggle_user_interest(self, event_id: str, user_slack_id: str, forced_s if set(interested_users) == unupdated_users_set: - return event + return await Event.select().where(Event.id == event_uuid).first() await Event.update( InterestedUsers=interested_users, diff --git a/isabelle/utils/env.py b/isabelle/utils/env.py index e6b29ee..b466b7c 100644 --- a/isabelle/utils/env.py +++ b/isabelle/utils/env.py @@ -26,6 +26,10 @@ def __init__(self): unset = [key for key, value in self.__dict__.items() if value == "unset"] + if self.environemnt == "development": + unset = [key for key in unset if key not in ("airtable_api_key", "airtable_base_id")] + + if unset: raise ValueError(f"Missing environment variables: {', '.join(unset)}") @@ -45,7 +49,7 @@ def __init__(self): "U06QST7V0J2", # Eesha "U097UCZE2BB", # Aishaani "U072PTA5BNG", # Victorio - "U09Q8MLTE58" # EPS + "U09Q8MLTE58", # EPS ] self.event_tags = [ diff --git a/isabelle/views/app_home.py b/isabelle/views/app_home.py index c8a7536..8edaa0a 100644 --- a/isabelle/views/app_home.py +++ b/isabelle/views/app_home.py @@ -137,7 +137,7 @@ async def get_home(user_id: str, client: AsyncWebClient): "type": "section", "text": { "type": "mrkdwn", - "text": f"{'*[UNAPPROVED]:* ' if not event["Approved"] else ''}*{event["Title"]}* - <@{event["LeaderSlackID"]}>\n{mrkdwn}\n*{formatted_time}*", + "text": f"{'*[UNAPPROVED]:* ' if not event["Approved"] else ''}*{event["Title"]}* - <@{event["LeaderSlackID"]}>\n{mrkdwn}\n*{formatted_time}*\nInterest: {event.get('InterestCount',0)}", }, "accessory": { "type": "image",