Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: kaspar-p/types-of-ants
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: dd72f85c083cddfa9854db65741aa974a3f0f091
Choose a base ref
..
head repository: kaspar-p/types-of-ants
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: b11ab0a16a6d984f6a8039fd5c53a1556dbec83f
Choose a head ref
Showing with 967 additions and 264 deletions.
  1. +29 −6 README.md
  2. +1 −0 dev/.gitignore
  3. +83 −4 projects/ant-data-farm/src/dao/dao.rs
  4. +10 −0 projects/ant-data-farm/src/dao/daos/ants.rs
  5. +1 −1 projects/ant-data-farm/src/dao/mod.rs
  6. +66 −39 projects/ant-data-farm/src/lib.rs
  7. +5 −5 projects/ant-data-farm/tests/integ.rs
  8. +24 −15 projects/ant-host-agent/src/routes/launch_project.rs
  9. +1 −1 projects/ant-metadata/src/common/project.rs
  10. +6 −2 projects/ant-on-the-web/server/src/main.rs
  11. +46 −11 projects/ant-on-the-web/server/src/routes/ants.rs
  12. +4 −4 projects/ant-on-the-web/server/src/routes/deployments.rs
  13. +4 −4 projects/ant-on-the-web/server/src/routes/hosts.rs
  14. +4 −4 projects/ant-on-the-web/server/src/routes/metrics.rs
  15. +4 −7 projects/ant-on-the-web/server/src/routes/tests.rs
  16. +8 −11 projects/ant-on-the-web/server/src/routes/users.rs
  17. +3 −3 projects/ant-on-the-web/server/src/types/mod.rs
  18. +44 −15 projects/ant-on-the-web/website/src/app/feed/page.tsx
  19. +12 −11 projects/ant-on-the-web/website/src/app/page.tsx
  20. +41 −33 projects/ant-on-the-web/website/src/components/AntBanner.tsx
  21. +41 −35 projects/ant-on-the-web/website/src/components/Header.tsx
  22. +9 −2 projects/ant-on-the-web/website/src/components/SuggestionBox.tsx
  23. +33 −25 projects/ant-on-the-web/website/src/components/UnhappyPath.tsx
  24. +5 −0 projects/ant-using-email/.gitignore
  25. +9 −0 projects/ant-using-email/README.md
  26. 0 projects/ant-using-email/data/.gitkeep
  27. +51 −0 projects/ant-using-email/emails/12-31-2023/new_year.txt
  28. +283 −0 projects/ant-using-email/package-lock.json
  29. +23 −0 projects/ant-using-email/package.json
  30. +86 −0 projects/ant-using-email/src/index.ts
  31. +31 −26 projects/ant-who-tweets/src/main.rs
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
# types-of-ants
it's the types of ants. see www.typesofants.org

## Start tweeting
it's the types of ants. see [www.typesofants.org](www.typesofants.org).

1. Log onto the raspberry pi `pi@$(find_rpi)`
1. Go to `ant-data-farm` and run `docker-compose up -d` to run the database
2. Go to `ant-who-tweets` and run `cargo run &` to start the tweet job.
3. Using the PID from the `cargo run` (or find it with `jobs -l`) disown the process with `disown <pid>`
## Development

### Start the developer database

1. Navigate to `projects/ant-data-farm` and run `docker-compose up -d`.

### Start the developer website

1. Navigate to `projects/ant-on-the-web/website` and run `npm dev`.

### Start the developer server

1. Navigate to `projects/ant-on-the-web/server` and run `cargo run`.

## Production

### Start the production database

1. Log onto the raspberry pi `pi@$(find_rpi)` if you're Kaspar and have that shell command. If not, find the IP yourself, smart guy.
1. Run `cd ~/projects/types-of-ants/projects/ant-data-farm` and `docker-compose up -d` to run the database.
1. Make sure it succeeds.

### Start tweeting

