diff options
| -rw-r--r-- | .sqlx/query-18f5f8a76476205ec485398a7cdbca5582dde4a67c06d2134f33efcde194dcfe.json | 20 | ||||
| -rw-r--r-- | .sqlx/query-8be5e4fd9a27f27efc9a45de63396990a8cadb7dd6ac84cfeb7fc8770f125190.json | 26 | ||||
| -rw-r--r-- | src/channel/repo.rs | 28 | ||||
| -rw-r--r-- | src/channel/routes.rs | 18 | ||||
| -rw-r--r-- | src/index.rs | 43 |
5 files changed, 125 insertions, 10 deletions
diff --git a/.sqlx/query-18f5f8a76476205ec485398a7cdbca5582dde4a67c06d2134f33efcde194dcfe.json b/.sqlx/query-18f5f8a76476205ec485398a7cdbca5582dde4a67c06d2134f33efcde194dcfe.json new file mode 100644 index 0000000..1411ea6 --- /dev/null +++ b/.sqlx/query-18f5f8a76476205ec485398a7cdbca5582dde4a67c06d2134f33efcde194dcfe.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n delete\n from channel_member\n where channel = $1\n and login = $2\n returning 1 as \"deleted: bool\"\n ", + "describe": { + "columns": [ + { + "name": "deleted: bool", + "ordinal": 0, + "type_info": "Null" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + null + ] + }, + "hash": "18f5f8a76476205ec485398a7cdbca5582dde4a67c06d2134f33efcde194dcfe" +} diff --git a/.sqlx/query-8be5e4fd9a27f27efc9a45de63396990a8cadb7dd6ac84cfeb7fc8770f125190.json b/.sqlx/query-8be5e4fd9a27f27efc9a45de63396990a8cadb7dd6ac84cfeb7fc8770f125190.json new file mode 100644 index 0000000..9d75dac --- /dev/null +++ b/.sqlx/query-8be5e4fd9a27f27efc9a45de63396990a8cadb7dd6ac84cfeb7fc8770f125190.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n select\n channel.id as \"id: Id\",\n channel.name\n from channel\n except\n select\n channel.id as \"id: Id\",\n channel.name\n from channel\n join channel_member\n on (channel.id = channel_member.channel)\n where channel_member.login = $1\n order by channel.name\n ", + "describe": { + "columns": [ + { + "name": "id: Id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "8be5e4fd9a27f27efc9a45de63396990a8cadb7dd6ac84cfeb7fc8770f125190" +} diff --git a/src/channel/repo.rs b/src/channel/repo.rs index bb39d6e..a255305 100644 --- a/src/channel/repo.rs +++ b/src/channel/repo.rs @@ -62,7 +62,7 @@ impl<'c> Channels<'c> { Ok(()) } - pub async fn for_login(&mut self, login: &LoginId) -> Result<Vec<Channel>, BoxedError> { + pub async fn joined(&mut self, login: &LoginId) -> Result<Vec<Channel>, BoxedError> { let channels = sqlx::query_as!( Channel, r#" @@ -83,6 +83,32 @@ impl<'c> Channels<'c> { Ok(channels) } + pub async fn unjoined(&mut self, login: &LoginId) -> Result<Vec<Channel>, BoxedError> { + let channels = sqlx::query_as!( + Channel, + r#" + select + channel.id as "id: Id", + channel.name + from channel + except + select + channel.id as "id: Id", + channel.name + from channel + join channel_member + on (channel.id = channel_member.channel) + where channel_member.login = $1 + order by channel.name + "#, + login, + ) + .fetch_all(&mut *self.0) + .await?; + + Ok(channels) + } + /// Unenrol a login from a channel. pub async fn leave(&mut self, channel: &Id, login: &LoginId) -> Result<(), BoxedError> { sqlx::query_scalar!( diff --git a/src/channel/routes.rs b/src/channel/routes.rs index 4453a1e..3dc2b1a 100644 --- a/src/channel/routes.rs +++ b/src/channel/routes.rs @@ -12,6 +12,7 @@ use crate::{error::InternalError, login::repo::logins::Login}; pub fn router() -> Router<SqlitePool> { Router::new() .route("/create", post(on_create)) + .route("/join", post(on_join)) .route("/:channel/leave", post(on_leave)) } @@ -33,6 +34,23 @@ async fn on_create( Ok(Redirect::to("/")) } +#[derive(serde::Deserialize)] +struct JoinRequest { + channel: ChannelId, +} + +async fn on_join( + State(db): State<SqlitePool>, + login: Login, + Form(req): Form<JoinRequest>, +) -> Result<impl IntoResponse, InternalError> { + let mut tx = db.begin().await?; + tx.channels().join(&req.channel, &login.id).await?; + tx.commit().await?; + + Ok(Redirect::to("/")) +} + async fn on_leave( State(db): State<SqlitePool>, login: Login, diff --git a/src/index.rs b/src/index.rs index 605d9f6..9de91d5 100644 --- a/src/index.rs +++ b/src/index.rs @@ -16,10 +16,15 @@ async fn index( async fn index_authenticated(db: SqlitePool, login: Login) -> Result<Markup, InternalError> { let mut tx = db.begin().await?; - let channels = tx.channels().for_login(&login.id).await?; + let joined_channels = tx.channels().joined(&login.id).await?; + let unjoined_channels = tx.channels().unjoined(&login.id).await?; tx.commit().await?; - Ok(templates::authenticated(login, &channels)) + Ok(templates::authenticated( + login, + &joined_channels, + &unjoined_channels, + )) } pub fn router() -> Router<SqlitePool> { @@ -33,7 +38,8 @@ mod templates { pub fn authenticated<'c>( login: Login, - channels: impl IntoIterator<Item = &'c Channel>, + joined_channels: impl IntoIterator<Item = &'c Channel>, + unjoined_channels: impl IntoIterator<Item = &'c Channel>, ) -> Markup { html! { (DOCTYPE) @@ -42,7 +48,8 @@ mod templates { } body { section { - (channel_list(channels)) + (channel_list(joined_channels)) + (join_channel(unjoined_channels)) (create_channel()) } section { @@ -52,21 +59,21 @@ mod templates { } } - fn channel_list<'c>(channels: impl IntoIterator<Item = &'c Channel>) -> Markup { + fn channel_list<'c>(joined_channels: impl IntoIterator<Item = &'c Channel>) -> Markup { html! { ul { - @for channel in channels { - (channel_entry(&channel)) + @for channel in joined_channels { + (joined_channel_entry(&channel)) } } } } - fn channel_entry(channel: &Channel) -> Markup { + fn joined_channel_entry(channel: &Channel) -> Markup { let leave_url = format!("/{}/leave", channel.id); html! { li { - (channel.name) "(" (channel.id) ")" + (channel.name) " (" (channel.id) ")" form action=(leave_url) method="post" { button { "leave" @@ -76,6 +83,24 @@ mod templates { } } + fn join_channel<'c>(unjoined_channels: impl IntoIterator<Item = &'c Channel>) -> Markup { + html! { + form action="join" method="post" { + select name="channel" required { + option value="" { "channel" } + @for channel in unjoined_channels { + option value=(channel.id) { + (channel.name) " (" (channel.id) ")" + } + } + } + button { + "join" + } + } + } + } + fn create_channel() -> Markup { html! { form action="/create" method="post" { |
