Skip to content

Commit

Permalink
Merge pull request #16 from Guysnacho/24-sale-lambda
Browse files Browse the repository at this point in the history
24 sale lambda
  • Loading branch information
Guysnacho authored Sep 19, 2024
2 parents 87e8c2e + f3ee217 commit 9f1a724
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 38 deletions.
1 change: 1 addition & 0 deletions fixtures/json/ValidSale.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "user_id": "insert_id_from_db_here", "quantity": 1, "sku": "0" }
2 changes: 1 addition & 1 deletion fixtures/sql/1_build_auth_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

CREATE TABLE member (
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
email TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
fname TEXT NOT NULL,
lname TEXT NOT NULL
Expand Down
75 changes: 75 additions & 0 deletions fixtures/sql/2_build_sale_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
-- Active: 1726216791298@@storefront-db.ct84ooq2shac.us-west-2.rds.amazonaws.com@5432@storefront
CREATE TABLE public.stock (
sku SERIAL PRIMARY KEY,
name TEXT NOT NULL,
quantity SMALLINT NOT NULL DEFAULT 0,
price INTEGER NOT NULL DEFAULT 0,
item_url TEXT NOT NULL
);

-- Seed DB
INSERT into public.stock (name, price, quantity, item_url) VALUES ('Bando Stone and The New World', 45, 3, 'https://t2.genius.com/unsafe/728x0/https%3A%2F%2Fimages.genius.com%2Ff19320aae82a75396d97def01ae89ff3.1000x1000x1.png');
INSERT into public.stock (name, price, quantity, item_url) VALUES ('alligator bites never heal - Doechii', 30, 2, 'https://shop.capitolmusic.com/cdn/shop/files/DoechiiABNHLPInsert.png?v=1724951711&width=800');
INSERT into public.stock (name, price, quantity, item_url) VALUES ('Nova - James Fauntleroy, Terrace Martin', 31, 4, 'https://images.squarespace-cdn.com/content/v1/5699291fa976afc919dbca7d/a701db3b-83c1-457b-a892-0c46b0e6749c/Nova+Artwork.jpg?format=500w');

CREATE TABLE public.order (
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
user_id UUID NOT NULL REFERENCES member (id),
sku SERIAL NOT NULL,
quantity SMALLINT NOT NULL DEFAULT 0
);

SELECT * from public.stock;
SELECT * from public.order;

CREATE or REPLACE function public.handle_sale ()
returns trigger as
$$
declare results RECORD;
begin
SELECT sku, quantity INTO results FROM public.stock WHERE sku = new.sku;

IF NOT FOUND THEN
RAISE EXCEPTION 'item not found';
ELSEIF results.quantity - new.quantity < 1 THEN
RAISE EXCEPTION 'not enough in stock';
END IF;
UPDATE public.stock SET quantity = quantity - new.quantity WHERE sku = new.sku;
return new;
END;
$$ language plpgsql;

-- trigger the function every time a user is created
create trigger on_sale_init
after insert on public.order
for each row execute procedure public.handle_sale();

-- ========================= ========================= ========================= =========================
-- Test your queries here before writing up production queries in the lambda
-- Invalid row, FK not present
INSERT into public.order (sku, quantity) VALUES (2, 1000);

-- ========================= ========================= ========================= =========================
-- Add stock user
-- INSERT into
-- member (email, password, fname, lname)
-- VALUES (
-- 'email',
-- 'password',
-- 'fname',
-- 'lname'
-- );
-- SELECT * from member;
-- -- Init an invalid sale
-- INSERT into public.order (user_id, sku, quantity) VALUES ('your_user_id_here', 2, 1000);
-- -- Init a valid sale
-- INSERT into public.order (user_id, sku, quantity) VALUES ('9b3b9b36-8fe5-4bbd-b655-c212704e4c79', 2, 1);

-- delete FROM public.order;
-- delete FROM public.stock;
-- delete FROM public.member;

-- -- Seed after manually running tests
-- INSERT into public.stock (name, price, quantity, item_url) VALUES ('Bando Stone and The New World', 45, 3, 'https://t2.genius.com/unsafe/728x0/https%3A%2F%2Fimages.genius.com%2Ff19320aae82a75396d97def01ae89ff3.1000x1000x1.png');
-- INSERT into public.stock (name, price, quantity, item_url) VALUES ('alligator bites never heal - Doechii', 30, 2, 'https://shop.capitolmusic.com/cdn/shop/files/DoechiiABNHLPInsert.png?v=1724951711&width=800');
-- INSERT into public.stock (name, price, quantity, item_url) VALUES ('Nova - James Fauntleroy, Terrace Martin', 31, 4, 'https://images.squarespace-cdn.com/content/v1/5699291fa976afc919dbca7d/a701db3b-83c1-457b-a892-0c46b0e6749c/Nova+Artwork.jpg?format=500w');
14 changes: 5 additions & 9 deletions terraform/auth_lambda.tf
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
locals {
package = "deployment_package.zip"
}

data "aws_caller_identity" "current" {}
data "aws_caller_identity" "auth_current" {}

data "archive_file" "package" {
data "archive_file" "auth_package" {
type = "zip"
source_dir = "${path.module}/lib/auth/"
output_path = "${path.module}/lib/auth/${local.package}"
excludes = [".gitignore", "README.md", "testbench.js", "package-lock.json", local.package]
output_path = "${path.module}/lib/auth/deployment_package.zip"
excludes = [".gitignore", "README.md", "testbench.js", "package-lock.json", "deployment_package.zip"]
}

module "auth_lambda" {
Expand All @@ -26,7 +22,7 @@ module "auth_lambda" {
# source_path = "${path.module}/lib/auth/auth.js"
# source_path = "${path.module}/lib/auth/"

local_existing_package = data.archive_file.package.output_path
local_existing_package = data.archive_file.auth_package.output_path
package_type = "Zip"
create_package = false

Expand Down
File renamed without changes.
Binary file removed terraform/lib/auth/deployment_package.zip
Binary file not shown.
35 changes: 10 additions & 25 deletions terraform/lib/auth/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// import { Handler } from "aws-lambda";
// All AWS SDK Clients are available under the @aws-sdk namespace. You can install them locally to see functions and types

import {
GetSecretValueCommand,
ListSecretsCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
import postgres from "postgres";
// const Handler = require("aws-lambda/handler");
// import { Handler } from "aws-lambda";

/** @type {Handler} */
const handler = async (event, context, callback) => {
Expand All @@ -18,40 +16,25 @@ const handler = async (event, context, callback) => {
const payload = isValidPayload(event);

// If bad request recieved
if (!payload) {
return {
statusCode: 400,
statusDescription: "bad request",
};
}
if (!payload) throw new Error("bad request");

const secret = await fetchDBSecret();

if (!secret || secret == "") {
return {
statusCode: 500,
statusDescription: "failed to fetch database creds",
};
}
if (!secret || secret == "")
throw new Error("failed to fetch database creds");

/** Database Credentials @type {{username: string, password: string} | undefined} */
let creds;
try {
creds = JSON.parse(secret);
} catch (error) {
console.error(error);
return {
statusCode: 500,
statusDescription: "Failed to fetch db creds",
};
throw new Error("Failed to fetch db creds");
}

if (creds == undefined || !creds.password || !creds.username) {
console.error("Mission failed, we'll get em next time");
return {
statusCode: 500,
statusDescription: "Invalid db creds",
};
throw new Error("Invalid db creds");
}
console.log("Successfully fetched DB creds ✨");

Expand Down Expand Up @@ -118,6 +101,7 @@ const handleLogin = (email, password, creds) => {
const handleSignUp = async ({ email, password, fname, lname }, creds) => {
console.log("Handling sign up");

// Build a client
const sql = postgres({
database: "storefront",
user: creds.username,
Expand All @@ -128,7 +112,7 @@ const handleSignUp = async ({ email, password, fname, lname }, creds) => {
},
});

const res = await sql`INSERT into member
const res = await sql`INSERT into public.member
(email, password, fname, lname) VALUES
(${email}, ${password}, ${fname}, ${lname})
Expand All @@ -146,10 +130,11 @@ const handleSignUp = async ({ email, password, fname, lname }, creds) => {
console.error(err);
return {
statusCode: 500,
statusDescription: err.message,
error: err.message,
};
});

if (res.error) throw new Error(res.error);
return res;
};

Expand Down
14 changes: 12 additions & 2 deletions terraform/lib/auth/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion terraform/lib/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"dependencies": {
"postgres": "^3.4.4"
},
"name": "lib",
"devDependencies": {
"@types/aws-lambda": "^8.10.145"
},
"name": "auth",
"type": "module",
"version": "1.0.0",
"description": "Auth Lambda Package",
Expand Down
1 change: 1 addition & 0 deletions terraform/lib/sale/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
140 changes: 140 additions & 0 deletions terraform/lib/sale/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// All AWS SDK Clients are available under the @aws-sdk namespace. You can install them locally to see functions and types
import {
GetSecretValueCommand,
ListSecretsCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
import postgres from "postgres";
// import { Handler } from "aws-lambda";

/** @type {Handler} */
const handler = async (event, context, callback) => {
console.log(`Starting ${context?.functionName} invocation`);
console.debug("Payload recieved");
console.debug(event);

const payload = isValidPayload(event);

// If bad request recieved
if (!payload) throw new Error("bad request");

const secret = await fetchDBSecret();

if (!secret || secret == "")
throw new Error("failed to fetch database creds");

/** Database Credentials @type {{username: string, password: string} | undefined} */
let creds;
try {
creds = JSON.parse(secret);
} catch (error) {
console.error(error);
throw new Error("Failed to fetch db creds");
}

if (creds == undefined || !creds.password || !creds.username) {
console.error("Mission failed, we'll get em next time");
throw new Error("Invalid db creds");
}
console.log("Successfully fetched DB creds ✨");

return await handleSale(payload, creds);
};

/**
* Request validation
* @param {*} event
* @returns {{ user_id: string; quantity: number; sku: string; } | undefined}
*/
const isValidPayload = (event) => {
if (
event?.user_id &&
event?.user_id !== "" &&
event?.sku &&
event?.sku !== "" &&
event?.quantity
) {
return event;
} else return undefined;
};

const fetchDBSecret = async () => {
const client = new SecretsManagerClient({ region: process.env.AWS_REGION });
const listCommand = new ListSecretsCommand({
region: process.env.AWS_REGION,
// For some reason plain text filtering isn't working. Need to fix this if we're gonna have multiple secrets
// Filters: [{ Key: "name", Values: "rds" }],
});

const res = await client
.send(listCommand)
.then((res) => res.SecretList)
.catch((err) => {
console.error(err);
return new Error("Failed to fetch db secret");
});
if (typeof res == typeof Error || !res || res.length == 0) return undefined;

const getSecretCommand = new GetSecretValueCommand({
SecretId: res[0].Name,
});
const secret = await client
.send(getSecretCommand)
.then((res) => res.SecretString)
.catch((err) => {
console.error(err);
return new Error("Failed to fetch db secret");
});
if (typeof res == typeof Error || !res || res.length == 0) return undefined;

return secret;
};

/**
* Handle our sale
* @param {{ user_id: string; quantity: number; sku: string; }} payload
* @param {{username: string, password: string}} creds
*/
const handleSale = async ({ user_id, sku, quantity }, creds) => {
console.log("Handling sale");

// Build a client
const sql = postgres({
database: "storefront",
user: creds.username,
pass: creds.password,
host: process.env.db_host,
connection: {
application_name: process.env.AWS_LAMBDA_FUNCTION_NAME,
},
});

// Perform our insert and select the price
const res = await sql`INSERT into public.order
(user_id, sku, quantity) VALUES
(${user_id}, ${sku}, ${quantity})
returning *
`
.then((res) => {
return {
statusCode: 201,
sku: res[0].id,
statusDescription: "sale complete",
};
})
.catch((err) => {
console.error("Ran into an issue during the sale");
console.error(err);
return {
statusCode: 500,
error: err.message,
};
});

if (res.error) throw new Error(res.error);
return res;
};

export { handler };

Loading

0 comments on commit 9f1a724

Please sign in to comment.