1. Log onto the raspberry pi `pi@$(find_rpi)`.
1. Make sure the database is up.
1. Run `cd ~/projects/types-of-ants/projects/ant-who-tweets` and run `cargo run &` to start the tweet job.
1. Using the PID from the `cargo run` (or find it with `jobs -l`) disown the process with `disown <pid>`.
1 change: 1 addition & 0 deletions dev/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
emails.csv
87 changes: 83 additions & 4 deletions projects/ant-data-farm/src/dao/dao.rs
Original file line number Diff line number Diff line change
@@ -3,10 +3,82 @@ use super::{
daos::{ants::AntsDao, hosts::HostsDao, releases::ReleasesDao, users::UsersDao},
};
use crate::{dao::db::Database, types::ConnectionPool};
use bb8::Pool;
use bb8_postgres::PostgresConnectionManager;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tokio_postgres::NoTls;
use tracing::{debug, info};

pub struct Dao {
#[derive(Debug, Clone)]
pub struct DatabaseCredentials {
pub database_name: String,
pub database_user: String,
pub database_password: String,
}

#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub port: Option<u16>,
/// The credentials to connect to the database.
/// If omitted, tries to get credentials from $DB_PG_NAME, DB_PG_PASSWORD,
/// and DB_PG_USER environment variables
pub creds: Option<DatabaseCredentials>,
/// The IP-address host of the database.
/// If omitted, checks for a $DB_HOST variable, then tries localhost.
pub host: Option<String>,
}

fn get_credentials_from_env() -> Result<DatabaseCredentials, dotenv::Error> {
dotenv::dotenv()?;
Ok(DatabaseCredentials {
database_name: dotenv::var("DB_PG_NAME")?,
database_user: dotenv::var("DB_PG_USER")?,
database_password: dotenv::var("DB_PG_PASSWORD")?,
})
}

fn get_port_from_env() -> Option<u16> {
dotenv::dotenv().ok()?;
dotenv::var("DB_PG_PORT").ok()?.parse::<u16>().ok()
}

fn get_host_from_env() -> Option<String> {
dotenv::dotenv().ok()?;
dotenv::var("DB_HOST").ok()
}

async fn database_connection(
config: DatabaseConfig,
) -> Result<Pool<PostgresConnectionManager<NoTls>>, dotenv::Error> {
let port = config
.port
.unwrap_or(get_port_from_env().unwrap_or(7000))
.clone();
let db_creds = config.creds.unwrap_or_else(|| {
get_credentials_from_env()
.expect("Credentials not explicitly passed in must be in the environment!")
});

// TODO: find out a more dynamic way of getting the IP of a host
let host = config
.host
.unwrap_or_else(|| get_host_from_env().unwrap_or("localhost".to_owned()));

let connection_string = format!(
"postgresql://{}:{}@{}:{}/{}",
db_creds.database_user, db_creds.database_password, host, port, db_creds.database_name
);

debug!("Connecting to database at port {port}...");
let manager = PostgresConnectionManager::new_from_stringlike(connection_string, NoTls).unwrap();
let pool: Pool<PostgresConnectionManager<NoTls>> =
Pool::builder().build(manager).await.unwrap();

Ok(pool)
}

