]> git.cworth.org Git - turbot/commitdiff
Refuse to create a new puzzle with the same URL as an existing puzzle
authorCarl Worth <cworth@cworth.org>
Fri, 1 Jan 2021 18:17:35 +0000 (10:17 -0800)
committerCarl Worth <cworth@cworth.org>
Fri, 1 Jan 2021 18:39:15 +0000 (10:39 -0800)
Just a simple safety check when multiple people might be creating the
same puzzle at roughly the same time.

And one less item left on the TODO list.

TODO
turbot/interaction.py
turbot/puzzle.py [new file with mode: 0644]

diff --git a/TODO b/TODO
index edbafc5489a9de38a7900716e07846a403339262..67a48304218ec5fd7b77616911e726fe3a5a3107 100644 (file)
--- a/TODO
+++ b/TODO
@@ -9,9 +9,6 @@ Low-hanging fruit
 [These are not highest priority, but are so easy that they are worth
 not delaying until after bigger features below.]
 
-• Fix /puzzle dialog to reject a puzzle with the same URL as an
-  existing puzzle.
-
 • Make /solved clear the state string for a puzzle.
 
 Round management
index 76eb061d024a29475bc01a368ab67abd3b3e162f..f2a35ee13066a8d8cdafc5c66d8f8c6db72ea134 100644 (file)
@@ -3,6 +3,7 @@ from turbot.blocks import (
     input_block, section_block, text_block, multi_select_block
 )
 from turbot.hunt import find_hunt_for_hunt_id
+from turbot.puzzle import find_puzzle_for_url
 import turbot.rot
 import turbot.sheets
 import turbot.slack
@@ -119,13 +120,14 @@ def new_hunt_submission(turb, payload, metadata):
             TableName='turbot',
             KeySchema=[
                 {'AttributeName': 'hunt_id', 'KeyType': 'HASH'},
-                {'AttributeName': 'SK', 'KeyType': 'RANGE'},
+                {'AttributeName': 'SK', 'KeyType': 'RANGE'}
             ],
             AttributeDefinitions=[
                 {'AttributeName': 'hunt_id', 'AttributeType': 'S'},
                 {'AttributeName': 'SK', 'AttributeType': 'S'},
                 {'AttributeName': 'channel_id', 'AttributeType': 'S'},
                 {'AttributeName': 'is_hunt', 'AttributeType': 'S'},
+                {'AttributeName': 'url', 'AttributeType': 'S'}
             ],
             ProvisionedThroughput={
                 'ReadCapacityUnits': 5,
@@ -158,6 +160,18 @@ def new_hunt_submission(turb, payload, metadata):
                         'WriteCapacityUnits': 5
                     }
                 }
+            ],
+            LocalSecondaryIndexes = [
+                {
+                    'IndexName': 'url_index',
+                    'KeySchema': [
+                        {'AttributeName': 'hunt_id', 'KeyType': 'HASH'},
+                        {'AttributeName': 'url', 'KeyType': 'RANGE'},
+                    ],
+                    'Projection': {
+                        'ProjectionType': 'ALL'
+                    }
+                }
             ]
         )
         return submission_error(
@@ -409,6 +423,7 @@ def puzzle_submission(turb, payload, metadata):
     This is the modal view presented to the user by the puzzle function
     above."""
 
+    # First, read all the various data from the request
     meta = json.loads(metadata)
     hunt_id = meta['hunt_id']
 
@@ -422,6 +437,15 @@ def puzzle_submission(turb, payload, metadata):
         rounds = []
     new_rounds = state['new_rounds']['new_rounds']['value']
 
+    # Before doing anything, reject this puzzle if a puzzle already
+    # exists with the same URL.
+    if url:
+        existing = find_puzzle_for_url(turb, hunt_id, url)
+        if existing:
+            return submission_error(
+                "url",
+                "Error: A puzzle with this URL already exists.")
+
     # Create a Slack-channel-safe puzzle_id
     puzzle_id = re.sub(r'[^a-zA-Z0-9_]', '', name).lower()
 
diff --git a/turbot/puzzle.py b/turbot/puzzle.py
new file mode 100644 (file)
index 0000000..9f9b1c6
--- /dev/null
@@ -0,0 +1,22 @@
+from boto3.dynamodb.conditions import Key
+
+def find_puzzle_for_url(turb, hunt_id, url):
+    """Given a hunt_id and URL, return the puzzle with that URL
+
+    Returns None if no puzzle with the given URL exists in the database,
+    otherwise a dictionary with all fields from the puzzle's row in
+    the database.
+    """
+
+    response = turb.table.query(
+        IndexName='url_index',
+        KeyConditionExpression=(
+            Key('hunt_id').eq(hunt_id) &
+            Key('url').eq(url)
+        )
+    )
+
+    if response['Count'] == 0:
+        return None
+
+    return response['Items'][0]