shared/domain/category.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
//! Types for categories.
use chrono::{DateTime, Utc};
use macros::make_path_parts;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{api::endpoints::PathPart, domain::user::UserScope};
wrap_uuid! {
/// Wrapper type around [`Uuid`], represents the ID of a category.
///
/// [`Uuid`]: ../../uuid/struct.Uuid.html
pub struct CategoryId
}
#[derive(Serialize, Deserialize, Debug)]
/// The response returned when a request for categories is successful.
pub struct CategoryResponse {
/// The categories returned.
pub categories: Vec<Category>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
/// The over-the-wire representation of a category.
pub struct Category {
/// The category's id.
pub id: CategoryId,
/// The category's user_scope.
pub user_scopes: Vec<UserScope>,
/// The category's name.
pub name: String,
/// The category's children, if any.
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub children: Vec<Category>,
/// When the category was initially created.
pub created_at: DateTime<Utc>,
/// When the category was last updated.
pub updated_at: Option<DateTime<Utc>>,
}
#[derive(Serialize, Deserialize, Debug)]
/// When getting a tree of categories, which direction should the categories be followed?
pub enum CategoryTreeScope {
/// Follow the parents up to the root.
Ancestors,
/// Follow the children down.
Descendants,
}
#[derive(Serialize, Deserialize)]
/// Request to create a category.
pub struct CreateCategoryRequest {
/// The name of the new category.
pub name: String,
/// The [`id`](Category::id) of the parent [`Category`](Category) to attatch it to.
pub parent_id: Option<CategoryId>,
}
make_path_parts!(GetCategoryPath => "/v1/category");
/// Request to get a tree of categories.
///
/// # Examples
///
/// There are a few different use cases.
///
/// ### get root categories.
/// ```ignore
/// GetCategoryRequest { ids: vec![], scope: None }
/// ```
///
/// Additionally, you can do the same with `scope: Some(CategoryTreeScope::Ancestors)` but it is not considered the cannonical form.
///
/// ### get all categories
/// ```ignore
/// GetCategoryRequest { ids: vec![], scope: Some(CategoryTreeScope::Descendants) }
/// ```
///
/// ### get exact categories
/// ```ignore
/// GetCategoryRequest { ids: vec![id1, id2, ...], scope: None }
/// ```
///
/// ### get exact categories and their ancestors
/// ```ignore
/// GetCategoryRequest { ids: vec![id1, id2, ...], scope: Some(CategoryTreeScope::Ancestors) }
/// ```
///
/// ### get exact categories and their decendants.
/// ```ignore
/// GetCategoryRequest { ids: vec![id1, id2, ...], scope: Some(CategoryTreeScope::Descendants) }
/// ```
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct GetCategoryRequest {
// fixme: Use CategoryId, unfortunately, sqlx doesn't currently allow for passing of T
// the backend _could_ transmute the `CategoryId`s into `Uuid`s, but that's `unsafe`.
/// The exact ids to be included in the response.
#[serde(default)]
#[serde(serialize_with = "super::csv_encode_uuids")]
#[serde(deserialize_with = "super::from_csv")]
pub ids: Vec<Uuid>,
/// Which direction to follow the tree.
#[serde(default)]
pub scope: Option<CategoryTreeScope>,
}
make_path_parts!(CreateCategoryPath => "/v1/category");
#[derive(Serialize, Deserialize)]
/// Response returned when a new category is created.
pub struct NewCategoryResponse {
/// The offset visual offset into the parent category.
pub index: u16,
/// The id of the new category.
pub id: CategoryId,
}
make_path_parts!(UpdateCategoryPath => "/v1/category/{}" => CategoryId);
#[derive(Serialize, Deserialize, Default, Eq, PartialEq)]
/// Request to update a category.
///
/// All fields are optional, any field that is [`None`] will not be updated.
///
/// # Errors
///
/// * [`UpdateError::OutOfRange`](crate::error::category::UpdateError::OutOfRange) if the given index is past the end of the parent.
pub struct UpdateCategoryRequest {
/// If [`Some`] change the category's name to this name
pub name: Option<String>,
/// If [`Some`], change the parent to the given `Option<CategoryId>`.
///
/// Specifically, if [`None`], don't update.
/// If `Some(None)`, set the parent to [`None`].
/// Otherwise set it to the given [`CategoryId`].
#[serde(deserialize_with = "super::deserialize_optional_field")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub parent_id: Option<Option<CategoryId>>,
/// If [`Some`] move to _before_ the category with the given index (ie, 0 moves to the start).
///
/// # interactions
/// If `index` is [`None`], and [`parent_id`](UpdateCategoryRequest::parent_id) is [`Some`] it will append to the end of the new parent.
pub index: Option<u16>,
/// If [`Some`] add a user scope
#[serde(skip_serializing_if = "Option::is_none")]
pub user_scopes: Option<Vec<UserScope>>,
}
make_path_parts!(DeleteCategoryPath => "/v1/category/{}" => CategoryId);