hexagonal?

hexagon
Patrick Cleavelin 2025-10-07 16:24:55 -05:00
parent 312d6c98cf
commit eb23143739
15 changed files with 325 additions and 30 deletions

7
Cargo.lock generated
View File

@ -103,6 +103,12 @@ dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -1122,6 +1128,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
name = "memejoin-rs"
version = "0.2.2-alpha"
dependencies = [
"anyhow",
"async-trait",
"axum",
"axum-extra",

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.100"
async-trait = "0.1.72"
axum = { version = "0.6.9", features = ["headers", "multipart"] }
axum-extra = { version = "0.7.5", features = ["cookie-private", "cookie"] }

View File

@ -36,11 +36,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
@ -62,11 +62,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1743682350,
"narHash": "sha256-S/MyKOFajCiBm5H5laoE59wB6w0NJ4wJG53iAPfYW3k=",
"lastModified": 1759718104,
"narHash": "sha256-TbkLsgdnXHUXR4gOQBmhxkEE9ne+eHmX1chZHWRogy0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c4a8327b0f25d1d81edecbb6105f74d7cf9d7382",
"rev": "edea9f33f9a03f615ad3609a40fbcefe0ec835ca",
"type": "github"
},
"original": {

View File

@ -24,7 +24,7 @@
};
});
local-rust = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain).override {
extensions = [ "rust-analysis" ];
extensions = [ "rust-analyzer" "rust-src" ];
};
in
{

View File

@ -0,0 +1,3 @@
pub mod models;
pub mod ports;
pub mod service;

View File

@ -0,0 +1,104 @@
use std::collections::HashMap;
use thiserror::Error;
pub struct GuildId(u64);
pub struct ExternalGuildId(u64);
pub struct UserName(String);
pub struct ChannelName(String);
pub struct IntroId(i32);
pub struct Guild {
id: GuildId,
name: String,
sound_delay: u32,
external_id: ExternalGuildId,
channels: Vec<Channel>,
users: Vec<User>,
}
pub struct User {
user: UserName,
channel_intros: HashMap<ChannelName, Vec<Intro>>,
}
pub struct Channel {
name: ChannelName,
}
pub struct Intro {
id: IntroId,
name: String,
filename: String,
}
pub struct CreateGuildRequest {
name: String,
sound_delay: u32,
external_id: ExternalGuildId,
}
pub struct CreateUserRequest {
user: UserName,
}
pub struct CreateChannelRequest {
guild_id: GuildId,
channel_name: ChannelName,
}
pub struct AddIntroToGuildRequest {
guild_id: GuildId,
name: String,
volume: i32,
filename: String,
}
pub struct AddIntroToUserRequest {
user: UserName,
guild_id: GuildId,
channel_name: ChannelName,
intro_id: IntroId,
}
#[derive(Debug, Error)]
pub enum CreateGuildError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum CreateUserError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum CreateChannelError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum AddIntroToGuildError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum AddIntroToUserError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum GetGuildError {
#[error("Guild not found")]
NotFound,
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}

View File

@ -0,0 +1 @@
pub mod guild;

View File

@ -0,0 +1,45 @@
use super::models::guild::{
AddIntroToGuildError, AddIntroToGuildRequest, AddIntroToUserError, AddIntroToUserRequest,
Channel, CreateChannelError, CreateChannelRequest, CreateGuildError, CreateGuildRequest,
CreateUserError, CreateUserRequest, GetGuildError, Guild, GuildId, User,
};
pub trait IntroToolService {
async fn create_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError>;
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
async fn create_channel(
&self,
req: CreateChannelRequest,
) -> Result<Channel, CreateChannelError>;
async fn add_intro_to_guild(
&self,
req: AddIntroToGuildRequest,
) -> Result<(), AddIntroToGuildError>;
async fn add_intro_to_user(
&self,
req: AddIntroToUserRequest,
) -> Result<(), AddIntroToUserError>;
}
pub trait IntroToolRepository {
async fn get_guild(&self, guild_id: GuildId) -> Result<Guild, GetGuildError>;
async fn create_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError>;
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
async fn create_channel(
&self,
req: CreateChannelRequest,
) -> Result<Channel, CreateChannelError>;
async fn add_intro_to_guild(
&self,
req: AddIntroToGuildRequest,
) -> Result<(), AddIntroToGuildError>;
async fn add_intro_to_user(
&self,
req: AddIntroToUserRequest,
) -> Result<(), AddIntroToUserError>;
}

View File

@ -0,0 +1,59 @@
use crate::lib::domain::intro_tool::ports::{IntroToolRepository, IntroToolService};
use super::models;
pub struct Service<R>
where
R: IntroToolRepository,
{
repo: R,
}
impl<R> Service<R>
where
R: IntroToolRepository,
{
pub fn new(repo: R) -> Self {
Self { repo }
}
}
impl<R> IntroToolService for Service<R>
where
R: IntroToolRepository,
{
async fn create_guild(
&self,
req: models::guild::CreateGuildRequest,
) -> Result<models::guild::Guild, models::guild::CreateGuildError> {
self.repo.create_guild(req).await
}
async fn create_user(
&self,
req: models::guild::CreateUserRequest,
) -> Result<models::guild::User, models::guild::CreateUserError> {
self.repo.create_user(req).await
}
async fn create_channel(
&self,
req: models::guild::CreateChannelRequest,
) -> Result<models::guild::Channel, models::guild::CreateChannelError> {
self.repo.create_channel(req).await
}
async fn add_intro_to_guild(
&self,
req: models::guild::AddIntroToGuildRequest,
) -> Result<(), models::guild::AddIntroToGuildError> {
self.repo.add_intro_to_guild(req).await
}
async fn add_intro_to_user(
&self,
req: models::guild::AddIntroToUserRequest,
) -> Result<(), models::guild::AddIntroToUserError> {
self.repo.add_intro_to_user(req).await
}
}

1
src/lib/domain/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod intro_tool;

0
src/lib/inbound/mod.rs Normal file
View File

3
src/lib/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod domain;
pub mod inbound;
pub mod outbound;

1
src/lib/outbound/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod sqlite;

View File

@ -0,0 +1,51 @@
use crate::lib::domain::intro_tool::{
models::guild::{
self, AddIntroToGuildError, AddIntroToGuildRequest, AddIntroToUserRequest, Channel,
CreateChannelError, CreateChannelRequest, CreateGuildError, CreateGuildRequest,
CreateUserError, CreateUserRequest, GetGuildError, Guild, GuildId, User,
},
ports::IntroToolRepository,
};
pub struct Sqlite {}
impl Sqlite {
pub fn new(path: &str) -> Result<Self, std::io::Error> {
todo!()
}
}
impl IntroToolRepository for Sqlite {
async fn get_guild(&self, guild_id: GuildId) -> Result<Guild, GetGuildError> {
todo!()
}
async fn create_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError> {
todo!()
}
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError> {
todo!()
}
async fn create_channel(
&self,
req: CreateChannelRequest,
) -> Result<Channel, CreateChannelError> {
todo!()
}
async fn add_intro_to_guild(
&self,
req: AddIntroToGuildRequest,
) -> Result<(), AddIntroToGuildError> {
todo!()
}
async fn add_intro_to_user(
&self,
req: AddIntroToUserRequest,
) -> Result<(), guild::AddIntroToUserError> {
todo!()
}
}

View File

@ -2,6 +2,8 @@
// #![feature(proc_macro_hygiene)]
// #![feature(async_closure)]
mod lib;
mod auth;
mod db;
mod htmx;
@ -28,6 +30,8 @@ use serenity::prelude::*;
use songbird::SerenityInit;
use tracing::*;
use crate::lib::domain::intro_tool;
use crate::lib::outbound;
use crate::settings::Settings;
enum HandlerMessage {
@ -312,37 +316,52 @@ async fn spawn_bot(db: Arc<tokio::sync::Mutex<db::Database>>) {
#[instrument]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
tracing_subscriber::fmt::init();
let settings = serde_json::from_str::<Settings>(
&std::fs::read_to_string("config/settings.json").expect("no config/settings.json"),
)
.expect("error parsing settings file");
info!("{settings:?}");
let (run_api, run_bot) = (settings.run_api, settings.run_bot);
let db = Arc::new(tokio::sync::Mutex::new(
db::Database::new("./config/db.sqlite").expect("couldn't open sqlite db"),
));
let db = outbound::sqlite::Sqlite::new(".config/db.sqlite").expect("couldn't open sqlite db");
let service = intro_tool::service::Service::new(db);
{
// attempt to initialize the database with the schema
let db = db.lock().await;
db.init().expect("couldn't init db");
}
if run_api {
spawn_api(db.clone());
}
if run_bot {
spawn_bot(db).await;
}
info!("spawned background tasks");
let _ = tokio::signal::ctrl_c().await;
info!("Received Ctrl-C, shuttdown down.");
// TODO: http server
Ok(())
// dotenv::dotenv().ok();
//
// tracing_subscriber::fmt::init();
//
// let settings = serde_json::from_str::<Settings>(
// &std::fs::read_to_string("config/settings.json").expect("no config/settings.json"),
// )
// .expect("error parsing settings file");
// info!("{settings:?}");
//
// let (run_api, run_bot) = (settings.run_api, settings.run_bot);
// let db = Arc::new(tokio::sync::Mutex::new(
// db::Database::new("./config/db.sqlite").expect("couldn't open sqlite db"),
// ));
//
// {
// // attempt to initialize the database with the schema
// let db = db.lock().await;
// db.init().expect("couldn't init db");
// }
//
// if run_api {
// spawn_api(db.clone());
// }
// if run_bot {
// spawn_bot(db).await;
// }
//
// info!("spawned background tasks");
//
// let _ = tokio::signal::ctrl_c().await;
// info!("Received Ctrl-C, shuttdown down.");
//
// Ok(())
}