use diesel::prelude::*;
#[cfg(feature = "napi")]
use napi_derive::napi;
#[cfg(feature = "pyo3")]
use pyo3::prelude::*;
#[cfg(feature = "pyo3")]
use rbox_derives::PyMutableMapping;
use super::menu_item::MenuItem;
use super::schema::{category, menuItem};
use crate::model_traits::{
load_related, ordering_error, Model, ModelDelete, ModelInsert, ModelRelations, ModelUpdate,
SequenceStore, SequenceVisible,
};
#[cfg(feature = "pyo3")]
use crate::util::{PyItemsIter, PyObjectIter, PyStrIter};
#[derive(Debug, Clone, PartialEq, HasQuery, Identifiable, AsChangeset, Associations)]
#[diesel(table_name = category)]
#[diesel(primary_key(id))]
#[diesel(belongs_to(MenuItem, foreign_key = menu_item_id))]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all, mapping))]
#[cfg_attr(feature = "pyo3", derive(PyMutableMapping))]
#[cfg_attr(feature = "napi", napi(object))]
pub struct Category {
pub id: i32,
pub menu_item_id: i32,
pub seq: i32,
pub is_visible: i32,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(get_all))]
#[cfg_attr(feature = "napi", napi(object))]
pub struct CategoryView {
pub category: Category,
pub menu_item: Option<MenuItem>,
}
impl ModelRelations for Category {
type Resolved = CategoryView;
fn resolve_many(
conn: &mut SqliteConnection,
models: Vec<Self>,
) -> QueryResult<Vec<Self::Resolved>> {
if models.is_empty() {
return Ok(Vec::new());
}
conn.transaction(|conn| {
let menu_items = load_related(
conn,
models.iter().map(|row| row.menu_item_id),
|conn, ids| {
MenuItem::query()
.filter(menuItem::id.eq_any(ids))
.load(conn)
},
|row| row.id,
)?;
Ok(models
.into_iter()
.map(|category| CategoryView {
menu_item: menu_items.get(&category.menu_item_id).cloned(),
category,
})
.collect())
})
}
}
impl Model for Category {
type Id = i32;
fn all(conn: &mut SqliteConnection) -> QueryResult<Vec<Self>> {
Self::query()
.order((category::seq, category::id))
.load(conn)
}
fn find(conn: &mut SqliteConnection, id: &Self::Id) -> QueryResult<Option<Self>> {
Self::query().find(id).first(conn).optional()
}
fn id_exists(conn: &mut SqliteConnection, id: &Self::Id) -> QueryResult<bool> {
diesel::dsl::select(diesel::dsl::exists(Self::query().find(id))).get_result(conn)
}
}
impl ModelUpdate for Category {
fn update(mut self, conn: &mut SqliteConnection) -> QueryResult<Self> {
conn.transaction(|conn| {
let old = Self::find(conn, &self.id)?.ok_or(diesel::result::Error::NotFound)?;
if self.is_visible != 0 && self.is_visible != 1 {
return Err(ordering_error("Visibility must be zero or one"));
}
self.seq = match (old.is_visible == 1, self.is_visible == 1) {
(false, true) => {
<Self as SequenceVisible>::update_seq_before_insert(conn, self.seq)?
}
(true, false) => {
Self::decrement_seq_gt(conn, &(), old.seq)?;
0
}
(true, true) => Self::update_seq_before_move_in(
conn,
&(),
old.seq,
if self.seq == 0 { None } else { Some(self.seq) },
)?
.map_or(old.seq, |(seq, _)| seq),
(false, false) => 0,
};
diesel::update(category::table.find(self.id))
.set(self)
.get_result(conn)
})
}
}
impl ModelDelete for Category {
fn delete(conn: &mut SqliteConnection, id: &Self::Id) -> QueryResult<usize> {
conn.transaction(|conn| {
let (is_visible, seq) = diesel::delete(category::table.find(id))
.returning((category::is_visible, category::seq))
.get_result::<(i32, i32)>(conn)?;
if is_visible == 1 {
Self::decrement_seq_after_delete(conn, seq)?;
}
Ok(1)
})
}
fn delete_all(conn: &mut SqliteConnection, ids: Vec<&Self::Id>) -> QueryResult<usize> {
conn.transaction(|conn| {
let is_visible: Vec<i32> =
diesel::delete(category::table.filter(category::id.eq_any(ids)))
.returning(category::is_visible)
.get_results(conn)?;
if is_visible.iter().any(|&v| v == 1) {
<Self as SequenceVisible>::reset_seq(conn)?;
}
Ok(is_visible.len())
})
}
}
impl Category {
#[diesel::dsl::auto_type(no_type_alias)]
fn filter_is_visible(is_visible: i32) -> _ {
category::table.filter(category::is_visible.eq(is_visible))
}
}
impl SequenceStore for Category {
type ParentId = ();
fn is_valid_parent(_: &mut SqliteConnection, _: &()) -> QueryResult<bool> {
Ok(true)
}
fn count_children(conn: &mut SqliteConnection, _: &()) -> QueryResult<i32> {
let count: i64 = Self::filter_is_visible(1).count().get_result(conn)?;
i32::try_from(count).map_err(|_| ordering_error("Collection too large"))
}
fn get_seq_numbers(conn: &mut SqliteConnection, _: &()) -> QueryResult<Vec<i32>> {
Self::filter_is_visible(1)
.order(category::seq)
.select(category::seq)
.load(conn)
}
fn reset_seq(conn: &mut SqliteConnection, _: &()) -> QueryResult<usize> {
conn.transaction(|conn| {
let rows: Vec<(i32, i32)> = Self::filter_is_visible(1)
.order((category::seq, category::id))
.select((category::id, category::seq))
.load(conn)?;
let mut count = 0;
for (i, (id, old)) in rows.into_iter().enumerate() {
let seq = i32::try_from(i)
.ok()
.and_then(|i| Self::START_SEQ.checked_add(i))
.ok_or_else(|| ordering_error("Position overflow"))?;
if seq != old {
count += diesel::update(category::table.find(id))
.set(category::seq.eq(seq))
.execute(conn)?;
}
}
Ok(count)
})
}
fn shift_seq(
conn: &mut SqliteConnection,
_: &(),
range: std::ops::Range<i32>,
delta: i32,
) -> QueryResult<usize> {
diesel::update(
Self::filter_is_visible(1)
.filter(category::seq.ge(range.start))
.filter(category::seq.lt(range.end)),
)
.set(category::seq.eq(category::seq + delta))
.execute(conn)
}
}
impl SequenceVisible for Category {}
#[derive(Debug, Clone, PartialEq, Default, Insertable)]
#[diesel(table_name = category)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all, mapping))]
#[cfg_attr(feature = "pyo3", derive(PyMutableMapping))]
#[cfg_attr(feature = "napi", napi(object))]
pub struct NewCategory {
pub menu_item_id: i32,
pub seq: i32,
pub is_visible: i32,
}
impl ModelInsert for NewCategory {
type Model = Category;
fn insert(mut self, conn: &mut SqliteConnection) -> QueryResult<Self::Model> {
conn.transaction(|conn| {
if self.is_visible != 0 && self.is_visible != 1 {
return Err(ordering_error("Visibility must be zero or one"));
}
self.seq = if self.is_visible == 1 {
<Category as SequenceVisible>::update_seq_before_insert(conn, self.seq)?
} else {
0 };
diesel::insert_into(category::table)
.values(self)
.get_result(conn)
})
}
}
impl NewCategory {
pub fn new(menu_item_id: i32) -> Self {
Self {
menu_item_id,
..Default::default()
}
}
pub fn seq(mut self, seq: i32) -> Self {
self.seq = seq;
self
}
pub fn visible(mut self, is_visible: i32) -> Self {
self.is_visible = is_visible;
self
}
}