pub struct AntDataFarmClient {
pub ants: RwLock<AntsDao>,
pub releases: RwLock<ReleasesDao>,
pub users: RwLock<UsersDao>,
@@ -16,8 +88,15 @@ pub struct Dao {
// pub tests: TestsDao,
}

impl Dao {
pub async fn new(pool: ConnectionPool) -> Result<Dao, anyhow::Error> {
impl AntDataFarmClient {
pub async fn new(config: DatabaseConfig) -> Result<AntDataFarmClient, anyhow::Error> {
let pool = database_connection(config).await?;
info!("Initializing data access layer...");
let client = AntDataFarmClient::initialize(pool).await?;
return Ok(client);
}

async fn initialize(pool: ConnectionPool) -> Result<AntDataFarmClient, anyhow::Error> {
let db_con = pool.get_owned().await?;

let database: Arc<Mutex<Database>> = Arc::new(Mutex::new(db_con));
@@ -27,7 +106,7 @@ impl Dao {
let hosts = RwLock::new(HostsDao::new(database.clone()).await?);
let users = RwLock::new(UsersDao::new(database.clone()).await?);

Ok(Dao {
Ok(AntDataFarmClient {
ants,
releases,
users,
10 changes: 10 additions & 0 deletions projects/ant-data-farm/src/dao/daos/ants.rs
Original file line number Diff line number Diff line change
@@ -167,6 +167,16 @@ impl DaoTrait<AntsDao, Ant> for AntsDao {
}

impl AntsDao {
/// Get the ants in the users feed at the time of the request.
/// If the user does not exist, returns None.
pub async fn get_user_feed_since(
&self,
user: &UserId,
since: DateTime<Utc>,
) -> Option<Vec<Ant>> {
return Some(vec![]);
}

pub async fn add_ant_tweet(&mut self, ant: &AntId) -> Option<&Ant> {
let time = chrono::offset::Utc::now();

2 changes: 1 addition & 1 deletion projects/ant-data-farm/src/dao/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod dao;
pub mod dao_trait;
pub mod daos;
mod db;
pub mod db;
105 changes: 66 additions & 39 deletions projects/ant-data-farm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
mod dao;
mod types;

use bb8::Pool;
use bb8_postgres::PostgresConnectionManager;

use tokio_postgres::NoTls;
use tracing::debug;

pub use crate::dao::dao::Dao;
pub use crate::dao::dao_trait::DaoTrait;
pub use crate::dao::daos::ants;
pub use crate::dao::daos::hosts;
pub use crate::dao::daos::users;

use crate::{
dao::daos::{ants::AntsDao, hosts::HostsDao, releases::ReleasesDao, users::UsersDao},
dao::db::Database,
types::ConnectionPool,
};
use bb8::Pool;
use bb8_postgres::PostgresConnectionManager;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tokio_postgres::NoTls;
use tracing::{debug, info};

#[derive(Debug, Clone)]
pub struct DatabaseCredentials {
pub database_name: String,
pub database_user: String,
pub database_password: String,
}

#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub port: Option<u16>,
/// The credentials to connect to the database.
/// If omitted, tries to get credentials from $DB_PG_NAME, DB_PG_PASSWORD,
/// and DB_PG_USER environment variables
pub creds: Option<DatabaseCredentials>,
/// The IP-address host of the database.
/// If omitted, checks for a $DB_HOST variable, then tries localhost.
pub host: Option<String>,
}

fn get_credentials_from_env() -> Result<DatabaseCredentials, dotenv::Error> {
dotenv::dotenv()?;
Ok(DatabaseCredentials {
@@ -69,38 +86,48 @@ async fn database_connection(
Ok(pool)
}

#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub port: Option<u16>,
/// The credentials to connect to the database.
/// If omitted, tries to get credentials from $DB_PG_NAME, DB_PG_PASSWORD,
/// and DB_PG_USER environment variables
pub creds: Option<DatabaseCredentials>,
/// The IP-address host of the database.
/// If omitted, checks for a $DB_HOST variable, then tries localhost.
pub host: Option<String>,
}

async fn internal_connect(config: DatabaseConfig) -> Result<Dao, anyhow::Error> {
let pool = database_connection(config)
.await
.unwrap_or_else(|e| panic!("Failed to get environment variable: {e}"));

debug!("Initializing data access layer...");
let dao = Dao::new(pool).await;
debug!("Data access layer initialized!");
return dao;
}

pub async fn connect() -> Result<Dao, anyhow::Error> {
internal_connect(DatabaseConfig {
port: None,
creds: None,
host: None,
})
.await
pub struct AntDataFarmClient {
pub ants: RwLock<AntsDao>,
pub releases: RwLock<ReleasesDao>,
pub users: RwLock<UsersDao>,
pub hosts: RwLock<HostsDao>,
// pub deployments: DeploymentsDao,
// pub metrics: MetricsDao,
// pub tests: TestsDao,
}

pub async fn connect_config(config: DatabaseConfig) -> Result<Dao, anyhow::Error> {
internal_connect(config).await
impl AntDataFarmClient {
pub async fn new(config: Option<DatabaseConfig>) -> Result<AntDataFarmClient, anyhow::Error> {
let config = match config {
None => DatabaseConfig {
port: None,
creds: None,
host: None,
},
Some(c) => c,
};

let pool = database_connection(config).await?;
info!("Initializing data access layer...");
let client = AntDataFarmClient::initialize(pool).await?;
return Ok(client);
}

async fn initialize(pool: ConnectionPool) -> Result<AntDataFarmClient, anyhow::Error> {
let db_con = pool.get_owned().await?;

let database: Arc<Mutex<Database>> = Arc::new(Mutex::new(db_con));

let ants = RwLock::new(AntsDao::new(database.clone()).await?);
let releases = RwLock::new(ReleasesDao::new(database.clone()).await?);
let hosts = RwLock::new(HostsDao::new(database.clone()).await?);
let users = RwLock::new(UsersDao::new(database.clone()).await?);

Ok(AntDataFarmClient {
ants,
releases,
users,
hosts,
})
}
}
10 changes: 5 additions & 5 deletions projects/ant-data-farm/tests/integ.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod util;

use ant_data_farm::{ants::Tweeted, connect_config, DaoTrait, DatabaseConfig};
use ant_data_farm::{ants::Tweeted, AntDataFarmClient, DaoTrait, DatabaseConfig};
use chrono::Duration;

use tracing::debug;
@@ -12,11 +12,11 @@ async fn more_than_500_ants() {
let fixture = test_fixture();
let container = fixture.docker.run(fixture.image);
let port = container.get_host_port_ipv4(5432);
let dao = connect_config(DatabaseConfig {
let dao = AntDataFarmClient::new(Some(DatabaseConfig {
port: Some(port),
creds: None,
host: None,
})
}))
.await
.expect("Connected!");

@@ -32,11 +32,11 @@ async fn add_tweeted(_logging: &()) {
let container = fixture.docker.run(fixture.image);
debug!("Ran fixture!");
let port = container.get_host_port_ipv4(5432);
let dao = connect_config(DatabaseConfig {
let dao = AntDataFarmClient::new(Some(DatabaseConfig {
port: Some(port),
creds: None,
host: None,
})
}))
.await
.expect("Connected!");

39 changes: 24 additions & 15 deletions projects/ant-host-agent/src/routes/launch_project.rs
Original file line number Diff line number Diff line change
@@ -10,26 +10,35 @@ use hyper::StatusCode;
use tracing::info;

use crate::{
common::launch_project::{LaunchProjectRequest, LaunchProjectResponse, LaunchStatus},
common::launch_project::LaunchProjectRequest,
procs::launch_project::{launch_project, LaunchProjectError},
};

impl IntoResponse for LaunchProjectError {
fn into_response(self) -> Response {
match self {
LaunchProjectError::LaunchBinary(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(format!("Launch failed: {}", e.to_string())).into_response(),
),
LaunchProjectError::SaveArtifact(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(e.to_string()).into_response(),
),
LaunchProjectError::AlreadyExists => (
StatusCode::OK,
Json("Project already launched!").into_response(),
),
}
LaunchProjectError::LaunchBinary(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(format!("Launch failed: {}", e.to_string()).to_string()),
)
.into_response()
}
LaunchProjectError::SaveArtifact(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(e.to_string()).into_response(),
)
.into_response()
}
LaunchProjectError::AlreadyExists => {
return (
StatusCode::OK,
Json("Project already launched!".to_string()),
)
.into_response()
}
};
}
}

@@ -63,6 +72,6 @@ pub async fn launch_project_route(
let res = launch_project(project, req.artifact.contents).await;
return match res {
Ok(r) => (StatusCode::OK, Json(r).into_response()),
Err(e) => e.into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.into_response()),
};
}
Loading