diff --git a/.github/actions/deploy-action/README.md b/.github/actions/deploy-action/README.md new file mode 100644 index 0000000..77bce26 --- /dev/null +++ b/.github/actions/deploy-action/README.md @@ -0,0 +1,77 @@ +# Shuttle Deploy Action + +This action automates the deployment of a Rust project to [Shuttle](https://www.shuttle.rs/). This action deploys the project to Shuttle, which builds it and hosts it. This action is a clone of the official deployment action, but having a `keep-alive` flag on deploy. + +Note that you need to have created a project on Shuttle before you can deploy to it. This action will NOT create a new project for you. +You can see the documentation on how to create a project [here](https://docs.shuttle.rs/introduction/quick-start). + +Shuttle Secrets are not handled by this action (yet). Add secrets using Secrets.toml with a manual deploy command. Read more about secrets [here](https://docs.shuttle.rs/resources/shuttle-secrets). + +## Inputs + +| Name | Description | Required | Default | +|-----------------------| --- | --- | --- | +| deploy-key | The Shuttle API key | true | N/A | +| cargo-shuttle-version | Version of cargo-shuttle | false | `""` (latest) | +| working-directory | The directory which includes the `Cargo.toml` | false | `"."` | +| name | The directory which includes the `Cargo.toml` | false | `"."` | +| allow-dirty | Allow uncommitted changes to be deployed | false | `"false"` | +| no-test | Don't run tests before deployment | false | `"false"` | +| secrets | Content of the `Secrets.toml` file, if any | false | `""` | +| keep-alive | Whether service should be kept alive. | false | `"true"` | + +## Outputs + +| Name | Description | +| --- | --- | + + +## Example usage + +### Typical Example + +```yaml +name: Shuttle Deploy + +on: + push: + branches: + - "main" + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: shuttle-hq/deploy-action@main + with: + deploy-key: ${{ secrets.SHUTTLE_DEPLOY_KEY }} +``` + +### Example with all inputs + +```yaml +name: Shuttle Deploy + +on: + push: + branches: + - "main" + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: shuttle-hq/deploy-action@main + with: + deploy-key: ${{ secrets.SHUTTLE_DEPLOY_KEY }} + working-directory: "backend" + name: "my-project" + allow-dirty: "true" + no-test: "true" + cargo-shuttle-version: "0.21.0" + secrets: | + MY_AWESOME_SECRET_1 = '${{ secrets.SECRET_1 }}' + MY_AWESOME_SECRET_2 = '${{ secrets.SECRET_2 }}' +``` diff --git a/.github/actions/deploy-action/action.yml b/.github/actions/deploy-action/action.yml new file mode 100644 index 0000000..d1ff021 --- /dev/null +++ b/.github/actions/deploy-action/action.yml @@ -0,0 +1,79 @@ +name: Shuttle Deploy +description: Deploy to Shuttle + +inputs: + deploy-key: + description: 'The key found at "https://www.shuttle.rs/login"' + required: true + cargo-shuttle-version: + description: "Version of cargo-shuttle" + required: false + default: "" + working-directory: + description: "The directory which includes the `Cargo.toml`" + required: false + default: "." + name: + description: "Name of the project (overrides crate name & Shuttle.toml)" + required: false + default: "" + allow-dirty: + description: "Allow uncommitted changes to be deployed" + required: false + default: "false" + no-test: + description: "Don't run tests before deployment" + required: false + default: "false" + secrets: + description: | + Content of the `Secrets.toml` file, if any. + Use multiline text with `|` in case of multiple secrets + required: false + default: "" + keep-alive: + description: Whether service should be kept alive. + required: false + default: "true" + +runs: + using: "composite" + steps: + # check out repo if not done already + - id: check-repo-is-not-initialized + run: echo "remote-url=$( git config --get remote.origin.url )" >> $GITHUB_OUTPUT + shell: bash + - uses: actions/checkout@v3 + if: ${{ !contains(steps.check-repo-is-not-initialized.outputs.remote-url, github.repository) }} + + # install with cargo-binstall + - name: Install cargo-binstall + run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + shell: bash + - name: Install cargo-shuttle + if: ${{ inputs.cargo-shuttle-version == '' }} + run: cargo binstall -y --locked cargo-shuttle + shell: bash + - name: Install cargo-shuttle ${{ inputs.cargo-shuttle-version }} + if: ${{ inputs.cargo-shuttle-version != '' }} + run: cargo binstall -y --locked cargo-shuttle@${{ inputs.cargo-shuttle-version }} + shell: bash + + - name: Create secret file + if: ${{ inputs.secrets != '' }} + run: echo "${{ inputs.secrets }}" > Secrets.toml + working-directory: ${{ inputs.working-directory }} + shell: bash + + - name: Deploy to Shuttle + run: | + cargo shuttle deploy \ + $(if [ "${{ inputs.name }}" != "" ]; then echo "--name ${{ inputs.name }}"; fi) \ + $(if [ "${{ inputs.allow-dirty }}" != "false" ]; then echo --allow-dirty; fi) \ + $(if [ "${{ inputs.no-test }}" != "false" ]; then echo --no-test; fi) \ + $(if [ "${{ inputs.keep-alive }}" != "false" ]; then echo --idle-minutes 0; fi) \ + | awk '!/Database URI.*?$/' + working-directory: ${{ inputs.working-directory }} + env: + SHUTTLE_API_KEY: ${{ inputs.deploy-key }} + shell: bash \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d58090d..c8f5a28 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,7 +10,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: shuttle-hq/deploy-action@main + - uses: ./.github/actions/deploy-action with: deploy-key: ${{ secrets.SHUTTLE_DEPLOY_KEY }} secrets: | diff --git a/.sqlx/query-361cba2a4dfed276a1a1a5285d98faf9c4bc74f27309f7f4bd590fab0f2cf9c0.json b/.sqlx/query-361cba2a4dfed276a1a1a5285d98faf9c4bc74f27309f7f4bd590fab0f2cf9c0.json new file mode 100644 index 0000000..005801e --- /dev/null +++ b/.sqlx/query-361cba2a4dfed276a1a1a5285d98faf9c4bc74f27309f7f4bd590fab0f2cf9c0.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT r.id\n FROM runs r\n JOIN users u on u.id = r.user_id\n WHERE u.telegram_userid = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "361cba2a4dfed276a1a1a5285d98faf9c4bc74f27309f7f4bd590fab0f2cf9c0" +} diff --git a/.sqlx/query-fa077bf85599d24f2961d5ab08221ba5ccfb2f24b16be8717e9c6c99e27a004a.json b/.sqlx/query-5a3d0c9d6fc2abb00f55f0815326ab31fcabe74adf4eb3d6d20af3ca7d6e54dd.json similarity index 58% rename from .sqlx/query-fa077bf85599d24f2961d5ab08221ba5ccfb2f24b16be8717e9c6c99e27a004a.json rename to .sqlx/query-5a3d0c9d6fc2abb00f55f0815326ab31fcabe74adf4eb3d6d20af3ca7d6e54dd.json index 8632299..094edbf 100644 --- a/.sqlx/query-fa077bf85599d24f2961d5ab08221ba5ccfb2f24b16be8717e9c6c99e27a004a.json +++ b/.sqlx/query-5a3d0c9d6fc2abb00f55f0815326ab31fcabe74adf4eb3d6d20af3ca7d6e54dd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, chat_id, user_name\n FROM users\n WHERE user_name = $1 AND chat_id = $2", + "query": "SELECT id, telegram_userid, chat_id, user_name\n FROM users\n WHERE user_name = $1 AND telegram_userid = $2 AND chat_id = $3", "describe": { "columns": [ { @@ -10,11 +10,16 @@ }, { "ordinal": 1, + "name": "telegram_userid", + "type_info": "Int8" + }, + { + "ordinal": 2, "name": "chat_id", "type_info": "Varchar" }, { - "ordinal": 2, + "ordinal": 3, "name": "user_name", "type_info": "Varchar" } @@ -22,14 +27,16 @@ "parameters": { "Left": [ "Text", + "Int8", "Text" ] }, "nullable": [ + false, false, false, false ] }, - "hash": "fa077bf85599d24f2961d5ab08221ba5ccfb2f24b16be8717e9c6c99e27a004a" + "hash": "5a3d0c9d6fc2abb00f55f0815326ab31fcabe74adf4eb3d6d20af3ca7d6e54dd" } diff --git a/.sqlx/query-0125034b9591596868a24565e0827028d1a6684dc1dfc67bbdfdb4884fe7ce87.json b/.sqlx/query-6f89962823361c56de89197f98752f621258fc4360146302b812ad0385374ffa.json similarity index 61% rename from .sqlx/query-0125034b9591596868a24565e0827028d1a6684dc1dfc67bbdfdb4884fe7ce87.json rename to .sqlx/query-6f89962823361c56de89197f98752f621258fc4360146302b812ad0385374ffa.json index b42daba..c63a5a5 100644 --- a/.sqlx/query-0125034b9591596868a24565e0827028d1a6684dc1dfc67bbdfdb4884fe7ce87.json +++ b/.sqlx/query-6f89962823361c56de89197f98752f621258fc4360146302b812ad0385374ffa.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, chat_id, user_name\n FROM users\n WHERE chat_id = $1", + "query": "SELECT id, telegram_userid, chat_id, user_name\n FROM users\n WHERE chat_id = $1", "describe": { "columns": [ { @@ -10,11 +10,16 @@ }, { "ordinal": 1, + "name": "telegram_userid", + "type_info": "Int8" + }, + { + "ordinal": 2, "name": "chat_id", "type_info": "Varchar" }, { - "ordinal": 2, + "ordinal": 3, "name": "user_name", "type_info": "Varchar" } @@ -25,10 +30,11 @@ ] }, "nullable": [ + false, false, false, false ] }, - "hash": "0125034b9591596868a24565e0827028d1a6684dc1dfc67bbdfdb4884fe7ce87" + "hash": "6f89962823361c56de89197f98752f621258fc4360146302b812ad0385374ffa" } diff --git a/.sqlx/query-8d3aa9a674037d772157fda5e3ba14240017aefd9841188eaec4693f078c1f9b.json b/.sqlx/query-7b341520c306021a8a5df11727a82506a9d3bce64e28e44e94f4ca3bedad2ea6.json similarity index 52% rename from .sqlx/query-8d3aa9a674037d772157fda5e3ba14240017aefd9841188eaec4693f078c1f9b.json rename to .sqlx/query-7b341520c306021a8a5df11727a82506a9d3bce64e28e44e94f4ca3bedad2ea6.json index f0e106c..74ea33d 100644 --- a/.sqlx/query-8d3aa9a674037d772157fda5e3ba14240017aefd9841188eaec4693f078c1f9b.json +++ b/.sqlx/query-7b341520c306021a8a5df11727a82506a9d3bce64e28e44e94f4ca3bedad2ea6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "DELETE FROM runs\n WHERE id = $1", + "query": "DELETE FROM runs\n WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -10,5 +10,5 @@ }, "nullable": [] }, - "hash": "8d3aa9a674037d772157fda5e3ba14240017aefd9841188eaec4693f078c1f9b" + "hash": "7b341520c306021a8a5df11727a82506a9d3bce64e28e44e94f4ca3bedad2ea6" } diff --git a/.sqlx/query-6100bc13e5ae09fa0ee7de24c9d017da6761440bb56f4de704c6b536eb42c6a0.json b/.sqlx/query-ef445b2437974493992e90be55b66135a514cfabb964886617634ab0eec50494.json similarity index 50% rename from .sqlx/query-6100bc13e5ae09fa0ee7de24c9d017da6761440bb56f4de704c6b536eb42c6a0.json rename to .sqlx/query-ef445b2437974493992e90be55b66135a514cfabb964886617634ab0eec50494.json index dc65349..da58469 100644 --- a/.sqlx/query-6100bc13e5ae09fa0ee7de24c9d017da6761440bb56f4de704c6b536eb42c6a0.json +++ b/.sqlx/query-ef445b2437974493992e90be55b66135a514cfabb964886617634ab0eec50494.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE runs\n SET distance = $1\n WHERE id = $2", + "query": "UPDATE runs\n SET distance = $1\n WHERE id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "6100bc13e5ae09fa0ee7de24c9d017da6761440bb56f4de704c6b536eb42c6a0" + "hash": "ef445b2437974493992e90be55b66135a514cfabb964886617634ab0eec50494" } diff --git a/.sqlx/query-f1987324ab30e2d14e32e5c8d1963114486c92d13547c43b98419dcbd18957e8.json b/.sqlx/query-f1987324ab30e2d14e32e5c8d1963114486c92d13547c43b98419dcbd18957e8.json deleted file mode 100644 index a6a7d8f..0000000 --- a/.sqlx/query-f1987324ab30e2d14e32e5c8d1963114486c92d13547c43b98419dcbd18957e8.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (chat_id, user_name) \n VALUES ($1, $2)\n ON CONFLICT (chat_id, user_name) DO NOTHING", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "f1987324ab30e2d14e32e5c8d1963114486c92d13547c43b98419dcbd18957e8" -} diff --git a/.sqlx/query-fec91934b8594955965fadaf4c1bc0874e9e209b2dde1ef6515224561e477ede.json b/.sqlx/query-fec91934b8594955965fadaf4c1bc0874e9e209b2dde1ef6515224561e477ede.json new file mode 100644 index 0000000..5e58c46 --- /dev/null +++ b/.sqlx/query-fec91934b8594955965fadaf4c1bc0874e9e209b2dde1ef6515224561e477ede.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO users (telegram_userid, chat_id, user_name) \n VALUES ($1, $2, $3)\n ON CONFLICT (telegram_userid, chat_id, user_name) DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "fec91934b8594955965fadaf4c1bc0874e9e209b2dde1ef6515224561e477ede" +} diff --git a/migrations/20230826131114_schema.sql b/migrations/20230826131114_schema.sql new file mode 100644 index 0000000..b817f4c --- /dev/null +++ b/migrations/20230826131114_schema.sql @@ -0,0 +1,5 @@ +-- Add migration script here +ALTER TABLE users +ADD COLUMN telegram_userid BIGINT NOT NULL DEFAULT 0; +ALTER TABLE users +ALTER COLUMN telegram_userid DROP DEFAULT; \ No newline at end of file diff --git a/src/bot.rs b/src/bot.rs index 3b32a1f..b6d5715 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -62,17 +62,12 @@ enum Command { #[command(description = "Show users registered on telerun within the chat. Usage: /show")] /// Matched to `/show` -> displays users within chat. Show, - /// Matched to `/add ` -> creates users in db if not present, + /// Matched to `/add ` -> creates users in db if not present, /// then adds run data to runs table. - #[command( - description = "Add run data to database. Usage: /add ", - parse_with = "split" - )] + #[command(description = "Add run data to database. Usage: /add ")] Add { /// Distance run in km distance: f32, - /// Name of user to tie the run to. Must be unique. - user_name: String, }, /// Matched to `/edit ` -> edits stored run data. #[command( @@ -123,50 +118,71 @@ async fn answer(bot: Bot, msg: Message, cmd: Command, db_connection: PgPool) -> error!("Unable to retrieve items required for Show."); } } - Command::Add { - distance, - user_name, - } => { - let add_result = - add_run_wrapper(distance, user_name.as_str(), msg.chat.id, &db_connection).await; - if add_result.is_ok() { - bot.send_message( - msg.chat.id, - format!("{} ran {}km added to database.", user_name, distance), - ) - .await - .map_err(|error| error!("Unable to send Add message: {:?}", error)) - .ok(); + Command::Add { distance } => { + let telegram_user = msg.from(); + if let Some(user) = telegram_user { + let user_name = &user.username; + if let Some(user_name) = user_name { + let add_result = add_run_wrapper( + distance, + user_name.as_str(), + user.id, + msg.chat.id, + &db_connection, + ) + .await; + if add_result.is_ok() { + bot.send_message( + msg.chat.id, + format!("{} ran {}km added to database.", user_name, distance), + ) + .await + .map_err(|error| error!("Unable to send Add message: {:?}", error)) + .ok(); + } else { + error!("Unable to Add run information."); + } + } else { + error!("Unable to retrieve username information from Telegram."); + } } else { - error!("Unable to Add run information."); + error!("Unable to retrieve user from message."); } } Command::Edit { run_id, distance } => { - let update_outcome = update_run(run_id, distance, &db_connection).await; - if update_outcome.is_ok() { - bot.send_message( - msg.chat.id, - format!( - "Run {} successfully updated with distance {}km.", - run_id, distance - ), - ) - .await - .map_err(|error| error!("Unable to send update message: {:?}", error)) - .ok(); + let telegram_user = msg.from(); + if let Some(user) = telegram_user { + let update_outcome = update_run(run_id, user.id, distance, &db_connection).await; + if update_outcome.is_ok() { + bot.send_message( + msg.chat.id, + format!( + "Run {} successfully updated with distance {}km.", + run_id, distance + ), + ) + .await + .map_err(|error| error!("Unable to send update message: {:?}", error)) + .ok(); + } else { + error!("Unable to update database entry for run_id: {}", run_id); + } } else { - error!("Unable to update database entry for run_id: {}", run_id); + error!("Unable to retrieve user information from Telegram."); } } Command::Delete { run_id } => { - let delete_outcome = delete_run(run_id, &db_connection).await; - if delete_outcome.is_ok() { - bot.send_message(msg.chat.id, format!("Run {} successfully deleted!", run_id)) - .await - .map_err(|error| error!("Unable to send delete message: {:?}", error)) - .ok(); - } else { - error!("Unable to delete entry from database."); + let telegram_user = msg.from(); + if let Some(user) = telegram_user { + let delete_outcome = delete_run(run_id, user.id, &db_connection).await; + if delete_outcome.is_ok() { + bot.send_message(msg.chat.id, format!("Run {} successfully deleted!", run_id)) + .await + .map_err(|error| error!("Unable to send delete message: {:?}", error)) + .ok(); + } else { + error!("Unable to delete entry from database."); + } } } Command::Tally => { diff --git a/src/database.rs b/src/database.rs index be9c4f4..05b8122 100644 --- a/src/database.rs +++ b/src/database.rs @@ -5,8 +5,8 @@ //! database at compile time. use crate::models::{Run, Score, User}; use sqlx::PgPool; -use teloxide::types::ChatId; -use tracing::error; +use teloxide::types::{ChatId, UserId}; +use tracing::{error, info}; /// Convenience type to wrap a generic `Ok` and `sqlx::Error`. type DBResult = Result; @@ -15,11 +15,17 @@ type DBResult = Result; /// /// Users are tied to the `chat_id` that the message came from /// and the `user_name` input. This combination must be unique. -pub async fn create_user(user_name: &str, chat_id: ChatId, connection: &PgPool) -> DBResult<()> { +pub async fn create_user( + user_name: &str, + telegram_userid: UserId, + chat_id: ChatId, + connection: &PgPool, +) -> DBResult<()> { sqlx::query!( - "INSERT INTO users (chat_id, user_name) - VALUES ($1, $2) - ON CONFLICT (chat_id, user_name) DO NOTHING", + "INSERT INTO users (telegram_userid, chat_id, user_name) + VALUES ($1, $2, $3) + ON CONFLICT (telegram_userid, chat_id, user_name) DO NOTHING", + telegram_userid.0 as i64, chat_id.to_string(), user_name ) @@ -31,14 +37,20 @@ pub async fn create_user(user_name: &str, chat_id: ChatId, connection: &PgPool) /// Retrieves a user. /// -/// Fetches user information based on `(user_name, chat_id)`. -async fn get_user(user_name: &str, chat_id: ChatId, connection: &PgPool) -> DBResult> { +/// Fetches user information based on `(user_name, telegram_userid, chat_id)`. +async fn get_user( + user_name: &str, + telegram_userid: UserId, + chat_id: ChatId, + connection: &PgPool, +) -> DBResult> { let user: Option = sqlx::query_as!( User, - "SELECT id, chat_id, user_name + "SELECT id, telegram_userid, chat_id, user_name FROM users - WHERE user_name = $1 AND chat_id = $2", + WHERE user_name = $1 AND telegram_userid = $2 AND chat_id = $3", user_name, + telegram_userid.0 as i64, chat_id.to_string() ) .fetch_optional(connection) @@ -55,7 +67,7 @@ pub async fn get_users_in_chat( connection: &PgPool, ) -> DBResult>> { let users: Vec = sqlx::query!( - "SELECT id, chat_id, user_name + "SELECT id, telegram_userid, chat_id, user_name FROM users WHERE chat_id = $1", chat_id.to_string() @@ -65,6 +77,7 @@ pub async fn get_users_in_chat( .iter() .map(|user_row| User { id: user_row.id, + telegram_userid: user_row.telegram_userid, chat_id: user_row.chat_id.clone(), user_name: user_row.user_name.clone(), }) @@ -82,6 +95,8 @@ pub async fn get_users_in_chat( /// # Arguments /// * `distance` - Distance run in km /// * `user_name` - Name user wishes to tie the run to. +/// * `telegram_userid` - Unique user id from Telegram. Can be retrieved +/// from `Message`. /// * `chat_id` - Unique ID identifying the chat, this comes from Telegram. /// /// # Remarks @@ -93,16 +108,17 @@ pub async fn get_users_in_chat( pub async fn add_run_wrapper( distance: f32, user_name: &str, + telegram_userid: UserId, chat_id: ChatId, connection: &PgPool, ) -> DBResult<()> { - let user = get_user(user_name, chat_id, connection).await?; + let user = get_user(user_name, telegram_userid, chat_id, connection).await?; if let Some(user) = user { add_run(distance, user.id, connection).await?; } else { - create_user(user_name, chat_id, connection).await?; - let user = get_user(user_name, chat_id, connection).await?; + create_user(user_name, telegram_userid, chat_id, connection).await?; + let user = get_user(user_name, telegram_userid, chat_id, connection).await?; if let Some(user) = user { add_run(distance, user.id, connection).await?; } else { @@ -172,31 +188,78 @@ pub async fn get_runs( } /// Updates a certain run by id. -pub async fn update_run(run_id: i32, distance: f32, connection: &PgPool) -> DBResult<()> { - sqlx::query!( - "UPDATE runs - SET distance = $1 - WHERE id = $2", - distance, - run_id, +pub async fn update_run( + run_id: i32, + telegram_userid: UserId, + distance: f32, + connection: &PgPool, +) -> DBResult<()> { + let valid_run = sqlx::query!( + "SELECT r.id + FROM runs r + JOIN users u on u.id = r.user_id + WHERE u.telegram_userid = $1", + telegram_userid.0 as i64, ) - .execute(connection) - .await?; - - Ok(()) + .fetch_optional(connection) + .await; + match valid_run { + Ok(Some(_)) => { + info!("Matched run_id: {} to user_id: {}", run_id, telegram_userid); + sqlx::query!( + "UPDATE runs + SET distance = $1 + WHERE id = $2", + distance, + run_id, + ) + .execute(connection) + .await?; + Ok(()) + } + Ok(None) => { + error!("No runs matched for the source user"); + Ok(()) + } + Err(error) => { + error!("Unable to retrieve run data: {:?}", error); + Err(error) + } + } } /// Deletes a run by id. -pub async fn delete_run(run_id: i32, connection: &PgPool) -> DBResult<()> { - sqlx::query!( - "DELETE FROM runs - WHERE id = $1", - run_id, +pub async fn delete_run(run_id: i32, telegram_userid: UserId, connection: &PgPool) -> DBResult<()> { + let valid_run = sqlx::query!( + "SELECT r.id + FROM runs r + JOIN users u on u.id = r.user_id + WHERE u.telegram_userid = $1", + telegram_userid.0 as i64, ) - .execute(connection) - .await?; - - Ok(()) + .fetch_optional(connection) + .await; + match valid_run { + Ok(Some(_)) => { + info!("Matched run_id: {} to user_id: {}", run_id, telegram_userid); + sqlx::query!( + "DELETE FROM runs + WHERE id = $1", + run_id, + ) + .execute(connection) + .await?; + Ok(()) + } + Ok(None) => { + error!("No runs matched for the source user"); + Ok(()) + } + Err(error) => { + error!("Unable to retrieve run data: {:?}", error); + Err(error) + } + } } /// Aggregates runs into a tally (`Vec`) diff --git a/src/message.rs b/src/message.rs index ee9d673..9d2c32c 100644 --- a/src/message.rs +++ b/src/message.rs @@ -169,11 +169,13 @@ mod tests { let users = vec![ User { id: 1, + telegram_userid: 1, chat_id: "chat1".into(), user_name: "meme".into(), }, User { id: 2, + telegram_userid: 2, chat_id: "chat1".into(), user_name: "youyou".into(), }, diff --git a/src/models.rs b/src/models.rs index 2d402ca..bdc4e6c 100644 --- a/src/models.rs +++ b/src/models.rs @@ -10,6 +10,11 @@ use sqlx::types::chrono; pub struct User { /// User id pub id: i32, + /// User id as seen from Telegram + /// PostgreSQL does not natively support u64 values, + /// and thus we will attempt to cast the values from Telegram + /// as i64 first before storing in DB. + pub telegram_userid: i64, /// Id of telegram chat pub chat_id: String, /// Self-specified username