Skip to content

Commit

Permalink
Operator functions (#33)
Browse files Browse the repository at this point in the history
* added api for retrieving operator config

* added api for generating upload url
  • Loading branch information
arshiamoghimi authored Aug 1, 2024
1 parent 70d5a83 commit b28789e
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import os
import boto3
import json
import psycopg2
import psycopg2.extras
import uuid
import csv
import base64

def get_db_secret():
db_secret_name = os.environ["SM_DB_CREDENTIALS"]
# secretsmanager client to get db credentials
sm_client = boto3.client("secretsmanager")
response = sm_client.get_secret_value(SecretId=db_secret_name)["SecretString"]
secret = json.loads(response)
return secret


def get_config(operator_id):
valid_uuid = str(uuid.UUID(operator_id))

db_secret = get_db_secret()
connection = psycopg2.connect(
user=db_secret["username"],
password=db_secret["password"],
host=db_secret["host"],
dbname=db_secret["dbname"],
)
cursor = connection.cursor(cursor_factory = psycopg2.extras.RealDictCursor)

get_hydrophone_ids = f"""
SELECT h.hydrophone_id, h.sampling_frequency, h.directory, h.file_name, h.calibration_available
FROM hydrophones h
WHERE h.hydrophone_operator_id = %s;
"""

cursor.execute(get_hydrophone_ids, (valid_uuid,))

results = cursor.fetchall()
if results == {}:
raise Exception("No hydrophones found for the given operator ID.")
return results


def get_calibration_data(hydrophone_id):
calibration_data = {"frequency": [], "sensitivity": []}
bucket = os.environ['BUCKET_NAME']
object_name = f"{hydrophone_id}/calibration.csv"
s3_client = boto3.client('s3')
s3_client.download_file(bucket, object_name, '/tmp/calibration.csv')
with open('/tmp/calibration.csv') as f:
reader = csv.reader(f)
next(reader)
for row in reader:
calibration_data["frequency"].append(row[0])
calibration_data["sensitivity"].append(row[1])
return calibration_data


def handler(event, context):
operator_id = event['queryStringParameters']['operator_id']
try:
configs = get_config(operator_id)
except ValueError:
return {'statusCode': 400, 'body': 'Entered ID is not valid.'}
except Exception as e:
return {'statusCode': 400, 'body': str(e)}
client_config = {"operator_id": operator_id, "hydrophones": [], "scan_interval": 5, "upload_interval": 5}

for config in configs:
hydrophone_config = {'id': config['hydrophone_id'], 'metrics': ['spl'],
'directory_to_watch': config['directory'], 'file_structure_pattern': config['file_name'],
'sample_rate': config['sampling_frequency']}
if config['calibration_available']:
try:
hydrophone_config['calibration_curve'] = get_calibration_data(hydrophone_config['id'])
except Exception:
print(f"Could not find the calibration file for hydrophone: {hydrophone_config['id']}")
client_config['hydrophones'].append(hydrophone_config)

return {'statusCode': 200, 'body': base64.b64encode(json.dumps(client_config, indent=2).encode('utf-8'))}
19 changes: 19 additions & 0 deletions backend/lambda/apiOperatorUploadUrl/apiOperatorUploadUrl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import boto3
import os
import base64
import json

s3_client = boto3.client('s3')


def handler(event, context):
object_key = event['queryStringParameters']['object_key']
bucket_name = os.environ['BUCKET_NAME']

response = s3_client.generate_presigned_post(bucket_name, object_key)

response = {
'statusCode': 200,
'body': base64.b64encode(json.dumps(response, indent=2).encode('utf-8'))
}
return response
151 changes: 94 additions & 57 deletions backend/lib/api-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as iam from 'aws-cdk-lib/aws-iam';
import { VpcStack } from './vpc-stack';
import { DBStack } from './database-stack';
import { FunctionalityStack } from './functionality-stack';
import * as cdk from "aws-cdk-lib";

export class APIStack extends Stack {
public readonly stageARN_APIGW: string;
Expand Down Expand Up @@ -211,6 +212,36 @@ export class APIStack extends Stack {
role: lambdaRole,
});

const apiOperatorConfigRetreiver = new lambda.Function(this, "noiseTracker-apiOperatorConfigRetreiver", {
functionName: "noiseTracker-apiOperatorConfigRetreiver",
runtime: lambda.Runtime.PYTHON_3_9,
handler: "apiOperatorConfigRetreiver.handler",
timeout: Duration.seconds(60),
memorySize: 512,
environment:{
SM_DB_CREDENTIALS: db.secretPathUser.secretName,
BUCKET_NAME: functionalityStack.bucketName,
},
vpc: vpcStack.vpc,
code: lambda.Code.fromAsset("lambda/apiOperatorConfigRetreiver"),
layers: [psyscopg2],
role: lambdaRole,
})

const apiOperatorUploadUrl = new lambda.Function(this, "noiseTracker-apiOperatorUploadUrl", {
functionName: "noiseTracker-apiOperatorUploadUrl",
runtime: lambda.Runtime.PYTHON_3_9,
handler: "apiOperatorUploadUrl.handler",
timeout: Duration.seconds(60),
memorySize: 512,
environment:{
BUCKET_NAME: functionalityStack.bucketName,
},
vpc: vpcStack.vpc,
code: lambda.Code.fromAsset("lambda/apiOperatorUploadUrl"),
role: lambdaRole,
})

const sesStatement = new iam.PolicyStatement();
sesStatement.addActions("ses:SendEmail");
sesStatement.addResources("*");
Expand Down Expand Up @@ -295,85 +326,91 @@ export class APIStack extends Stack {
const adminAuthorizer = new apigateway.TokenAuthorizer(this, 'adminAuthorizer', {handler: adminAuthorizerFunction});
const operatorAuthorizer = new apigateway.TokenAuthorizer(this, 'operatorAuthorizer', {handler: operatorAuthorizerFunction});

// define api resources
const adminResource = api.root.addResource('admin');
const adminHydrophones = adminResource.addResource('hydrophones')
const adminOperators = adminResource.addResource('operators')

const operatorResource = api.root.addResource('operator');
const operatorDownload = operatorResource.addResource('download')
const operatorHydrophones = operatorResource.addResource('hydrophones')
const operatorOperators = operatorResource.addResource('operators')

const publicResource = api.root.addResource('public');
const publicHydrophones = publicResource.addResource('hydrophones')
const publicSpectrograms = publicResource.addResource('spectrograms')
const publicSPL = publicResource.addResource('spl')
const publicGauge = publicResource.addResource('gauge')

adminHydrophones.addMethod('GET', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}), {
// define api resources
const adminResource = api.root.addResource('admin');
const adminHydrophones = adminResource.addResource('hydrophones')
const adminOperators = adminResource.addResource('operators')

const operatorResource = api.root.addResource('operator');
const operatorDownload = operatorResource.addResource('download')
const operatorHydrophones = operatorResource.addResource('hydrophones')
const operatorOperators = operatorResource.addResource('operators')
const operatorConfig = operatorResource.addResource('config')
const operatorUploadUrl = operatorResource.addResource('upload-url')

const publicResource = api.root.addResource('public');
const publicHydrophones = publicResource.addResource('hydrophones')
const publicSpectrograms = publicResource.addResource('spectrograms')
const publicSPL = publicResource.addResource('spl')
const publicGauge = publicResource.addResource('gauge')

adminHydrophones.addMethod('GET', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}), {
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
adminHydrophones.addMethod('POST', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
});
adminHydrophones.addMethod('POST', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
adminHydrophones.addMethod('PUT', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
});
adminHydrophones.addMethod('PUT', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
adminHydrophones.addMethod('DELETE', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
});
adminHydrophones.addMethod('DELETE', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
});

