#[macro_use]
extern crate log;
extern crate ansi_term;
#[macro_use]
extern crate serde_derive;
extern crate docopt;
#[macro_use]
extern crate rust_embed;
extern crate rusqlite;
mod migration;
use ansi_term::Colour::{self, Cyan, Green};
use ansi_term::Style;
use docopt::Docopt;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef};
use rusqlite::Connection;
use std::fmt;
use std::fs;
use std::path::Path;
const VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = "
mdal - Award medals
Usage:
mdal group set <name> <desc>
mdal group delete <name>
mdal group rename <name> <newname>
mdal group list [-c]
mdal group rank <name> [-c] [PERIOD]
mdal entity set <name> <desc> [<groupname>] [<parentname>]
mdal entity delete <name>
mdal entity rename <name> <newname>
mdal entity group <name> <groupname>
mdal entity parent <name> <parentname>
mdal entity rank <name> [-c] [PERIOD]
mdal aspect set <name> <desc> TYPE
mdal aspect delete <name>
mdal aspect rename <name> <newname>
mdal aspect list [-c]
mdal award <name> <aspectname> VALUE
mdal profile <name> [-c]
mdal reset
mdal (-h | --help)
mdal (-V | --version)
Options:
TYPE The medal type.
Valid values: medal, star, heart, wing.
VALUE The value of the medal.
Valid values: platinum, gold, silver, bronze.
PERIOD The period range, e.g., current week.
Valid values: week, month, year.
-c, --colorless Don't use colors.
-h, --help Show this screen.
-V, --version Show version.
";
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
struct Command {
arg_name: String,
arg_desc: Option<String>,
arg_newname: Option<String>,
arg_groupname: Option<String>,
arg_parentname: Option<String>,
arg_aspectname: Option<String>,
arg_TYPE: Option<MedalType>,
arg_VALUE: Option<ValueType>,
arg_PERIOD: Option<Period>,
flag_colorless: bool,
flag_help: bool,
flag_version: bool,
cmd_group: bool,
cmd_entity: bool,
cmd_parent: bool,
cmd_aspect: bool,
cmd_award: bool,
cmd_rank: bool,
cmd_set: bool,
cmd_delete: bool,
cmd_rename: bool,
cmd_list: bool,
cmd_profile: bool,
cmd_reset: bool,
}
impl Command {
fn group_set(self, conn: &Connection) -> Result<bool> {
debug!("Group set");
let name = self.arg_name.to_lowercase();
let gid = query_id(&conn, &name, "group");
debug!("Group ({:?}, {:?})", &gid, &name);
if gid.is_none() {
conn.execute(
"INSERT INTO `group` (`name`, `desc`) VALUES (?1, ?2)",
&[&name, &self.arg_desc],
)?;
} else {
warn!("This group already exists! Updating description...");
conn.execute(
"UPDATE `group` SET `desc`=?1 WHERE id=?2",
&[&self.arg_desc, &gid],
)?;
}
info!("The group '{}' was set.", self.arg_desc.unwrap());
Ok(true)
}
fn group_delete(self, conn: &Connection) -> Result<bool> {
debug!("Group delete");
let name = self.arg_name.to_lowercase();
let gid = query_id(&conn, &name, "group");
if gid.is_none() {
return Ok(false);
}
debug!("Group ({:?}, {:?})", &gid, &name);
conn.execute("DELETE FROM `group` WHERE `id`=?1", &[&gid])?;
conn.execute("DELETE FROM `group_entity` WHERE `group_id`=?1", &[&gid])?;
info!("The group '{}' was deleted.", name);
Ok(true)
}
fn group_rename(self, conn: &Connection) -> Result<bool> {
debug!("Group rename");
let name = self.arg_name.to_lowercase();
let gid = query_id(&conn, &name, "group");
if gid.is_none() {
return Ok(false);
}
debug!("Group ({:?}, {:?})", &gid, &name);
let newname = self.arg_newname.unwrap().to_lowercase();
conn.execute(
"UPDATE `group` SET `name`=?1 WHERE id=?2",
&[&newname, &gid],
)?;
Ok(true)
}
fn group_rank(self, conn: &Connection) -> Result<bool> {
debug!("Group rank");
let name = self.arg_name.to_lowercase();
let gid = query_id(&conn, &name, "group");
if gid.is_none() {
return Ok(false);
}
debug!("Group ({:?}, {:?})", &gid, &name);
let sql = match self.arg_PERIOD {
Some(Period::Week) => SQL_GROUP_RANK.replace("{}", SQL_WEEK),
Some(Period::Month) => SQL_GROUP_RANK.replace("{}", SQL_MONTH),
Some(Period::Year) => SQL_GROUP_RANK.replace("{}", SQL_YEAR),
None => SQL_GROUP_RANK.replace("{}", ""),
};
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query(&[&gid])?;
print_rank(rows, self.flag_colorless)?;
Ok(true)
}
fn group_list(self, conn: &Connection) -> Result<bool> {
debug!("Group list");
let mut stmt = conn.prepare("SELECT `name`, `desc` FROM `group` ORDER BY `name`")?;
let rows = stmt.query(&[])?;
print_list(rows, self.flag_colorless)?;
Ok(true)
}
fn entity_set(self, conn: &Connection) -> Result<bool> {
debug!("Entity set");
let name = self.arg_name.to_lowercase();
let parentname = self.arg_parentname.unwrap_or_default().to_lowercase();
let groupname = self.arg_groupname.unwrap_or_default().to_lowercase();
let gid = if !groupname.is_empty() {
match query_id(&conn, &groupname, "group") {
Some(id) => Some(id),
None => return Ok(false),
}
} else {
None
};
debug!("Group ({:?}, {:?})", &gid, &groupname);
let pid = if !parentname.is_empty() {
match query_id(&conn, &parentname, "entity") {
Some(id) => Some(id),
None => return Ok(false),
}
} else {
None
};
debug!("Parent ({:?}, {:?})", &pid, &parentname);
let eid = query_id(&conn, &name, "entity");
debug!("Entity ({:?}, {:?})", &eid, &name);
if eid.is_none() {
conn.execute(
"INSERT INTO `entity` (`name`, `desc`) VALUES (?1, ?2)",
&[&name, &self.arg_desc],
)?;
let last_id = conn.last_insert_rowid();
if gid.is_some() {
conn.execute(
"INSERT INTO `group_entity` (`group_id`, `entity_id`) VALUES (?1, ?2)",
&[&gid, &last_id],
)?;
if pid.is_some() {
conn.execute(
"INSERT INTO `parent_entity` (`parent_id`, `entity_id`) VALUES (?1, ?2)",
&[&pid, &last_id],
)?;
}
}
} else {
warn!("This entity already exists! Group and parent will be ignored. Updating description...");
conn.execute(
"UPDATE `entity` SET `desc`=?1 WHERE id=?2",
&[&self.arg_desc, &eid],
)?;
}
info!("The entity '{}' was set.", self.arg_desc.unwrap());
Ok(true)
}
fn entity_delete(self, conn: &Connection) -> Result<bool> {
debug!("Entity delete");
let name = self.arg_name.to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
debug!("Entity ({:?}, {:?})", &eid, &name);
conn.execute("DELETE FROM `entity` WHERE `id`=?1", &[&eid])?;
conn.execute("DELETE FROM `group_entity` WHERE `entity_id`=?1", &[&eid])?;
conn.execute(
"DELETE FROM `parent_entity` WHERE `parent_id`=?1 OR `entity_id`=?1",
&[&eid],
)?;
conn.execute("DELETE FROM `medal` WHERE `entity_id`=?1", &[&eid])?;
info!("The entity '{}' and its medals were deleted.", name);
Ok(true)
}
fn entity_rename(self, conn: &Connection) -> Result<bool> {
debug!("Entity rename");
let name = self.arg_name.to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
debug!("Entity ({:?}, {:?})", &eid, &name);
let newname = self.arg_newname.unwrap().to_lowercase();
conn.execute(
"UPDATE `entity` SET `name`=?1 WHERE id=?2",
&[&newname, &eid],
)?;
Ok(true)
}
fn entity_group(self, conn: &Connection) -> Result<bool> {
debug!("Entity group");
let name = self.arg_name.to_lowercase();
let groupname = self.arg_groupname.unwrap().to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
debug!("Entity ({:?}, {:?})", &eid, &name);
let gid = query_id(&conn, &groupname, "group");
if gid.is_none() {
return Ok(false);
}
debug!("Group ({:?}, {:?})", &gid, &groupname);
match conn.query_row::<i32, _>(
"SELECT `group_id` FROM `group_entity` WHERE `group_id`=?1 AND `entity_id` = ?2",
&[&gid, &eid],
|row| row.get(0),
) {
Ok(_) => {
warn!("The entity '{}' is in the group '{}' already! It will be removed from the group.",
&name, &groupname);
conn.execute(
"DELETE FROM `group_entity` WHERE `group_id`=?1 AND `entity_id`=?2",
&[&gid, &eid],
)?;
info!(
"The entity '{}' was removed from the group '{}'.",
&name, &groupname
);
}
Err(_) => {
conn.execute(
"INSERT INTO `group_entity` (`group_id`, `entity_id`) VALUES (?1, ?2)",
&[&gid, &eid],
)?;
info!("The entity '{}' was set on group '{}'.", &name, &groupname);
}
}
Ok(true)
}
fn entity_parent(self, conn: &Connection) -> Result<bool> {
debug!("Entity parent");
let name = self.arg_name.to_lowercase();
let parentname = self.arg_parentname.unwrap().to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
let eid = eid.unwrap();
debug!("Entity ({:?}, {:?})", &eid, &name);
let pid = query_id(&conn, &parentname, "entity");
if pid.is_none() {
return Ok(false);
}
let pid = pid.unwrap();
debug!("Parent ({:?}, {:?})", &pid, &parentname);
if pid == eid {
error!("Entities cannot be parents of themselves.");
return Ok(false);
}
match conn.query_row::<i32, _>(
"SELECT `parent_id` FROM `parent_entity` WHERE `parent_id`=?1 AND `entity_id` = ?2",
&[&pid, &eid],
|row| row.get(0),
) {
Ok(_) => {
warn!(
"The entity '{}' is parent of '{}' already! This relationship will be removed.",
&parentname, &name
);
conn.execute(
"DELETE FROM `parent_entity` WHERE `parent_id`=?1 AND `entity_id`=?2",
&[&pid, &eid],
)?;
info!(
"The entity '{}' is not parent of '{}' anymore.",
&parentname, &name
);
}
Err(_) => match conn.query_row::<i32, _>(
"SELECT `parent_id` FROM `parent_entity` WHERE `parent_id`=?1 AND `entity_id` = ?2",
&[&eid, &pid],
|row| row.get(0),
) {
Ok(_) => {
error!("The entity '{}' is parent of '{}'! A parent can't be child of its own child.",
&name, &parentname);
return Ok(false);
}
Err(_) => {
conn.execute(
"INSERT INTO `parent_entity` (`parent_id`, `entity_id`) VALUES (?1, ?2)",
&[&pid, &eid],
)?;
info!("The entity '{}' is parent of '{}' now.", &parentname, &name);
}
},
}
Ok(true)
}
fn entity_rank(self, conn: &Connection) -> Result<bool> {
debug!("Entity rank");
let name = self.arg_name.to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
debug!("Entity ({:?}, {:?})", &eid, &name);
let sql = match self.arg_PERIOD {
Some(Period::Week) => SQL_ENTITY_RANK.replace("{}", SQL_WEEK),
Some(Period::Month) => SQL_ENTITY_RANK.replace("{}", SQL_MONTH),
Some(Period::Year) => SQL_ENTITY_RANK.replace("{}", SQL_YEAR),
None => SQL_ENTITY_RANK.replace("{}", ""),
};
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query(&[&eid])?;
print_rank(rows, self.flag_colorless)?;
Ok(true)
}
fn aspect_set(self, conn: &Connection) -> Result<bool> {
debug!("Aspect set");
let name = self.arg_name.to_lowercase();
let aid = query_id(&conn, &name, "aspect");
debug!("Aspect ({:?}, {:?})", &aid, &name);
let mtype = self.arg_TYPE.unwrap();
if aid.is_none() {
conn.execute(
"INSERT INTO `aspect` (`medaltype_id`, `name`, `desc`) VALUES (?1, ?2, ?3)",
&[&(mtype as i32), &name, &self.arg_desc],
)?;
} else {
warn!("This aspect already exists! Updating type and description...");
conn.execute(
"UPDATE `aspect` SET `medaltype_id`=?1, `desc`=?2 WHERE id=?3",
&[&(mtype as i32), &self.arg_desc, &aid],
)?;
}
info!(
"An awardable {} {} is now available.",
self.arg_desc.unwrap(),
mtype
);
Ok(true)
}
fn aspect_delete(self, conn: &Connection) -> Result<bool> {
debug!("Aspect delete");
let name = self.arg_name.to_lowercase();
let aid = query_id(&conn, &name, "aspect");
if aid.is_none() {
return Ok(false);
}
debug!("Aspect ({:?}, {:?})", &aid, &name);
conn.execute("DELETE FROM `aspect` WHERE `id`=?1", &[&aid])?;
conn.execute("DELETE FROM `medal` WHERE `aspect_id`=?1", &[&aid])?;
info!("The aspect '{}' and its medals were deleted.", name);
Ok(true)
}
fn aspect_rename(self, conn: &Connection) -> Result<bool> {
debug!("Aspect rename");
let name = self.arg_name.to_lowercase();
let aid = query_id(&conn, &name, "aspect");
if aid.is_none() {
return Ok(false);
}
debug!("Aspect ({:?}, {:?})", &aid, &name);
let newname = self.arg_newname.unwrap().to_lowercase();
conn.execute(
"UPDATE `aspect` SET `name`=?1 WHERE id=?2",
&[&newname, &aid],
)?;
Ok(true)
}
fn aspect_list(self, conn: &Connection) -> Result<bool> {
debug!("Aspect list");
let mut stmt = conn.prepare("SELECT `name`, `desc` FROM `aspect` ORDER BY `name`")?;
let rows = stmt.query(&[])?;
print_list(rows, self.flag_colorless)?;
Ok(true)
}
fn award(self, conn: &Connection) -> Result<bool> {
debug!("Award");
let name = self.arg_name.to_lowercase();
let aspectname = self.arg_aspectname.unwrap().to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
debug!("Entity ({:?}, {:?})", &eid, &name);
let aid = query_id(&conn, &aspectname, "aspect");
if aid.is_none() {
return Ok(false);
}
debug!("Aspect ({:?}, {:?})", &aid, &aspectname);
let value = self.arg_VALUE.unwrap();
conn.execute(
"INSERT INTO `medal` (`entity_id`, `aspect_id`, `value`, `created_at`)
VALUES (?1, ?2, ?3, strftime('%s', 'now'))",
&[&eid, &aid, &(value as i32)],
)?;
info!("A {} medal was awarded to '{}'!", value, name);
Ok(true)
}
fn profile(self, conn: &Connection) -> Result<bool> {
debug!("Profile");
let name = self.arg_name.to_lowercase();
let eid = query_id(&conn, &name, "entity");
if eid.is_none() {
return Ok(false);
}
debug!("Entity ({:?}, {:?})", &eid, &name);
let mut stmt = conn.prepare("SELECT COUNT(s.name), m.value, s.desc, s.medaltype_id
FROM medal m LEFT JOIN aspect s ON s.id = m.aspect_id
WHERE m.entity_id=?1 GROUP BY s.name, m.value ORDER BY value DESC")?;
let rows = stmt.query(&[&eid])?;
print_profile(rows, self.flag_colorless)?;
Ok(true)
}
#[cfg_attr(rustfmt, rustfmt_skip)]
fn execute(self, conn: &Connection) -> Result<bool> {
match self {
Command { cmd_group: true, cmd_set: true, .. } => self.group_set(&conn),
Command { cmd_group: true, cmd_delete: true, .. } => self.group_delete(&conn),
Command { cmd_group: true, cmd_rename: true, .. } => self.group_rename(&conn),
Command { cmd_group: true, cmd_rank: true, .. } => self.group_rank(&conn),
Command { cmd_group: true, cmd_list: true, .. } => self.group_list(&conn),
Command { cmd_entity: true, cmd_set: true, .. } => self.entity_set(&conn),
Command { cmd_entity: true, cmd_delete: true, .. } => self.entity_delete(&conn),
Command { cmd_entity: true, cmd_rename: true, .. } => self.entity_rename(&conn),
Command { cmd_entity: true, cmd_group: true, .. } => self.entity_group(&conn),
Command { cmd_entity: true, cmd_parent: true, .. } => self.entity_parent(&conn),
Command { cmd_entity: true, cmd_rank: true, .. } => self.entity_rank(&conn),
Command { cmd_aspect: true, cmd_set: true, .. } => self.aspect_set(&conn),
Command { cmd_aspect: true, cmd_delete: true, .. } => self.aspect_delete(&conn),
Command { cmd_aspect: true, cmd_rename: true, .. } => self.aspect_rename(&conn),
Command { cmd_aspect: true, cmd_list: true, .. } => self.aspect_list(&conn),
Command { cmd_award: true, .. } => self.award(&conn),
Command { cmd_profile: true, .. } => self.profile(&conn),
Command { flag_help: true, .. } => {
println!("{}", USAGE);
Ok(true)
}
Command { flag_version: true, .. } => {
println!("mdal v{}", VERSION);
println!("db v{}", ::migration::DB_VERSION);
Ok(true)
}
_ => Ok(false),
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Deserialize)]
enum MedalType {
Medal = 0,
Star,
Heart,
Wing,
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Deserialize)]
enum ValueType {
Bronze = 5,
Silver = 15,
Gold = 45,
Platinum = 135,
}
#[derive(Copy, Clone, Debug, Deserialize)]
enum Period {
Week,
Month,
Year,
}
impl fmt::Display for MedalType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MedalType::Medal => write!(f, "Medal"),
MedalType::Star => write!(f, "Star"),
MedalType::Heart => write!(f, "Heart"),
MedalType::Wing => write!(f, "Wing"),
}
}
}
impl FromSql for MedalType {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
i64::column_result(value).and_then(|i| {
if i < i64::from(u8::min_value()) || i > i64::from(u8::max_value()) {
Err(FromSqlError::OutOfRange(i))
} else {
match i {
0 => Ok(MedalType::Medal),
1 => Ok(MedalType::Star),
2 => Ok(MedalType::Heart),
3 => Ok(MedalType::Wing),
_ => Err(FromSqlError::InvalidType),
}
}
})
}
}
impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ValueType::Bronze => write!(f, "Bronze"),
ValueType::Silver => write!(f, "Silver"),
ValueType::Gold => write!(f, "Gold"),
ValueType::Platinum => write!(f, "Platinum"),
}
}
}
impl FromSql for ValueType {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
i64::column_result(value).and_then(|i| {
if i < i64::from(u8::min_value()) || i > i64::from(u8::max_value()) {
Err(FromSqlError::OutOfRange(i))
} else {
match i {
5 => Ok(ValueType::Bronze),
15 => Ok(ValueType::Silver),
45 => Ok(ValueType::Gold),
135 => Ok(ValueType::Platinum),
_ => Err(FromSqlError::InvalidType),
}
}
})
}
}
pub fn run(data_dir: &Path) -> Result<bool> {
let command: Command = Docopt::new(USAGE).and_then(|d| d.help(false).deserialize())?;
debug!("{:?}", command);
let db_path = data_dir.join("mdal.db");
if command.cmd_reset {
if db_path.exists() {
fs::remove_file(&db_path)?;
}
info!("The database has gone poof!");
}
let conn = Connection::open(db_path)?;
migration::run(&conn)?;
command.execute(&conn)
}
fn query_id(conn: &Connection, name: &str, table: &str) -> Option<i32> {
match conn.query_row(
&format!("SELECT `id` FROM `{}` WHERE `name` = ?1", table),
&[&name],
|row| row.get(0),
) {
Ok(id) => return Some(id),
Err(e) => {
warn!("Could not find {} '{}'", table, name);
debug!("{}", e);
}
}
None
}
fn setup_color_support() -> Result<bool> {
#[cfg(target_os = "windows")]
ansi_term::enable_ansi_support()?;
Ok(true)
}
fn print_rank(mut rows: rusqlite::Rows, colorless: bool) -> Result<bool> {
let mut i = 1;
let mut pos = 1;
let mut width = 4;
let mut last_value = 0;
while let Some(result_row) = rows.next() {
let row = result_row?;
let value = row.get_checked(2).unwrap_or(0) as i32;
let value_str = value.to_string();
let desc: String = row.get(1);
let name: String = row.get(0);
if i == 1 && last_value == 0 {
width = value_str.len();
} else if last_value != value {
pos = i
}
if !colorless {
setup_color_support()?;
println!(
" {} ┃ {} ┃ {} ({})",
Cyan.bold().paint(format!("{:04}", pos)),
Green.bold().paint(format!("{:>w$}", value_str, w = width)),
Style::new().bold().paint(desc),
Cyan.paint(name)
);
} else {
println!(
" {:04} ┃ {:>w$} ┃ {} ({})",
pos,
value,
desc,
name,
w = width
);
}
i += 1;
last_value = value;
}
Ok(true)
}
fn print_profile(mut rows: rusqlite::Rows, colorless: bool) -> Result<bool> {
while let Some(result_row) = rows.next() {
let row = result_row?;
let count: i32 = row.get(0);
let value: ValueType = row.get(1);
let desc: String = row.get(2);
let medal: MedalType = row.get(3);
if !colorless {
setup_color_support()?;
let value = match value {
ValueType::Bronze => Colour::Fixed(94).bold().paint(value.to_string()),
ValueType::Silver => Colour::Fixed(245).bold().paint(value.to_string()),
ValueType::Gold => Colour::Fixed(142).bold().paint(value.to_string()),
ValueType::Platinum => Colour::Fixed(249).bold().paint(value.to_string()),
};
println!(
" {} {} {} {}",
Green.paint(format!("#{}", count.to_string())),
value,
Style::new().bold().paint(desc),
Cyan.bold().paint(medal.to_string())
);
} else {
println!(" #{} {} {} {}", count, value, desc, medal);
}
}
Ok(true)
}
fn print_list(mut rows: rusqlite::Rows, colorless: bool) -> Result<bool> {
while let Some(result_row) = rows.next() {
let row = result_row?;
let name: String = row.get(0);
let desc: String = row.get(1);
if !colorless {
setup_color_support()?;
println!(
" {} ({})",
Style::new().bold().paint(desc),
Cyan.bold().paint(name)
);
} else {
println!(" {} ({})", desc, name);
}
}
Ok(true)
}
const SQL_GROUP_RANK: &str = "SELECT e.name, e.desc, SUM(m.value) as total FROM entity e
LEFT JOIN `group_entity` g ON e.id = g.entity_id
LEFT JOIN medal m ON e.id = m.entity_id {}
WHERE g.group_id = ?
GROUP BY e.name ORDER BY total DESC";
const SQL_ENTITY_RANK: &str = "SELECT e.name, e.desc, SUM(m.value) as total FROM entity e
LEFT JOIN `parent_entity` p ON e.id = p.entity_id
LEFT JOIN medal m ON e.id = m.entity_id {}
WHERE p.parent_id = ?
GROUP BY e.name ORDER BY total DESC";
const SQL_WEEK: &str = "AND m.created_at BETWEEN
strftime('%s', 'now', 'weekday 0', '-7 days') AND
strftime('%s', 'now')";
const SQL_MONTH: &str = "AND m.created_at >= strftime('%s', 'now', 'start of month')";
const SQL_YEAR: &str = "AND m.created_at >= strftime('%s', 'now', 'start of year')";