Skip to content

Commit

Permalink
Merge pull request #37 from LCOGT/dev
Browse files Browse the repository at this point in the history
Add endpoint for removing expired lco scheduler events
  • Loading branch information
timbeccue authored Nov 8, 2024
2 parents 56b949c + dee5a8d commit 1f96edb
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 3 deletions.
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

0 comments on commit 1f96edb

Please sign in to comment.