oko 0.1.14

Fully local home security system.
Documentation
use serde::{Deserialize, Serialize};
use sqlx::{Result, SqlitePool};

use super::{CameraPermissionUserView, Model};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraPermission {
    pub permission_id: i64,
    pub camera_id: i64,
    pub user_id: i64,
    pub can_view: bool,
    pub can_control: bool,
}

pub struct Default {
    pub permission_id: i64,
    pub can_view: bool,
    pub can_control: bool,
}

impl Model for CameraPermission {
    type Default = Default;
    const DEFAULT: Default = Default {
        permission_id: -1,
        can_view: true,
        can_control: false,
    };

    async fn create_using_self(&mut self, pool: &SqlitePool) -> Result<()> {
        let result = sqlx::query!(
            r#"
            INSERT INTO camera_permissions (camera_id, user_id, can_view, can_control)
            VALUES (?, ?, ?, ?)
            RETURNING permission_id
            "#,
            self.camera_id,
            self.user_id,
            self.can_view,
            self.can_control
        )
        .fetch_one(pool)
        .await?;

        self.permission_id = result.permission_id;

        Ok(())
    }

    async fn get_using_id(pool: &SqlitePool, permission_id: i64) -> Result<Self> {
        sqlx::query_as!(
            CameraPermission,
            r#"
            SELECT *
            FROM camera_permissions
            WHERE permission_id = ?
            "#,
            permission_id
        )
        .fetch_one(pool)
        .await
    }

    async fn update_using_self(&self, pool: &SqlitePool) -> Result<()> {
        sqlx::query!(
            r#"
            UPDATE camera_permissions
            SET can_view = ?, can_control = ?
            WHERE permission_id = ?
            RETURNING permission_id
            "#,
            self.can_view,
            self.can_control,
            self.permission_id
        )
        .fetch_one(pool)
        .await?;

        Ok(())
    }

    async fn delete_using_id(pool: &SqlitePool, permission_id: i64) -> Result<()> {
        sqlx::query!(
            r#"
            DELETE
            FROM camera_permissions
            WHERE permission_id = ?
            RETURNING permission_id
            "#,
            permission_id
        )
        .fetch_one(pool)
        .await?;

        Ok(())
    }
}

impl CameraPermission {
    pub async fn list_for_camera(pool: &SqlitePool, camera_id: i64) -> Result<Vec<Self>> {
        sqlx::query_as!(
            CameraPermission,
            r#"
            SELECT *
            FROM camera_permissions
            WHERE camera_id = ?
            "#,
            camera_id
        )
        .fetch_all(pool)
        .await
    }

