Compare commits
No commits in common. "78a44157f6829a38bbf21ce084d73a602af2316a" and "8d740cfd429115bda51e168c3825dff38180b4ea" have entirely different histories.
78a44157f6
...
8d740cfd42
6 changed files with 10 additions and 1833 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,15 +1,3 @@
|
||||||
.env
|
.env
|
||||||
.envrc
|
.envrc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|
||||||
# Added by cargo
|
|
||||||
|
|
||||||
/target
|
|
||||||
|
|
||||||
|
|
||||||
# Added by cargo
|
|
||||||
#
|
|
||||||
# already existing elements were commented out
|
|
||||||
|
|
||||||
#/target
|
|
||||||
|
|
1604
Cargo.lock
generated
1604
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -1,14 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "smsbb"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
aws-config = { version = "1.1.5", features = ["sso"] }
|
|
||||||
aws-sdk-dynamodb = "1.14.0"
|
|
||||||
aws-sdk-sns = "1.13.0"
|
|
||||||
aws-sdk-sqs = "1.13.0"
|
|
||||||
clap = "4.4.18"
|
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
|
37
README.md
37
README.md
|
@ -1,37 +0,0 @@
|
||||||
# smsbb 👼
|
|
||||||
|
|
||||||
✨ DIY sms announce lists for you and yours ✨
|
|
||||||
|
|
||||||
## norms for working on this repo
|
|
||||||
|
|
||||||
- work in a branch
|
|
||||||
- branch names should use the format `username/thing-you-are-doing`
|
|
||||||
- for example `git checkout -b maren/update-readme-3`
|
|
||||||
- don't worry about tidy commits! just keep your work in a branch
|
|
||||||
- open a PR and review with someone synchronously before merging to main
|
|
||||||
|
|
||||||
## getting started real fast
|
|
||||||
|
|
||||||
1. Make sure you have Rust (+ cargo) installed. The preferred way to do this
|
|
||||||
is with [rustup](https://rustup.rs).
|
|
||||||
|
|
||||||
2. Set up [an AWS Account](https://aws.amazon.com). Following the getting
|
|
||||||
started guide for the [AWS SDK For Rust](https://aws.amazon.com/sdk-for-rust/)
|
|
||||||
is a good way to get set up. We can confirm that this all works correctly
|
|
||||||
using Workforce Identity, i.e. establishing a profile in `~/.aws/config`,
|
|
||||||
setting the `AWS_PROFILE` environment variable appropriately, and using
|
|
||||||
`aws sso login` to get an access token.
|
|
||||||
|
|
||||||
3. With your preferred authentication mechanism set up (see above for
|
|
||||||
settings for Workforce Identity), run
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
As of right this second, all the code does is set up DynamoDB tables.
|
|
||||||
The immediate future work here is:
|
|
||||||
|
|
||||||
1. Write code to provision AWS Pinpoint for each of smsbb's text campaigns
|
|
||||||
2. Figure out how to embed another Rust binary inside the main Rust binary
|
|
||||||
3. Use the embedded Rust binaries to deploy Lambda functions
|
|
10
readme.md
Normal file
10
readme.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# smsbb 👼
|
||||||
|
|
||||||
|
✨ DIY sms announce lists for you and yours ✨
|
||||||
|
|
||||||
|
## norms for working on this repo
|
||||||
|
- work in a branch
|
||||||
|
- branch names should use the format `username/thing-you-are-doing`
|
||||||
|
- for example `git checkout -b maren/update-readme-3`
|
||||||
|
- don't worry about tidy commits! just keep your work in a branch
|
||||||
|
- open a PR and review with someone synchronously before merging to main
|
166
src/main.rs
166
src/main.rs
|
@ -1,166 +0,0 @@
|
||||||
use aws_config::meta::region::RegionProviderChain;
|
|
||||||
use aws_config::BehaviorVersion;
|
|
||||||
use aws_sdk_dynamodb::operation::create_table::CreateTableError;
|
|
||||||
// use aws_sdk_sns::{Client as SnsClient, Error as SnsError};
|
|
||||||
use aws_sdk_dynamodb::Client as DynamoClient;
|
|
||||||
use aws_sdk_dynamodb::types::{AttributeDefinition, BillingMode, KeySchemaElement, KeyType, ScalarAttributeType};
|
|
||||||
|
|
||||||
// const TOPIC_PREFIX: &'static str = "smsbb_";
|
|
||||||
const TABLE_PREFIX: &'static str = "smsbb_";
|
|
||||||
|
|
||||||
/*
|
|
||||||
fn full_topic_name(s: &str) -> String {
|
|
||||||
format!("{}{}", TOPIC_PREFIX, s)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn table_name_with_prefix(s: &str) -> String {
|
|
||||||
format!("{}{}", TABLE_PREFIX, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn users_table_name() -> String {
|
|
||||||
table_name_with_prefix("users")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn campaigns_table_name() -> String {
|
|
||||||
table_name_with_prefix("campaigns")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn messages_table_name() -> String {
|
|
||||||
table_name_with_prefix("messages")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsubscribes_table_name() -> String {
|
|
||||||
table_name_with_prefix("unsubscribes")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum SmsbbInternalError {
|
|
||||||
CreateTableFailed(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_table_failed(table_name: &str) -> SmsbbInternalError {
|
|
||||||
SmsbbInternalError::CreateTableFailed(table_name.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DynamoKeySpecification<'a> {
|
|
||||||
hash_key: &'a str,
|
|
||||||
sort_key: Option<&'a str>
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_key_schema_element(attr_name: &str, key_type: KeyType) -> KeySchemaElement {
|
|
||||||
KeySchemaElement::builder()
|
|
||||||
.attribute_name(attr_name)
|
|
||||||
.key_type(key_type)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn string_attribute_definition(name: &str) -> AttributeDefinition {
|
|
||||||
AttributeDefinition::builder().attribute_name(name).attribute_type(ScalarAttributeType::S).build().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn atribute_definitions_for_key<'a>(primary_key: &DynamoKeySpecification) -> Option<Vec<AttributeDefinition>> {
|
|
||||||
let mut results = vec!(string_attribute_definition(primary_key.hash_key));
|
|
||||||
match primary_key.sort_key {
|
|
||||||
Some(sort_key) => {
|
|
||||||
results.push(string_attribute_definition(sort_key))
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
Some(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_table_if_not_exists<'a>(
|
|
||||||
client: &DynamoClient,
|
|
||||||
table_name: &str,
|
|
||||||
primary_key: &DynamoKeySpecification<'a>
|
|
||||||
) -> Result<(), SmsbbInternalError> {
|
|
||||||
let primary_key_schema = build_key_schema_element(primary_key.hash_key, KeyType::Hash);
|
|
||||||
let mut table_builder = client.create_table()
|
|
||||||
.set_attribute_definitions(atribute_definitions_for_key(primary_key))
|
|
||||||
.table_name(table_name)
|
|
||||||
.key_schema(primary_key_schema)
|
|
||||||
.billing_mode(BillingMode::PayPerRequest);
|
|
||||||
|
|
||||||
match primary_key.sort_key {
|
|
||||||
Some(sort_key) => {
|
|
||||||
table_builder = table_builder.key_schema(build_key_schema_element(sort_key, KeyType::Range));
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let build_result = table_builder.send().await;
|
|
||||||
match build_result {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(e) => {
|
|
||||||
match e.as_service_error() {
|
|
||||||
None => {
|
|
||||||
dbg!("{}", e);
|
|
||||||
return Err(create_table_failed(table_name));
|
|
||||||
},
|
|
||||||
Some(cte) => {
|
|
||||||
match cte {
|
|
||||||
CreateTableError::ResourceInUseException(_) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
problem => {
|
|
||||||
dbg!("{}", problem);
|
|
||||||
return Err(create_table_failed(table_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're not handling secondary indexes yet.
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_tables(client: &DynamoClient) -> Result<(), SmsbbInternalError> {
|
|
||||||
let users_table = users_table_name();
|
|
||||||
let users_key = DynamoKeySpecification { hash_key: "email", sort_key: None };
|
|
||||||
|
|
||||||
create_table_if_not_exists(client, &users_table, &users_key).await?;
|
|
||||||
|
|
||||||
let campaign_table = campaigns_table_name();
|
|
||||||
let campaign_key = DynamoKeySpecification { hash_key: "campaign_name", sort_key: Some("created_at") };
|
|
||||||
|
|
||||||
create_table_if_not_exists(client, &campaign_table, &campaign_key).await?;
|
|
||||||
|
|
||||||
let messages_table = messages_table_name();
|
|
||||||
let messages_key = DynamoKeySpecification { hash_key: "campaign_name", sort_key: Some("message_id") };
|
|
||||||
|
|
||||||
create_table_if_not_exists(client, &messages_table, &messages_key).await?;
|
|
||||||
|
|
||||||
let unsubscribes_table = unsubscribes_table_name();
|
|
||||||
let unsubscribes_key = DynamoKeySpecification { hash_key: "campaign_name", sort_key: Some("phone_number") };
|
|
||||||
|
|
||||||
create_table_if_not_exists(client, &unsubscribes_table, &unsubscribes_key).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), SmsbbInternalError> {
|
|
||||||
let region_provider = RegionProviderChain::default_provider()
|
|
||||||
.or_else("us-east-1");
|
|
||||||
let config = aws_config::defaults(BehaviorVersion::latest())
|
|
||||||
.region(region_provider)
|
|
||||||
.load()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let dynamo_client = DynamoClient::new(&config);
|
|
||||||
let ensure_resp = ensure_tables(&dynamo_client).await;
|
|
||||||
match ensure_resp {
|
|
||||||
Err(sdk_error) => {
|
|
||||||
dbg!("{}", sdk_error);
|
|
||||||
return Err(SmsbbInternalError::CreateTableFailed("woof".to_string()));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Loading…
Reference in a new issue