adminOperators.addMethod('GET', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
adminOperators.addMethod('GET', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
adminOperators.addMethod('POST', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
});
adminOperators.addMethod('POST', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
adminOperators.addMethod('PUT', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
});
adminOperators.addMethod('PUT', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});
adminOperators.addMethod('DELETE', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
});
adminOperators.addMethod('DELETE', new apigateway.LambdaIntegration(apiAdminHandler, {proxy: true}),
{
authorizer: adminAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});

operatorDownload.addMethod('GET', new apigateway.LambdaIntegration(operatorDownloadHandler, {proxy: true}),
{
});
operatorDownload.addMethod('GET', new apigateway.LambdaIntegration(operatorDownloadHandler, {proxy: true}),
{
authorizer: operatorAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});

operatorHydrophones.addMethod('GET', new apigateway.LambdaIntegration(apiOperatorHandler, {proxy: true}),
{
});
operatorHydrophones.addMethod('GET', new apigateway.LambdaIntegration(apiOperatorHandler, {proxy: true}),
{
authorizer: operatorAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});

operatorOperators.addMethod('GET', new apigateway.LambdaIntegration(apiOperatorHandler, {proxy: true}),
{
});
operatorOperators.addMethod('GET', new apigateway.LambdaIntegration(apiOperatorHandler, {proxy: true}),
{
authorizer: operatorAuthorizer,
authorizationType: apigateway.AuthorizationType.CUSTOM,
});

publicHydrophones.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));
publicSpectrograms.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));
publicSPL.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));
publicGauge.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));

});
operatorConfig.addMethod('GET', new apigateway.LambdaIntegration(apiOperatorConfigRetreiver, {proxy: true}));
operatorUploadUrl.addMethod('GET', new apigateway.LambdaIntegration(apiOperatorUploadUrl, {proxy: true}));
publicHydrophones.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));
publicSpectrograms.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));
publicSPL.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));
publicGauge.addMethod('GET', new apigateway.LambdaIntegration(apiPublicHandler, {proxy: true}));

new cdk.CfnOutput(this, 'Output-Message', {
value: `
Operator Get Config URL: ${api.urlForPath()}operator/config
Operator Upload URL: ${api.urlForPath()}operator/upload-url
`,
})
}
}

0 comments on commit b28789e

Please sign in to comment.