    pub async fn list_for_camera_with_username(
        pool: &SqlitePool,
        camera_id: i64,
    ) -> Result<Vec<CameraPermissionUserView>> {
        sqlx::query_as!(
            CameraPermissionUserView,
            r#"
            SELECT cp.permission_id, cp.camera_id, cp.user_id, u.username, cp.can_view, cp.can_control
            FROM camera_permissions cp
            JOIN users u ON cp.user_id = u.user_id
            WHERE cp.camera_id = ?
            "#,
            camera_id
        )
        .fetch_all(pool)
        .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[sqlx::test(fixtures(
        path = "../../fixtures",
        scripts("users", "cameras", "camera_permissions")
    ))]
    async fn create(pool: SqlitePool) -> Result<()> {
        let mut camera_permission = CameraPermission {
            permission_id: CameraPermission::DEFAULT.permission_id,
            camera_id: 1,
            user_id: 1,
            can_view: true,
            can_control: false,
        };

        camera_permission.create_using_self(&pool).await?;

        assert_eq!(camera_permission.permission_id, 9);

        let returned_permission =
            CameraPermission::get_using_id(&pool, camera_permission.permission_id).await?;

        assert_eq!(returned_permission.camera_id, camera_permission.camera_id);
        assert_eq!(returned_permission.user_id, camera_permission.user_id);
        assert_eq!(returned_permission.can_view, camera_permission.can_view);
        assert_eq!(
            returned_permission.can_control,
            camera_permission.can_control
        );

        Ok(())
    }

    #[sqlx::test(fixtures(
        path = "../../fixtures",
        scripts("users", "cameras", "camera_permissions")
    ))]
    async fn get(pool: SqlitePool) -> Result<()> {
        let permission_id = 1;

        let returned_permission = CameraPermission::get_using_id(&pool, permission_id).await?;

        assert_eq!(returned_permission.permission_id, permission_id);
        assert_eq!(returned_permission.camera_id, 1);
        assert_eq!(returned_permission.user_id, 1);
        assert!(returned_permission.can_view);
        assert!(returned_permission.can_control);

        Ok(())
    }

    #[sqlx::test(fixtures(
        path = "../../fixtures",
        scripts("users", "cameras", "camera_permissions")
    ))]
    async fn update(pool: SqlitePool) -> Result<()> {
        let old_camera_permission = CameraPermission::get_using_id(&pool, 1).await?;

        let new_camera_permission = CameraPermission {
            permission_id: old_camera_permission.permission_id,
            camera_id: old_camera_permission.camera_id,
            user_id: old_camera_permission.user_id,
            can_view: false,
            can_control: true,
        };

        let updated = new_camera_permission.update_using_self(&pool).await;

        assert!(updated.is_ok());

        let returned_permission =
            CameraPermission::get_using_id(&pool, old_camera_permission.permission_id).await?;

        assert_eq!(
            returned_permission.camera_id,
            new_camera_permission.camera_id
        );
        assert_eq!(returned_permission.user_id, new_camera_permission.user_id);
        assert_eq!(returned_permission.can_view, new_camera_permission.can_view);
        assert_eq!(
            returned_permission.can_control,
            new_camera_permission.can_control
        );

        Ok(())
    }

    #[sqlx::test(fixtures(
        path = "../../fixtures",
        scripts("users", "cameras", "camera_permissions")
    ))]
    async fn delete(pool: SqlitePool) -> Result<()> {
        let permission_id = 1;

        let deleted = CameraPermission::delete_using_id(&pool, permission_id).await;

        assert!(deleted.is_ok());

        let returned_permission = CameraPermission::get_using_id(&pool, permission_id).await;

        assert!(returned_permission.is_err());

        let impossible_deleted = CameraPermission::delete_using_id(&pool, permission_id).await;

        assert!(impossible_deleted.is_err());

        Ok(())
    }

    #[sqlx::test(fixtures(
        path = "../../fixtures",
        scripts("users", "cameras", "camera_permissions")
    ))]
    async fn list_for_camera(pool: SqlitePool) -> Result<()> {
        let camera_id = 1;

        let returned_permissions = CameraPermission::list_for_camera(&pool, camera_id).await?;
        assert_eq!(returned_permissions.len(), 4);

        let permission_ids: Vec<i64> = returned_permissions
            .iter()
            .map(|permission| permission.permission_id)
            .collect();

        assert!(permission_ids.contains(&1));
        assert!(permission_ids.contains(&3));
        assert!(permission_ids.contains(&5));
        assert!(permission_ids.contains(&7));

        Ok(())
    }

    #[sqlx::test(fixtures(
        path = "../../fixtures",
        scripts("users", "cameras", "camera_permissions")
    ))]
    async fn list_for_camera_with_username(pool: SqlitePool) -> Result<()> {
        let camera_id = 1;

        let returned_permissions =
            CameraPermission::list_for_camera_with_username(&pool, camera_id).await?;
        assert_eq!(returned_permissions.len(), 4);

        let permission_ids: Vec<i64> = returned_permissions
            .iter()
            .map(|permission| permission.permission_id)
            .collect();

        assert!(permission_ids.contains(&1));
        assert!(permission_ids.contains(&3));
        assert!(permission_ids.contains(&5));
        assert!(permission_ids.contains(&7));

        let usernames: Vec<&str> = returned_permissions
            .iter()
            .map(|permission| permission.username.as_str())
            .collect();

        assert!(usernames.contains(&"piotrpdev"));
        assert!(usernames.contains(&"joedaly"));
        assert!(usernames.contains(&"admin"));
        assert!(usernames.contains(&"guest"));

        Ok(())
    }
}