feat(cli): `user join` — rattacher un compte existant à une équipeUnverified
Claude-Session: https://claude.ai/code/session_01FPKHKGfqvnvXKY9tNhYmkh
d4a0a1e parent: 97afc3b modified
src/cli.rs +28 -1 | @@ -6,7 +6,7 @@ use uuid::Uuid; | ||
| 6 | 6 | |
| 7 | 7 | use crate::auth::password; |
| 8 | 8 | use crate::config; |
| 9 | -use crate::db::teams; | |
| 9 | +use crate::db::{teams, users}; | |
| 10 | 10 | |
| 11 | 11 | /// Génère un keypair racine et l'affiche (à faire une fois, en local). |
| 12 | 12 | pub fn keygen() { |
| @@ -97,3 +97,30 @@ pub async fn user_add(username: &str, password_arg: Option<String>, team: &str) | ||
| 97 | 97 | println!("Utilisateur « {username} » créé ({id}), membre de « {} ».", team.trim()); |
| 98 | 98 | Ok(()) |
| 99 | 99 | } |
| 100 | + | |
| 101 | +/// `time-time user join <username> --team <nom>` — rattache un compte | |
| 102 | +/// existant à une équipe (équivalent CLI d'une invitation acceptée). | |
| 103 | +pub async fn user_join(username: &str, team: &str) -> Result<()> { | |
| 104 | + let username = username.trim(); | |
| 105 | + let team = team.trim(); | |
| 106 | + let pool = connect().await?; | |
| 107 | + let user = users::find_by_username(&pool, username) | |
| 108 | + .await? | |
| 109 | + .with_context(|| format!("l'utilisateur « {username} » n'existe pas"))?; | |
| 110 | + let team_id = teams::find_by_name(&pool, team) | |
| 111 | + .await? | |
| 112 | + .with_context(|| format!("l'équipe « {team} » n'existe pas (time-time team add)"))?; | |
| 113 | + let res = sqlx::query( | |
| 114 | + "INSERT INTO team_members (team_id, user_id) VALUES ($1, $2) ON CONFLICT DO NOTHING", | |
| 115 | + ) | |
| 116 | + .bind(team_id) | |
| 117 | + .bind(user.id) | |
| 118 | + .execute(&pool) | |
| 119 | + .await?; | |
| 120 | + if res.rows_affected() == 0 { | |
| 121 | + println!("« {username} » est déjà membre de « {team} »."); | |
| 122 | + } else { | |
| 123 | + println!("« {username} » ajouté à « {team} »."); | |
| 124 | + } | |
| 125 | + Ok(()) | |
| 126 | +} | |
| @@ -6,7 +6,7 @@ use uuid::Uuid; | |||
| 6 | 6 | ||
| 7 | use crate::auth::password; | 7 | use crate::auth::password; |
| 8 | use crate::config; | 8 | use crate::config; |
| 9 | -use crate::db::teams; | 9 | +use crate::db::{teams, users}; |
| 10 | 10 | ||
| 11 | /// Génère un keypair racine et l'affiche (à faire une fois, en local). | 11 | /// Génère un keypair racine et l'affiche (à faire une fois, en local). |
| 12 | pub fn keygen() { | 12 | pub fn keygen() { |
| @@ -97,3 +97,30 @@ pub async fn user_add(username: &str, password_arg: Option<String>, team: &str) | |||
| 97 | println!("Utilisateur « {username} » créé ({id}), membre de « {} ».", team.trim()); | 97 | println!("Utilisateur « {username} » créé ({id}), membre de « {} ».", team.trim()); |
| 98 | Ok(()) | 98 | Ok(()) |
| 99 | } | 99 | } |
| 100 | + | ||
| 101 | +/// `time-time user join <username> --team <nom>` — rattache un compte | ||
| 102 | +/// existant à une équipe (équivalent CLI d'une invitation acceptée). | ||
| 103 | +pub async fn user_join(username: &str, team: &str) -> Result<()> { | ||
| 104 | + let username = username.trim(); | ||
| 105 | + let team = team.trim(); | ||
| 106 | + let pool = connect().await?; | ||
| 107 | + let user = users::find_by_username(&pool, username) | ||
| 108 | + .await? | ||
| 109 | + .with_context(|| format!("l'utilisateur « {username} » n'existe pas"))?; | ||
| 110 | + let team_id = teams::find_by_name(&pool, team) | ||
| 111 | + .await? | ||
| 112 | + .with_context(|| format!("l'équipe « {team} » n'existe pas (time-time team add)"))?; | ||
| 113 | + let res = sqlx::query( | ||
| 114 | + "INSERT INTO team_members (team_id, user_id) VALUES ($1, $2) ON CONFLICT DO NOTHING", | ||
| 115 | + ) | ||
| 116 | + .bind(team_id) | ||
| 117 | + .bind(user.id) | ||
| 118 | + .execute(&pool) | ||
| 119 | + .await?; | ||
| 120 | + if res.rows_affected() == 0 { | ||
| 121 | + println!("« {username} » est déjà membre de « {team} »."); | ||
| 122 | + } else { | ||
| 123 | + println!("« {username} » ajouté à « {team} »."); | ||
| 124 | + } | ||
| 125 | + Ok(()) | ||
| 126 | +} | ||
modified
src/main.rs +10 -0 | @@ -52,6 +52,13 @@ enum UserCommand { | ||
| 52 | 52 | #[arg(long)] |
| 53 | 53 | team: String, |
| 54 | 54 | }, |
| 55 | + /// Ajoute un utilisateur existant à une équipe | |
| 56 | + Join { | |
| 57 | + username: String, | |
| 58 | + /// Équipe à rejoindre (doit exister) | |
| 59 | + #[arg(long)] | |
| 60 | + team: String, | |
| 61 | + }, | |
| 55 | 62 | } |
| 56 | 63 | |
| 57 | 64 | #[derive(Subcommand)] |
| @@ -76,6 +83,9 @@ async fn main() -> Result<()> { | ||
| 76 | 83 | team, |
| 77 | 84 | }, |
| 78 | 85 | }) => cli::user_add(&username, password, &team).await, |
| 86 | + Some(Command::User { | |
| 87 | + command: UserCommand::Join { username, team }, | |
| 88 | + }) => cli::user_join(&username, &team).await, | |
| 79 | 89 | Some(Command::Team { |
| 80 | 90 | command: TeamCommand::Add { name }, |
| 81 | 91 | }) => cli::team_add(&name).await, |
| @@ -52,6 +52,13 @@ enum UserCommand { | |||
| 52 | #[arg(long)] | 52 | #[arg(long)] |
| 53 | team: String, | 53 | team: String, |
| 54 | }, | 54 | }, |
| 55 | + /// Ajoute un utilisateur existant à une équipe | ||
| 56 | + Join { | ||
| 57 | + username: String, | ||
| 58 | + /// Équipe à rejoindre (doit exister) | ||
| 59 | + #[arg(long)] | ||
| 60 | + team: String, | ||
| 61 | + }, | ||
| 55 | } | 62 | } |
| 56 | 63 | ||
| 57 | #[derive(Subcommand)] | 64 | #[derive(Subcommand)] |
| @@ -76,6 +83,9 @@ async fn main() -> Result<()> { | |||
| 76 | team, | 83 | team, |
| 77 | }, | 84 | }, |
| 78 | }) => cli::user_add(&username, password, &team).await, | 85 | }) => cli::user_add(&username, password, &team).await, |
| 86 | + Some(Command::User { | ||
| 87 | + command: UserCommand::Join { username, team }, | ||
| 88 | + }) => cli::user_join(&username, &team).await, | ||
| 79 | Some(Command::Team { | 89 | Some(Command::Team { |
| 80 | command: TeamCommand::Add { name }, | 90 | command: TeamCommand::Add { name }, |
| 81 | }) => cli::team_add(&name).await, | 91 | }) => cli::team_add(&name).await, |