diff --git a/.gitignore b/.gitignore index 992143cb4..5c1a2eddc 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ tests/cypress/videos tests/cypress/screenshots tests/cypress/fixtures/packageDashboardSPAIDs.json tests/cypress/fixtures/packageDashboardWaiverNumbers.json -tests/cypress/fixtures/savedID.json \ No newline at end of file +tests/cypress/fixtures/savedID.json +/services/layers/aws-sdk-v2-layer/.serverless diff --git a/deploy.sh b/deploy.sh index 9b7f24e44..9d4dff33a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -3,6 +3,15 @@ stage=${1:-dev} install_deps() { + local islayer=false # Flag to track whether we navigated into the nodejs folder + + # Check if we're inside a layer directory (layers have a 'nodejs' folder) + if [ -d "nodejs" ]; then + # If we're in a Lambda layer, navigate to the 'nodejs' folder + cd nodejs + islayer=true # Set the flag to true since we navigated into nodejs + fi + if [ "$CI" == "true" ]; then # If we're in a CI system if [ ! -d "node_modules" ]; then # If we don't have any node_modules (CircleCI cache miss scenario), run npm ci. Otherwise, we're all set, do nothing. npm ci --legacy-peer-deps @@ -10,6 +19,11 @@ install_deps() { else # We're not in a CI system, let's npm install npm install --legacy-peer-deps fi + + # If we navigated to the nodejs folder (i.e., for a layer), go back to the root folder + if [ "$islayer" = true ]; then + cd .. + fi } deploy() { @@ -25,6 +39,7 @@ install_deps services=( 'ui' 'uploads' + 'layers/aws-sdk-v2-layer' 'app-api' 'email' 'one-stream' diff --git a/services/admin/package.json b/services/admin/package.json index e31500319..73af5d181 100644 --- a/services/admin/package.json +++ b/services/admin/package.json @@ -4,13 +4,13 @@ "version": "1.0.0", "devDependencies": { "aws-sdk-client-mock": "^0.5.6", + "aws-sdk": "^2.752.0", "esbuild": "^0.19.4", "serverless-dotenv-plugin": "^3.12.2", "serverless-esbuild": "^1.48.4", "serverless-offline": "^13.5.0" }, "dependencies": { - "aws-sdk": "^2.752.0", "@aws-sdk/client-dynamodb": "^3.43.0", "@aws-sdk/lib-dynamodb": "^3.454.0", "cmscommonlib": "file:../common", diff --git a/services/admin/scripts/READ.md b/services/admin/scripts/READ.md new file mode 100644 index 000000000..a95966c49 --- /dev/null +++ b/services/admin/scripts/READ.md @@ -0,0 +1,7 @@ +This /scripts Folder simply contains scripts that are not used in the application or deployed via lambda but are useful for admin type functions. + +##### + +generate_packages - creates new records in Under Review status by inserting both a OneMAC record and a SEATool record +rebuildPackagesbyPK - force a rebuild of the package record for all packages listed in pk_values.json +setPOCs - set the action_officers (srt) and lead_analyst (cpoc) for all packages listed in pk_values.json diff --git a/services/admin/scripts/createUsers.sh b/services/admin/scripts/createUsers.sh new file mode 100644 index 000000000..5afaf265c --- /dev/null +++ b/services/admin/scripts/createUsers.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Variables +USER_POOL_ID="us-east-1_9NXuk7ucb" +INPUT_FILE="users.json" +DYNAMODB_TABLE="onemac-masterclone-one" +DATE=$(date +%s%3N) # Current timestamp in milliseconds + +# Read the JSON file and loop through each user +jq -c '.users[]' $INPUT_FILE | while read -r user; do + PASSWORD=$(echo $user | jq -r '.password') + EMAIL=$(echo $user | jq -r '.email') + FAMILY_NAME=$(echo $user | jq -r '.family_name') + GIVEN_NAME=$(echo $user | jq -r '.given_name') + ROLE=$(echo $user | jq -r '.role') + + # Concatenate given name and family name to form full name + FULL_NAME="$GIVEN_NAME $FAMILY_NAME" + + # Determine territory and GSI fields based on role + if [ "$ROLE" = "systemadmin" ]; then + TERRITORY="N/A" + GSI1SK="Boss" + GSI1PK="systemadmin#N/A" + else + TERRITORY=$(echo $user | jq -r '.territory') + GSI1SK="statesystemadmin#$TERRITORY" + GSI1PK="USER" + fi + + echo "Creating user with email: $EMAIL" + + # Use the email as the username in Cognito + aws cognito-idp admin-create-user \ + --user-pool-id "$USER_POOL_ID" \ + --username "$EMAIL" \ + --user-attributes Name="email",Value="$EMAIL" \ + Name="family_name",Value="$FAMILY_NAME" \ + Name="given_name",Value="$GIVEN_NAME" \ + Name="email_verified",Value="true" \ + --message-action SUPPRESS + + # Set the password and mark the user as confirmed + aws cognito-idp admin-set-user-password \ + --user-pool-id "$USER_POOL_ID" \ + --username "$EMAIL" \ + --password "$PASSWORD" \ + --permanent + + echo "User created with email (used as username): $EMAIL" + + # Insert ContactInfo record into DynamoDB + aws dynamodb put-item --table-name "$DYNAMODB_TABLE" --item \ + '{"pk": {"S": "'"$EMAIL"'"}, + "sk": {"S": "ContactInfo"}, + "email": {"S": "'"$EMAIL"'"}, + "fullName": {"S": "'"$FULL_NAME"'"}, + "GSI1pk": {"S": "'"$GSI1PK"'"}, + "GSI1sk": {"S": "'"$EMAIL"'"} }' + + echo "ContactInfo record created for user: $EMAIL" + + # Insert Roles record into DynamoDB + aws dynamodb put-item --table-name "$DYNAMODB_TABLE" --item \ + '{"pk": {"S": "'"$EMAIL"'"}, + "sk": {"S": "v0#'"$ROLE"'#'"$TERRITORY"'"}, + "date": {"N": "'"$DATE"'"}, + "doneByEmail": {"S": "'"$EMAIL"'"}, + "doneByName": {"S": "'"$FULL_NAME"'"}, + "email": {"S": "'"$EMAIL"'"}, + "fullName": {"S": "'"$FULL_NAME"'"}, + "GSI1pk": {"S": "'"$GSI1PK"'"}, + "GSI1sk": {"S": "'"$GSI1SK"'"}, + "GSI2pk": {"S": "'"$ROLE"'#'"$TERRITORY"'"}, + "GSI2sk": {"S": "active"}, + "Latest": {"S": "1"}, + "role": {"S": "'"$ROLE"'"}, + "status": {"S": "active"}, + "territory": {"S": "'"$TERRITORY"'"} }' + + echo "Roles record created for user: $EMAIL" +done \ No newline at end of file diff --git a/services/admin/scripts/delete_packages.sh b/services/admin/scripts/delete_packages.sh new file mode 100644 index 000000000..f893ec0fa --- /dev/null +++ b/services/admin/scripts/delete_packages.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Variables +DYNAMODB_TABLE="onemac-masterclone-one" +PK_VALUES=("NY-24-9356" "NY-24-6035" "NY-24-3865" "NY-24-1282" "NY-24-1036" "NY-24-9253" "NY-24-9972" "NY-24-1717" "NY-24-5342" "NY-24-8523" "NY-24-1571" "NY-24-7203" "NY-80815.R01.02" "NY-66338.R01.02" "NY-84734.R01.00" "NY-23214.R01.02" "NY-94872.R00.00" "NY-93400.R00.00" "NY-72598.R01.02" "NY-84074.R01.00" "NY-49584.R00.01" "NY-52227.R00.00" "NY-27845.R01.00" "NY-95809.R00.01" "NY-53801.R01.02" "NY-38783.R00.01" "NY-75550.R00.01" "NY-92181.R01.00" "NY-18445.R00.00" "NY-57782.R01.00" "NY-52513.R00.00" "NY-99845.R00.01" "NY-76823.R00.01" "NY-42146.R01.02" "NY-52725.R00.00" "NY-14938.R01.00") + +# Counter for deleted items +DELETED_COUNT=0 + +# Loop through each pk value +for PK in "${PK_VALUES[@]}"; do + # Query DynamoDB for items with the specified pk + ITEMS=$(aws dynamodb query \ + --table-name "$DYNAMODB_TABLE" \ + --key-condition-expression "pk = :pk" \ + --expression-attribute-values '{":pk": {"S": "'"$PK"'"}}' \ + --projection-expression "pk, sk" \ + --output json | jq -c '.Items[]') + + # Loop through the found items and delete them + echo "$ITEMS" | while read -r item; do + pk=$(echo "$item" | jq -r '.pk.S') + sk=$(echo "$item" | jq -r '.sk.S') + + # Skip if pk or sk is empty + if [[ -z "$pk" || -z "$sk" ]]; then + continue + fi + + # Delete the record + aws dynamodb delete-item \ + --table-name "$DYNAMODB_TABLE" \ + --key "{\"pk\": {\"S\": \"$pk\"}, \"sk\": {\"S\": \"$sk\"}}" + + echo "Deleted record with pk: $pk and sk: $sk" + ((DELETED_COUNT++)) + done +done + +# Output the total number of deleted items +echo "Total number of deleted items: $DELETED_COUNT" \ No newline at end of file diff --git a/services/admin/scripts/generate_packages.py b/services/admin/scripts/generate_packages.py new file mode 100644 index 000000000..2ff6b1fe4 --- /dev/null +++ b/services/admin/scripts/generate_packages.py @@ -0,0 +1,416 @@ +import boto3 +import random +import time +from datetime import datetime, timedelta +import os +from botocore.exceptions import ClientError + +# Initialize DynamoDB client +dynamodb = boto3.resource('dynamodb') + +# Get table name from environment variable or use default +table_name = os.getenv('TABLE_NAME', 'onemac-masterclone-one') +table = dynamodb.Table(table_name) + +# Define different component configurations +component_config = { + "medicaidspa": { + "componentType": "medicaidspa", + "GSI1pk": "OneMAC#submitmedicaidspa", + "GSI1sk_prefix": "OneMAC#", + "second_GSI1pk": "SEATool#Medicaid_SPA", + "pk_format": "{state_code}-{current_year}-{random_4_digits}" + }, + "chipspa": { + "componentType": "chipspa", + "GSI1pk": "OneMAC#submitchipspa", + "GSI1sk_prefix": "OneMAC#", + "second_GSI1pk": "SEATool#CHIP_SPA", + "pk_format": "{state_code}-{current_year}-{random_4_digits}" + }, + "waivernew": { + "componentType": "waivernew", + "GSI1pk": "OneMAC#submitwaivernew", + "GSI1sk_prefix": "OneMAC#", + "second_GSI1pk": "SEATool#1915b_waivers", + "pk_format": "{state_code}-{random_5_digits}.R00.00" + }, + "waiverappk": { + "componentType": "waiverappk", + "GSI1pk": "OneMAC#submitwaiverappk", + "GSI1sk_prefix": "OneMAC#", + "second_GSI1pk": "SEATool#1915c_waivers", + "pk_format": "{state_code}-{random_5_digits}.R00.01" + }, + "waiveramendment": { + "componentType": "waiveramendment", + "GSI1pk": "OneMAC#submitwaiveramendment", + "GSI1sk_prefix": "OneMAC#", + "second_GSI1pk": "SEATool#1915b_waivers", + "pk_format": "{state_code}-{random_5_digits}.R01.02" + }, + "waiverrenewal": { + "componentType": "waiverrenewal", + "GSI1pk": "OneMAC#submitwaiverrenewal", + "GSI1sk_prefix": "OneMAC#", + "second_GSI1pk": "SEATool#1915b_waivers", + "pk_format": "{state_code}-{random_5_digits}.R01.00" + } +} + +# Function to generate random ID and timestamps, ensuring no duplicate IDs +generated_ids = set() + +def generate_ids_and_timestamps(state_code, component_type): + config = component_config[component_type] + current_year = datetime.now().year % 100 # last 2 digits of the year + while True: + pk_format = config['pk_format'] + # Replace placeholders with actual values + random_id = pk_format.format( + state_code=state_code, + current_year=current_year, + random_4_digits=random.randint(1000, 9999), + random_5_digits=random.randint(10000, 99999) + ) + + if random_id not in generated_ids: + generated_ids.add(random_id) + break + + # Generate two timestamps within the last 90 days + now = int(time.time() * 1000) + start_time = now - (random.randint(0, 90) * 24 * 3600 * 1000) + end_time = start_time + random.randint(1, 600000) # Ensure second timestamp is slightly later + return random_id, start_time, end_time + +# default LEAD_ANALYST and ACTION_OFFICERS +default_lead_analyst = [ + { + "EMAIL": "onemaccpoc6@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "CPOC06", + "OFFICER_ID": 3743, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } +] + +default_action_officers = [ + { + "EMAIL": "onemacsrt52@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "SRT52", + "OFFICER_ID": 3740, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } +] + +# Define different action officers and lead analysts per state, using the original defaults for now +action_officers_per_state = { + "MD": [ + { + "EMAIL": "onemacsrt52@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "SRT52", + "OFFICER_ID": 3740, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "NY": [ + { + "EMAIL": "onemacsrt5@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "SRT5", + "OFFICER_ID": 3740, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "CA": [ + { + "EMAIL": "onemacsrt2@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "SRT2", + "OFFICER_ID": 3740, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "OH": [ + { + "EMAIL": "onemacsrt3@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "SRT3", + "OFFICER_ID": 3740, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "WI": [ + { + "EMAIL": "onemacsrt4@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "SRT4", + "OFFICER_ID": 3740, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ] +} + +lead_analyst_per_state = { + "MD": [ + { + "EMAIL": "onemaccpoc6@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "CPOC06", + "OFFICER_ID": 3743, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "NY": [ + { + "EMAIL": "onemaccpoc50@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "CPOC5", + "OFFICER_ID": 3743, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "CA": [ + { + "EMAIL": "onemaccpoc9@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "CPOC02", + "OFFICER_ID": 3743, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "OH": [ + { + "EMAIL": "onemaccpoc319@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "CPOC3", + "OFFICER_ID": 3743, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ], + "WI": [ + { + "EMAIL": "onemaccpoc49@gmail.com", + "FIRST_NAME": "OneMAC", + "LAST_NAME": "CPOC4", + "OFFICER_ID": 3743, + "POSITION_ID": 538, + "TELEPHONE": "(410) 555-5445" + } + ] +} + +# Function to generate onemace submission record based on component type +def generate_onemac_submission_record(state, random_id, first_timestamp, component_type): + config = component_config[component_type] + if component_type == "waiverappk": + attachments = [ + { + "contentType": "text/csv", + "filename": "Report_UserList(1).csv", + "s3Key": f"{first_timestamp}/Report_UserList(1).csv", + "title": "1915(c) Appendix K Amendment Waiver Template", + "url": f"https://uploads-master-attachments-989324938326.s3.us-east-1.amazonaws.com/protected/us-east-1%3A30413432-e223-4a6d-bfe1-7ed87236ff55/{first_timestamp}/Report_UserList(1).csv" + }, + { + "contentType": "text/csv", + "filename": "Report_spaList(5).csv", + "s3Key": f"{first_timestamp}/Report_spaList(5).csv", + "title": "1915(c) Appendix K Amendment Waiver Template", + "url": f"https://uploads-master-attachments-989324938326.s3.us-east-1.amazonaws.com/protected/us-east-1%3A30413432-e223-4a6d-bfe1-7ed87236ff55/{first_timestamp}/Report_spaList(5).csv" + }, + { + "contentType": "text/csv", + "filename": "Report_waiverList(8).csv", + "s3Key": f"{first_timestamp}/Report_waiverList(8).csv", + "title": "Other", + "url": f"https://uploads-master-attachments-989324938326.s3.us-east-1.amazonaws.com/protected/us-east-1%3A30413432-e223-4a6d-bfe1-7ed87236ff55/{first_timestamp}/Report_waiverList(8).csv" + } + ] + return { + "pk": random_id, + "sk": f"{config['GSI1sk_prefix']}{first_timestamp}", + "additionalInformation": "created new APP k", + "attachments": attachments, + "clockEndTimestamp": first_timestamp + 77777777, + "componentId": random_id, + "componentType": config["componentType"], + "currentStatus": "Submitted", + "eventTimestamp": first_timestamp, + "GSI1pk": config["GSI1pk"], + "GSI1sk": random_id, + "proposedEffectiveDate": "2023-01-20", + "submissionTimestamp": first_timestamp, + "submitterEmail": "mdstateonemac@gmail.com", + "submitterName": "MDSTATE SUBMITTERNK", + "territory": state, + "title": "New Title for App K", + "transmittalNumberWarningMessage": "", + "waiverAuthority": "1915(c)" + } + else: + return { + "pk": random_id, + "sk": f"{config['GSI1sk_prefix']}{first_timestamp}", + "additionalInformation": "test", + "attachments": [ + { + "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "filename": "file.docx", + "s3Key": f"{first_timestamp}/file.docx", + "title": "1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print", + "url": f"https://uploads-develop-attachments-116229642442.s3.us-east-1.amazonaws.com/protected/us-east-1%3A86a190fe-b195-42bf-9685-9761bf0ff14b/{first_timestamp}/file.docx" + } + ], + "clockEndTimestamp": first_timestamp + 77777777, + "componentId": random_id, + "componentType": config["componentType"], + "currentStatus": "Submitted", + "eventTimestamp": first_timestamp, + "GSI1pk": config["GSI1pk"], + "GSI1sk": random_id, + "proposedEffectiveDate": "2024-06-27", + "submissionTimestamp": first_timestamp, + "submitterEmail": "statesubmitter@nightwatch.test", + "submitterName": "Statesubmitter Nightwatch", + "territory": state, + "transmittalNumberWarningMessage": "", + "waiverAuthority": "1915(b)(4)" + } + +# Function to generate second record based on component type +def generate_seatool_pending_record(state, random_id, second_timestamp, component_type): + config = component_config[component_type] + + # Use state-specific action officers and lead analysts + action_officers = action_officers_per_state.get(state, default_action_officers) + lead_analyst = lead_analyst_per_state.get(state, default_lead_analyst) + + # Base common record structure + seatool_record = { + "pk": random_id, + "sk": f"SEATool#{second_timestamp}", + "GSI1pk": config["second_GSI1pk"], + "GSI1sk": random_id, + "LEAD_ANALYST": lead_analyst, + "ACTION_OFFICERS": action_officers, + "OCD_REVIEW": [ + { + "OCD_REVIEW_DESCRIPTION": "No", + "OCD_REVIEW_ID": 2 + } + ], + "SPW_STATUS": [ + { + "SPW_STATUS_DESC": "Pending", + "SPW_STATUS_ID": 1 + } + ], + "STATE_PLAN": { + "ID_NUMBER": random_id, + "SPW_STATUS_ID": 1, # Ensures SPW_STATUS_ID is always included + "LEAD_ANALYST_ID": lead_analyst[0]["OFFICER_ID"], # Using the first lead analyst's ID + "STATE_CODE": state, + "SUMMARY_MEMO": "This is just a test" if component_type != "waiverappk" else "Sample Summary Memo", + "UUID": "66908FBA-9AFC-41BD-BA77-AA74379E6F44" if component_type != "waiverappk" else None + } + } + + # Add or modify specific fields based on component_type + if component_type == "waiverappk": + seatool_record["ACTIONTYPES"] = [ + { + "ACTION_ID": 76, + "ACTION_NAME": "Amend", + "PLAN_TYPE_ID": 123 + } + ] + seatool_record["PLAN_TYPES"] = [ + { + "PLAN_TYPE_ID": 123, + "PLAN_TYPE_NAME": "1915(c)" + } + ] + seatool_record["STATE_PLAN"]["ACTION_TYPE"] = 76 + seatool_record["STATE_PLAN"]["ALERT_90_DAYS_DATE"] = 1681862400000 + else: + seatool_record["PLAN_TYPES"] = [ + { + "PLAN_TYPE_ID": 122, + "PLAN_TYPE_NAME": "1915(b)" + } + ] + seatool_record["STATE_PLAN"]["PLAN_TYPE"] = 122 + + return seatool_record + + + +# Function to insert records into DynamoDB with multiple component types +def insert_records(state_codes, num_records, component_types): + created_records = [] # List to store final records (State, PK, Component Type) + + for state in state_codes: + for _ in range(num_records): + for component_type in component_types: + random_id, first_timestamp, second_timestamp = generate_ids_and_timestamps(state, component_type) + + # Generate first record + onemac_record = generate_onemac_submission_record(state, random_id, first_timestamp, component_type) + + # Generate second record + seatool_record = generate_seatool_pending_record(state, random_id, second_timestamp, component_type) + + # Try to insert both records, retry if duplicate key error occurs + try: + # insert SEATOOL record first just to avoid package builder issues + table.put_item(Item=seatool_record) + + # Add a delay of 2 second between inserting the first and second records + time.sleep(2) + table.put_item(Item=onemac_record) + + print(f"Inserted records for {random_id} with component type: {component_type}") + + # Add record details to created_records list + created_records.append([state, random_id, component_type]) + + except ClientError as e: + if e.response['Error']['Code'] == 'ConditionalCheckFailedException': + print(f"Duplicate ID {random_id} detected. Retrying with a new ID.") + continue # Generate a new ID and retry + else: + raise e # Raise the exception for non-duplicate errors + + # Sort created records by State, Component Type, PK + created_records.sort(key=lambda x: (x[0], x[2], x[1])) + + # Output final records in a table-like format + print("\nFinal created records:") + print(f"{'State':<10} {'PK':<30} {'Component Type':<20}") + print("-" * 60) + for record in created_records: + print(f"{record[0]:<10} {record[1]:<30} {record[2]:<20}") + + +# Retrieve state codes, number of records, and component types from environment variables +state_codes = os.getenv('STATE_CODES', 'MD,NY,CA,OH,WI').split(',') # Example: 'MD,NY,CA' +num_records = int(os.getenv('NUM_RECORDS', '6')) # Default is 5 if not provided +component_types = os.getenv('COMPONENT_TYPES', 'medicaidspa,chipspa,waivernew,waiverappk,waiveramendment,waiverrenewal').split(',') # Example: 'medicaidspa,chipspa,waivernew,waiverappk' + +# Call the insert function +insert_records(state_codes, num_records, component_types) diff --git a/services/admin/scripts/pk_values.json b/services/admin/scripts/pk_values.json new file mode 100644 index 000000000..aa3a39f93 --- /dev/null +++ b/services/admin/scripts/pk_values.json @@ -0,0 +1,40 @@ +{ + "pk_values": [ + "WI-24-5681", + "WI-24-4068", + "WI-24-6952", + "WI-24-5766", + "WI-24-9659", + "WI-24-3678", + "WI-24-4176", + "WI-24-2648", + "WI-24-7668", + "WI-24-1278", + "WI-24-5949", + "WI-24-5076", + "WI-73399.R01.00", + "WI-74402.R00.00", + "WI-91643.R00.01", + "WI-95019.R00.00", + "WI-44177.R00.01", + "WI-91036.R00.00", + "WI-16228.R01.02", + "WI-70842.R01.00", + "WI-48754.R01.00", + "WI-78312.R00.01", + "WI-11759.R00.00", + "WI-73939.R01.00", + "WI-73184.R01.02", + "WI-92164.R01.02", + "WI-71371.R01.02", + "WI-93250.R00.00", + "WI-89280.R01.00", + "WI-14865.R01.00", + "WI-85003.R00.01", + "WI-10359.R01.02", + "WI-72170.R00.00", + "WI-97670.R00.01", + "WI-55406.R00.01", + "WI-88967.R01.02" + ] +} diff --git a/services/admin/scripts/rebuildPackagesByPk.py b/services/admin/scripts/rebuildPackagesByPk.py new file mode 100644 index 000000000..b124ad044 --- /dev/null +++ b/services/admin/scripts/rebuildPackagesByPk.py @@ -0,0 +1,75 @@ +import boto3 +import datetime +import json +import logging +from boto3.dynamodb.conditions import Key +from botocore.exceptions import ClientError + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Variables +dynamodb_table = "onemac-masterclone-one" + +# Load pk values from an external configuration file with error handling +try: + with open('pk_values.json', 'r') as file: + pk_values = json.load(file) +except (FileNotFoundError, json.JSONDecodeError) as e: + logger.error(f"Error loading pk_values.json: {e}") + pk_values = [] + +# Initialize DynamoDB resource +dynamodb = boto3.resource('dynamodb') +table = dynamodb.Table(dynamodb_table) + +# Counter for updated items +updated_count = 0 + +# Loop through each pk value +for pk in pk_values: + # Query DynamoDB for items with the specified pk and sk starting with OneMAC + response = table.query( + KeyConditionExpression=Key('pk').eq(pk) & Key('sk').begins_with('OneMAC'), + ProjectionExpression='pk, sk' + ) + + items = response.get('Items', []) + + # Handle pagination to ensure all items are retrieved + while 'LastEvaluatedKey' in response: + response = table.query( + KeyConditionExpression=Key('pk').eq(pk) & Key('sk').begins_with('OneMAC'), + ProjectionExpression='pk, sk', + ExclusiveStartKey=response['LastEvaluatedKey'] + ) + items.extend(response.get('Items', [])) + + # Loop through the found items and update them + for item in items: + pk_value = item['pk'] + sk_value = item['sk'] + + # Update the record to trigger a DynamoDB stream event with error handling + try: + table.update_item( + Key={ + 'pk': pk_value, + 'sk': sk_value + }, + UpdateExpression='SET #updatedAt = :timestamp', + ExpressionAttributeNames={ + '#updatedAt': 'updatedAt' + }, + ExpressionAttributeValues={ + ':timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat() + } + ) + logger.info(f"Updated record with pk: {pk_value} and sk: {sk_value} to trigger DynamoDB stream") + updated_count += 1 + except ClientError as e: + logger.error(f"Failed to update record with pk: {pk_value} and sk: {sk_value}. Error: {e.response['Error']['Message']}") + +# Output the total number of updated items +logger.info(f"Total number of updated items: {updated_count}") \ No newline at end of file diff --git a/services/admin/scripts/setPOCs.py b/services/admin/scripts/setPOCs.py new file mode 100644 index 000000000..66c0df19e --- /dev/null +++ b/services/admin/scripts/setPOCs.py @@ -0,0 +1,100 @@ +import json +import boto3 +import os +from botocore.exceptions import ClientError + +# Load the list of GSI1SK values from an external file +with open('pk_values.json') as f: + pk_values = json.load(f) + +GSI1PK_VALUES = [ + "SEATool#Medicaid_SPA", + "SEATool#CHIP_SPA", + "SEATool#1915b_waivers", + "SEATool#1915c_waivers" +] + +LEAD_ANALYST_JSON = { + ":leadAnalyst": { + "L": [ + { + "M": { + "EMAIL": {"S": "onemaccpoc49@gmail.com"}, + "FIRST_NAME": {"S": "OneMAC"}, + "INITIALS": {"NULL": True}, + "LAST_NAME": {"S": "CPOC4"}, + "OFFICER_ID": {"N": "3743"}, + "POSITION_ID": {"N": "538"}, + "TELEPHONE": {"S": "(410) 555-5445"} + } + } + ] + } +} + +ACTION_OFFICERS_JSON = { + ":actionOfficers": { + "L": [ + { + "M": { + "EMAIL": {"S": "onemacsrt4@gmail.com"}, + "FIRST_NAME": {"S": "OneMAC"}, + "LAST_NAME": {"S": "SRT4"}, + "OFFICER_ID": {"N": "3740"}, + "POSITION_ID": {"N": "538"}, + "TELEPHONE": {"S": "(410) 555-5445"} + } + } + ] + } +} + +dynamodb = boto3.client('dynamodb') +DYNAMODB_TABLE = os.getenv('DYNAMODB_TABLE', 'onemac-masterclone-one') + +updated_count = 0 + +for gsi1pk in GSI1PK_VALUES: + for gsi1sk in pk_values['pk_values']: + try: + # Query DynamoDB for items with the specified gsi1pk and gsi1sk + response = dynamodb.query( + TableName=DYNAMODB_TABLE, + IndexName='GSI1', + KeyConditionExpression='GSI1pk = :gsi1pk AND GSI1sk = :gsi1sk', + ExpressionAttributeValues={ + ':gsi1pk': {'S': gsi1pk}, + ':gsi1sk': {'S': gsi1sk} + }, + ProjectionExpression='pk, sk' + ) + + items = response.get('Items', []) + + # Loop through the found items and update them + for item in items: + pk = item.get('pk', {}).get('S') + sk = item.get('sk', {}).get('S') + + # Skip if pk or sk is empty + if not pk or not sk: + continue + + # Update the record by adding or replacing the LEAD_ANALYST and ACTION_OFFICERS properties + dynamodb.update_item( + TableName=DYNAMODB_TABLE, + Key={ + 'pk': {'S': pk}, + 'sk': {'S': sk} + }, + UpdateExpression='SET LEAD_ANALYST = :leadAnalyst, ACTION_OFFICERS = :actionOfficers', + ExpressionAttributeValues={**LEAD_ANALYST_JSON, **ACTION_OFFICERS_JSON} + ) + + print(f"Updated record with pk: {pk} and sk: {sk} with lead analyst: OneMAC CPOC05") + updated_count += 1 + + except ClientError as e: + print(f"Error querying or updating item with GSI1pk: {gsi1pk} and GSI1sk: {gsi1sk}: {e}") + +print(f"Total number of updated items: {updated_count}") \ No newline at end of file diff --git a/services/admin/scripts/users.json b/services/admin/scripts/users.json new file mode 100644 index 000000000..b43b7c711 --- /dev/null +++ b/services/admin/scripts/users.json @@ -0,0 +1,76 @@ +{ + "users": [ + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemaccpoc9@gmail.com", + "family_name": "OneMAC", + "given_name": "CPOC2", + "territory": "CA", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemaccpoc319@gmail.com", + "family_name": "OneMAC", + "given_name": "CPOC3", + "territory": "OH", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemaccpoc49@gmail.com", + "family_name": "OneMAC", + "given_name": "CPOC4", + "territory": "WI", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemaccpoc50@gmail.com", + "family_name": "OneMAC", + "given_name": "CPOC5", + "territory": "NY", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemacsrt2@gmail.com", + "family_name": "OneMAC", + "given_name": "SRT2", + "territory": "CA", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemacsrt3@gmail.com", + "family_name": "OneMAC", + "given_name": "SRT3", + "territory": "OH", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemacsrt4@gmail.com", + "family_name": "OneMAC", + "given_name": "SRT4", + "territory": "WI", + "role": "systemadmin" + }, + { + "password": "Passw0rd!", + "cms_roles": "onemac-state-user", + "email": "onemacsrt5@gmail.com", + "family_name": "OneMAC", + "given_name": "SRT5", + "territory": "NY", + "role": "systemadmin" + } + ] +} \ No newline at end of file diff --git a/services/admin/serverless.yml b/services/admin/serverless.yml index 842c8e4ee..fe425b7f4 100644 --- a/services/admin/serverless.yml +++ b/services/admin/serverless.yml @@ -12,8 +12,6 @@ plugins: - serverless-dotenv-plugin - serverless-s3-bucket-helper custom: - esbuild: - exclude: [] stage: ${opt:stage, self:provider.stage} iamPermissionsBoundaryPolicy: ${ssm:/configuration/${self:custom.stage}/iam/permissionsBoundaryPolicy, ssm:/configuration/default/iam/permissionsBoundaryPolicy, ""} oneMacTableName: onemac-${self:custom.stage}-one @@ -42,6 +40,8 @@ provider: environment: NODE_OPTIONS: '--enable-source-maps' oneMacTableName: ${self:custom.oneMacTableName} + layers: + - ${cf:aws-sdk-v2-layer-${self:custom.stage}.AwsSdkV2LambdaLayerQualifiedArn} functions: diff --git a/services/app-api/email/CMSSubsequentSubmissionNotice.js b/services/app-api/email/CMSSubsequentSubmissionNotice.js index 3b0ef2e82..46beabac4 100644 --- a/services/app-api/email/CMSSubsequentSubmissionNotice.js +++ b/services/app-api/email/CMSSubsequentSubmissionNotice.js @@ -7,7 +7,6 @@ import { getCPOCandSRTEmailAddresses } from "../utils/getCpocAndSrtEmail.js"; * @returns {Object} email parameters in generic format. */ export const CMSSubsequentSubmissionNotice = async (data, config) => { - data.submitterName = ""; // remove this bc we dont want it on the cms email const CMSEmailItem = await getCPOCandSRTEmailAddresses(data.componentId); const ToAddresses = CMSEmailItem.reviewTeamEmailList @@ -20,11 +19,18 @@ export const CMSSubsequentSubmissionNotice = async (data, config) => { let typeLabel = config.typeLabel; // cut the type label at sub sub and set that at the new idLabel typeLabel = typeLabel - .substring(0, typeLabel.indexOf("Subsequent Submission")) + .substring(0, typeLabel.indexOf("Subsequent Documents")) .trim(); config.idLabel = `${typeLabel} Package ID`; } + // creating own object + const cmsSubSubDetails = { + componentId: data.componentId, + additionalInformation: data.additionalInformation, + attachments: data.attachments, + }; + return { ToAddresses: ToAddresses, CcAddresses: [], @@ -33,7 +39,7 @@ export const CMSSubsequentSubmissionNotice = async (data, config) => {

New documents have been submitted for ${config.typeLabel} ${ data.componentId } in OneMAC.

- ${formatPackageDetails(data, config)} + ${formatPackageDetails(cmsSubSubDetails, config)}

How to access:

Thank you!

diff --git a/services/app-api/email/formatPackageDetails.js b/services/app-api/email/formatPackageDetails.js index d7688eb40..a1d519230 100644 --- a/services/app-api/email/formatPackageDetails.js +++ b/services/app-api/email/formatPackageDetails.js @@ -58,7 +58,7 @@ export const formatPackageDetails = (data, config) => { ) { detailText += `

- Files: + Files: