2 Commits

Author SHA1 Message Date
fb03a110ed allow to migrate tables 2022-05-26 17:03:55 +03:00
473953fc9c save users to sqlite 2022-05-26 12:19:47 +03:00
10 changed files with 2150 additions and 107 deletions

View File

@ -7,9 +7,12 @@ platform:
os: linux os: linux
arch: arm arch: arm
clone:
skip_verify: true
steps: steps:
- name: validate - name: validate
image: rust:1.52 image: rust:1.49
commands: commands:
- cargo test --release --target=armv7-unknown-linux-gnueabihf - cargo test --release --target=armv7-unknown-linux-gnueabihf
environment: environment:

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target /target
Cargo.lock
.testdata .testdata
.vscode .vscode

2103
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
[package] [package]
name = "altherego" name = "altherego"
version = "0.9.9" version = "0.9.9"
authors = ["Aleksandr Trushkin <atrushkin@outlook.com>"] authors = ["Aleksandr Trushkin <aleksandr.trushkin@rt.ru>"]
edition = "2018" edition = "2018"
default-run = "altherego"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
teloxide = { version = "0.12.2", features = ["macros", "auto-send"] } teloxide = { version = "0.9.0", features = ["macros", "auto-send"] }
tokio = {version = "1.8", features = ["full"]} tokio = {version = "1.8", features = ["full"]}
uuid = { version = "0.8.1", features = ["v4"] } uuid = { version = "0.8.1", features = ["v4"] }
log = "0.4" log = "0.4"
@ -24,13 +23,5 @@ async-trait = "0.1.53"
tokio-stream = "0.1.8" tokio-stream = "0.1.8"
rand = "0.8.5" rand = "0.8.5"
[[bin]]
name = "altherego"
path = "src/main.rs"
[[bin]]
name = "migrator"
path = "src/migrator/main.rs"
[profile.dev.package.sqlx-macros] [profile.dev.package.sqlx-macros]
opt-level = 3 opt-level = 3

View File

@ -1,12 +1,12 @@
use std::env; use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() {
let rev = get_value_from_env("GIT_VERSION") let rev = get_value_from_env("GIT_VERSION")
.or_else(|| get_value_from_command("git", ["rev-parse", "--short", "HEAD"])) .or_else(|| get_value_from_command("git", &["rev-parse", "--short", "HEAD"]))
.unwrap_or_else(|| "unknown".to_owned()); .unwrap_or_else(|| "unknown".to_owned());
let branch = get_value_from_env("GIT_BRANCH") let branch = get_value_from_env("GIT_BRANCH")
.or_else(|| get_value_from_command("git", ["rev-parse", "--abbrev-ref", "HEAD"])) .or_else(|| get_value_from_command("git", &["rev-parse", "--abbrev-ref", "HEAD"]))
.unwrap_or_else(|| "unknown".to_owned()); .unwrap_or_else(|| "unknown".to_owned());
println!("cargo:rustc-env=GIT_REVISION={}", rev); println!("cargo:rustc-env=GIT_REVISION={}", rev);
@ -14,27 +14,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-env-changed=GIT_REVISION"); println!("cargo:rerun-if-env-changed=GIT_REVISION");
if let Ok(data) = std::fs::read_to_string(".env") { if let Ok(data) = std::fs::read_to_string(".env") {
data.split('\n').for_each(|v| { data.split('\n').into_iter().for_each(|v| {
if v.starts_with("DATABASE") {
return;
}
let kv: Vec<&str> = v.split('=').collect(); let kv: Vec<&str> = v.split('=').collect();
if kv.len() != 2 { if kv.len() != 2 {
return; return;
} }
let (key, value) = (kv[0], kv[1]); let (key, value) = (kv[0], kv[1]);
if key == "DATABASE_URL" {
return;
}
println!("cargo:rustc-env={}={}", key, value); println!("cargo:rustc-env={}={}", key, value);
println!("cargo:rerun-if-env-changed={}", key); println!("cargo:rerun-if-env-changed={}", key);
}) })
} }
Ok(())
} }
fn get_value_from_env(key: &str) -> Option<String> { fn get_value_from_env(key: &str) -> Option<String> {
env::var(key).ok() env::var(key).map_or_else(|_| None, |v| Some(v))
} }
fn get_value_from_command<I: IntoIterator<Item = S>, S: AsRef<std::ffi::OsStr>>( fn get_value_from_command<I: IntoIterator<Item = S>, S: AsRef<std::ffi::OsStr>>(

View File

@ -1,3 +1,5 @@
BEGIN;
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
`user_id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `user_id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,
`chat_id` integer NOT NULL UNIQUE, `chat_id` integer NOT NULL UNIQUE,
@ -26,3 +28,10 @@ CREATE TABLE IF NOT EXISTS actions (
REFERENCES users(user_id) REFERENCES users(user_id)
ON DELETE CASCADE ON DELETE CASCADE
); );
COMMIT;
-- drop index actions_action_id_user_id_idx;
-- drop table users;
-- drop table parameters;
-- drop table actions;

View File

@ -3,8 +3,7 @@ export DOCKER_BUILDKIT=1
DOCKERFLAGS:=-it --rm \ DOCKERFLAGS:=-it --rm \
-v "${PWD}":"/app" \ -v "${PWD}":"/app" \
--workdir "/app" \ --workdir "/app" \
-e "PWD=/app" \ -e "PWD=/app"
-u $(shell id -u):$(shell id -g)
DOCKERIMG:="rust-build-env:V1" DOCKERIMG:="rust-build-env:V1"
@ -28,10 +27,6 @@ build_release_arm:
docker run ${DOCKERFLAGS} ${DOCKERIMG} /bin/sh -c 'cargo build --release --target=armv7-unknown-linux-gnueabihf' docker run ${DOCKERFLAGS} ${DOCKERIMG} /bin/sh -c 'cargo build --release --target=armv7-unknown-linux-gnueabihf'
.PHONY: build_release_arm .PHONY: build_release_arm
inside:
docker run ${DOCKERFLAGS} ${DOCKERIMG} /bin/bash
.PHONY: inside
docker_build_release_arm: docker_build_release_arm:
docker run ${DOCKERFLAGS} ${DOCKERIMG} make build_release_arm docker run ${DOCKERFLAGS} ${DOCKERIMG} make build_release_arm
@ -43,11 +38,3 @@ dronefile:
-V target_arch=${TARGET_ARCH} -V target_arch=${TARGET_ARCH}
drone sign frx/altherego --save drone sign frx/altherego --save
.PHONY: dronefile .PHONY: dronefile
init_db:
rm -rf .testdata
mkdir .testdata
sqlite3 -init ./db/001_initial.sql ./.testdata/db.sqlite '.q'
open_db:
sqlite3 ./.testdata/db.sqlite

View File

@ -28,6 +28,7 @@ async fn main() {
env_logger::init(); env_logger::init();
debug!("starting the application"); debug!("starting the application");
println!("starting the application");
tokio::spawn(run()).await.unwrap(); tokio::spawn(run()).await.unwrap();
} }
@ -57,9 +58,11 @@ async fn run() -> anyhow::Result<()> {
info!("starting"); info!("starting");
let settings = Settings::init_from_env().expect("reading config values"); let settings = Settings::init_from_env().expect("reading config values");
let bot = teloxide::Bot::new(&settings.telegram_token); let bot = teloxide::Bot::new(&settings.telegram_token).auto_send();
let migrate = std::env::args().any(|v| v == "migrate");
let repo_config = repo::SqliteConfig { let repo_config = repo::SqliteConfig {
source: settings.db_source, source: settings.db_source,
migrate,
..Default::default() ..Default::default()
}; };
@ -70,7 +73,7 @@ async fn run() -> anyhow::Result<()> {
let climate_client = climate::Client::new(&settings.climate_dsn); let climate_client = climate::Client::new(&settings.climate_dsn);
let self_temp_client = { let self_temp_client = {
let splitted: Vec<&str> = settings.hosttemp_cmd.split(' ').collect(); let splitted: Vec<&str> = settings.hosttemp_cmd.split(' ').into_iter().collect();
let (cmd, arg) = (splitted[0], splitted[1]); let (cmd, arg) = (splitted[0], splitted[1]);
climate::SelfTemperature::new(cmd, arg) climate::SelfTemperature::new(cmd, arg)
@ -108,7 +111,6 @@ async fn run() -> anyhow::Result<()> {
.branch(dptree::case![Command::RoomTemperature].endpoint(handle_temperature_sensor)) .branch(dptree::case![Command::RoomTemperature].endpoint(handle_temperature_sensor))
.branch(dptree::case![Command::HostTemperature].endpoint(handle_host_temperature)) .branch(dptree::case![Command::HostTemperature].endpoint(handle_host_temperature))
.branch(dptree::case![Command::VersionRequest].endpoint(handle_version)) .branch(dptree::case![Command::VersionRequest].endpoint(handle_version))
.branch(dptree::case![Command::ChatID].endpoint(handle_chat_id))
.branch(dptree::case![Command::Help].endpoint(handle_help)); .branch(dptree::case![Command::Help].endpoint(handle_help));
let mut dependencies = DependencyMap::new(); let mut dependencies = DependencyMap::new();
@ -121,11 +123,11 @@ async fn run() -> anyhow::Result<()> {
Dispatcher::builder(bot, handler) Dispatcher::builder(bot, handler)
.dependencies(dependencies) .dependencies(dependencies)
.enable_ctrlc_handler()
.default_handler(|upd| async move { .default_handler(|upd| async move {
warn!("unhandled update: {:?}", upd); warn!("unhandled update: {:?}", upd);
}) })
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
@ -139,7 +141,7 @@ fn error_msg(reqid: &utils::RequestID) -> String {
} }
async fn handle_temperature_sensor( async fn handle_temperature_sensor(
bot: Bot, bot: AutoSend<Bot>,
msg: Message, msg: Message,
climate: climate::Client, climate: climate::Client,
next_req_id: utils::Generators, next_req_id: utils::Generators,
@ -173,7 +175,7 @@ async fn handle_temperature_sensor(
} }
async fn handle_host_temperature( async fn handle_host_temperature(
bot: Bot, bot: AutoSend<Bot>,
msg: Message, msg: Message,
temp: SelfTemperature, temp: SelfTemperature,
next_req_id: utils::Generators, next_req_id: utils::Generators,
@ -200,7 +202,7 @@ async fn handle_host_temperature(
Ok(()) Ok(())
} }
async fn handle_version(bot: Bot, msg: Message) -> Result<()> { async fn handle_version(bot: AutoSend<Bot>, msg: Message) -> Result<()> {
let chat_id = msg.chat.id; let chat_id = msg.chat.id;
let text = format!("Bot version is {} (branch: {})", VERSION, BRANCH,); let text = format!("Bot version is {} (branch: {})", VERSION, BRANCH,);
@ -209,16 +211,7 @@ async fn handle_version(bot: Bot, msg: Message) -> Result<()> {
Ok(()) Ok(())
} }
async fn handle_chat_id(bot: Bot, msg: Message) -> Result<()> { async fn handle_help(bot: AutoSend<Bot>, msg: Message) -> Result<()> {
let chat_id = msg.chat.id;
let text = format!("Current chat id: {chat_id}");
bot.send_message(chat_id, text).await?;
Ok(())
}
async fn handle_help(bot: Bot, msg: Message) -> Result<()> {
let chat_id = msg.chat.id; let chat_id = msg.chat.id;
bot.send_message(chat_id, Command::descriptions().to_string()) bot.send_message(chat_id, Command::descriptions().to_string())
@ -261,16 +254,14 @@ struct Climate {
} }
#[derive(BotCommands, Debug, Clone, PartialEq, Eq)] #[derive(BotCommands, Debug, Clone, PartialEq, Eq)]
#[command(description = "These commands are supported:")] #[command(rename = "lowercase", description = "These commands are supported:")]
enum Command { enum Command {
#[command(rename = "help", description = "display this text.")] #[command(description = "display this text.")]
Help, Help,
#[command(rename = "roomtemp", description = "temperature of your room.")] #[command(description = "temperature of your room.")]
RoomTemperature, RoomTemperature,
#[command(rename = "hosttemp", description = "temperature of raspberry.")] #[command(description = "temperature of raspberry.")]
HostTemperature, HostTemperature,
#[command(rename = "version", description = "prints current version.")] #[command(description = "prints current version.")]
VersionRequest, VersionRequest,
#[command(rename = "chatid", description = "prints current chat id.")]
ChatID,
} }

View File

@ -1,44 +0,0 @@
use std::str::FromStr;
use anyhow::Result;
use log::{debug, info};
use envconfig::Envconfig;
use sqlx::{
SqlitePool,
sqlite::SqliteConnectOptions,
migrate,
};
#[derive(Envconfig)]
struct Settings {
#[envconfig(from = "ALTEREGO_DATABASE_URL", default = "./db.sqlite")]
pub db_source: String,
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
info!("starting the application");
tokio::spawn(run()).await?
}
async fn run() -> Result<()> {
debug!("running migrations");
let settings = Settings::init_from_env().expect("reading config values");
info!("opening database {}", settings.db_source);
let opts = SqliteConnectOptions::from_str(&settings.db_source)?
.create_if_missing(true)
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
let pool = SqlitePool::connect_with(opts).await?;
migrate!("./db/").run(&pool).await?;
Ok(())
}

View File

@ -15,6 +15,7 @@ pub struct SqliteConfig {
pub source: String, pub source: String,
pub timeout: std::time::Duration, pub timeout: std::time::Duration,
pub max_conns: u32, pub max_conns: u32,
pub migrate: bool,
} }
impl Default for SqliteConfig { impl Default for SqliteConfig {
@ -23,6 +24,7 @@ impl Default for SqliteConfig {
source: ":memory:".to_string(), source: ":memory:".to_string(),
timeout: std::time::Duration::from_secs(10), timeout: std::time::Duration::from_secs(10),
max_conns: 2, max_conns: 2,
migrate: false,
} }
} }
} }
@ -54,6 +56,10 @@ impl SqliteRepo {
.connect_with(opts) .connect_with(opts)
.await?; .await?;
if config.migrate {
sqlx::migrate!("./db").run(&pool).await?;
}
sqlx::query("pragma temp_store = memory;") sqlx::query("pragma temp_store = memory;")
.execute(&pool) .execute(&pool)
.await?; .await?;