Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add endpoint for removing expired lco scheduler events #37

Merged
merged 8 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Deploy Script
on:
push:
branches: [main, dev]
paths-ignore:
- 'README.md'

jobs:
deploy:
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,14 @@ The body of a calendar event follows the JSON format below:
```javascript
{
"event_id": "024242f...", // Unique ID generated for each new reservation
"start": "2022-06-20T06:15:00Z", // Starting UTC date and time of reservation
"end": "2022-06-20T06:45:00Z", // Ending UTC date and time of reservation
"start": "2022-06-20T16:15:00Z", // Starting UTC date and time of reservation
"end": "2022-06-20T16:45:00Z", // Ending UTC date and time of reservation
"creator": "Firstname Lastname", // String of user display name
"creator_id": "google-oauth2|xxxxxxxxxxxxx", // Auth0 user 'sub' string
"site": "saf", // Sitecode where reservation was made
"title": "My Name", // Name of reservation, defaults to username
"reservation_type": "realtime", // String, can be "realtime" or "project"
"origin": "ptr", // "ptr" if created on the ptr site, or "lco" if the event was created by the lco scheduler
"resourceId": "saf", // Sitecode where reservation was made
"project_id": "none", // Or concatenated string of project_name#created_at timestamp
"reservation_note": "", // User-supplied comment string, can be empty
Expand All @@ -94,6 +95,8 @@ The body of a calendar event follows the JSON format below:

Calendar requests are handled at the base URL `https://calendar.photonranch.org/{stage}`, where `{stage}` is `dev` for the dev environment, or `calendar` for the production version.

All datetimes should be formatted yyyy-MM-ddTHH:mmZ (UTC, 24-hour format)

- POST `/newevent`
- Description: Create a new reservation on the calendar.
- Authorization required: Yes.
Expand Down Expand Up @@ -127,6 +130,17 @@ Calendar requests are handled at the base URL `https://calendar.photonranch.org/
- Responses:
- 200: success.

- POST `/remove-expired-lco-schedule`
- Description: Removes all events at a given site under the following conditions:
- the event `origin` == "lco"
- the event `start` is after (greater than) the specified cutoff_time
- Authorization required: No.
- Request body:
- `site` (string): dictionaries for each calendar event to update
- `cutoff_time` (string): UTC datestring, which is compared against the `start` attribute
- Responses:
- 200: success.

- POST `/delete`
- Description: Delete a calendar event given an event_id.
- Authorization required: Yes.
Expand Down
74 changes: 74 additions & 0 deletions handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,64 @@ def getProject(project_name, created_at):
return "Project not found."


def remove_expired_scheduler_events(cutoff_time, site):
""" Method for deleting calendar events created in response to the LCO scheduler.

This method takes a site and a cutoff time, and deletes all events that satisfy the following conditions:
- the event belongs to the given site
- the event starts after the cutoff_time (specifically, the event start is greater than the cutoff_time)
- the event origin is 'lco'
It returns an array of project IDs that were associated with the deleted events so that they can be deleted as well.

Args:
cutoff_time (str):
Formatted yyyy-MM-ddTHH:mmZ (UTC, 24-hour format)
Any events that start before this time are not deleted.
site (str):
Only delete events from the given site (e.g. 'mrc')

Returns:
(array of str) project IDs for any projects that were connected to deleted events.
"""
table = dynamodb.Table(calendar_table_name)
index_name = "site-end-index"

# Query items from the secondary index with 'site' as the partition key and 'end' greater than the specified end_date
# We're using 'end' time for the query because it's part of a pre-existing GSI that allows for efficient queries.
# But ultimately we want this to apply to events that start after the cutoff, so add that as a filter condition too.
query = table.query(
IndexName=index_name,
KeyConditionExpression=Key('site').eq(site) & Key('end').gt(cutoff_time),
FilterExpression=Attr('origin').eq('lco') & Attr('start').gt(cutoff_time)
)
items = query.get('Items', [])

# Extract key attributes for deletion (use the primary key attributes, not the index keys)
key_names = [k['AttributeName'] for k in table.key_schema]

with table.batch_writer() as batch:
for item in items:
batch.delete_item(Key={k: item[k] for k in key_names if k in item})

# Handle pagination if results exceed 1MB
while 'LastEvaluatedKey' in query:
query = table.query(
IndexName=index_name,
KeyConditionExpression=Key('site').eq(site) & Key('end').gt(cutoff_time),
FilterExpression=Attr('origin').eq('lco') & Attr('start').gt(cutoff_time),
ExclusiveStartKey=query['LastEvaluatedKey']
)
items = query.get('Items', [])

with table.batch_writer() as batch:
for item in items:
batch.delete_item(Key={k: item[k] for k in key_names if k in item})

associated_projects = [x["project_id"] for x in items]
return associated_projects



#=========================================#
#======= API Endpoints ========#
#=========================================#
Expand Down Expand Up @@ -392,6 +450,22 @@ def deleteEventById(event, context):
print(f"success deleting event, message: {message}")
return create_response(200, message)

def clearExpiredSchedule(event, context):
"""Endpoint to delete calendar events with an event_id.

Args:
event.body.site (str):
sitecode for the site we are dealing with
event.body.time (str):
UTC datestring (eg. '2022-05-14T17:30:00Z'). All events that start after this will be removed.

Returns:
200 status code, with list of projects that were associated with the deleted events
"""
event_body = json.loads(event.get("body", ""))
associated_projects = remove_expired_scheduler_events(event_body["cutoff_time"], event_body["site"])
return create_response(200, json.dumps(associated_projects))


def getSiteEventsInDateRange(event, context):
"""Return calendar events within a specified date range at a given site.
Expand Down
10 changes: 9 additions & 1 deletion serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ provider:
- "dynamodb:DeleteItem"
- "dynamodb:Scan"
- "dynamodb:Query"
- "dynamodb:DescribeTable"
- "dynamodb:BatchWriteItem"
Resource:
- "arn:aws:dynamodb:${self:provider.region}:*:table/${self:custom.calendarTableName}*"

Expand Down Expand Up @@ -265,7 +267,13 @@ functions:
- X-Amz-User-Agent
- Access-Control-Allow-Origin
- Access-Control-Allow-Credentials

clearExpiredSchedule:
handler: handler.clearExpiredSchedule
events:
- http:
path: remove-expired-lco-schedule
method: post
cors: true
getSiteEventsInDateRange:
handler: handler.getSiteEventsInDateRange
events:
Expand Down
Loading