rustolio-db 0.1.0

An DB extention for the rustolio HTTP-Server
Documentation
//
// SPDX-License-Identifier: MPL-2.0
//
// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

use std::collections::HashMap;

use rustolio_rpc::prelude::*;
use rustolio_utils::{
    bytes::{
        encoding::{self, encode_to_bytes},
        hex, Bytes,
    },
    crypto,
};
use rustolio_web::prelude::*;
use serde::de;

use crate::{client::CLIENT, Key, Value};

const DB_TEAMS: &str = "DB_TEAMS";

global! {
    static TEAMS: GlobalSignal<Teams> = GlobalSignal::new(Teams(HashMap::new()));
    Effect::new(|| {
        Teams::init().unwrap();
    });
}

#[derive(Clone)]
pub struct Teams(HashMap<crate::Key, (crypto::encryption::Cipher, crypto::encryption::Key)>);

impl Teams {
    fn init() -> rustolio_web::Result<()> {
        let storage = super::storage()?;
        let Some(teams) = storage.get_item(DB_TEAMS).unwrap() else {
            return Ok(());
        };
        let teams: HashMap<crate::Key, (crypto::encryption::Cipher, crypto::encryption::Key)> =
            encoding::decode_from_bytes::<Vec<(crate::Key, crypto::encryption::Key)>>(
                hex::decode(&teams).context("Failed to decode teams")?,
            )
            .context("Failed to decode teams")?
            .into_iter()
            .map(|(k, ek)| {
                let c = crypto::encryption::Cipher::new(&ek);
                Ok((k, (c, ek)))
            })
            .collect::<rustolio_web::Result<_>>()?;

        TEAMS.update(|t| t.0 = teams);

        Ok(())
    }

    fn save(
        teams: impl IntoIterator<Item = (crate::Key, crypto::encryption::Key)>,
    ) -> rustolio_web::Result<()> {
        let storage = super::storage()?;

        let teams: Vec<_> = teams.into_iter().collect();
        let teams =
            hex::encode(encode_to_bytes(&teams).context("Failed to encode teams to bytes")?);
        storage.set(DB_TEAMS, &teams)?;

        Ok(())
    }

    pub(super) fn login(client: super::Client) -> rustolio_web::Result<()> {
        let encrypted_teams = todo!();

        let teams = {
            let password_hash = crypto::hash::Hasher::once_raw(password.as_bytes());
            let password_key = crypto::encryption::Key::from_bytes(password_hash.as_ref())
                .context("Failed to create encryption key from password")?;
            let cipher = crypto::encryption::Cipher::new(&password_key);

            cipher
                .decrypt(encrypted_teams)
                .context("Failed to decrypt private key")?
        };

        let teams: Vec<(crate::Key, crypto::encryption::Key)> =
            encoding::decode_from_bytes(teams).context("Failed to decode teams")?;
        Self::save(teams);

        let teams: HashMap<crate::Key, (crypto::encryption::Cipher, crypto::encryption::Key)> =
            teams
                .into_iter()
                .map(|(k, ck)| {
                    let c = crypto::encryption::Cipher::new(&ck);
                    Ok((k, (c, ck)))
                })
                .collect::<rustolio_web::Result<_>>()?;

        TEAMS.update(|t| {
            t.0 = teams;
        });

        Ok(())
    }

    pub fn create_team(teamname: &str) -> rustolio_web::Result<()> {
        let team_key = crate::Key::builder()
            .update("teamname")
            .update(teamname)
            .build();
        let encryption_key = crypto::encryption::Key::generate()
            .context("Failed to create a team encryption key")?;

        CLIENT.update(|c| {
            let Some(c) = c else {
                return;
            };
            c.teams
                .insert(team_key, crypto::encryption::Cipher::new(&encryption_key));
        });

        // TODO: Upload

        Ok(())
    }

    pub fn add_team_member(teamname: &str) -> rustolio_web::Result<()> {}

    pub(super) fn logout() -> rustolio_web::Result<()> {
        TEAMS.update(|t| {
            t.0.clear();
        });
        let storage = super::storage()?;
        storage.delete(DB_TEAMS)?;
        Ok(())
    }
}