1macro_rules! into_i16_index {
4 ( $( $t:ty ),+ $(,)? ) => {
5 $(
6 impl From<$t> for i16 {
7 fn from(t: $t) -> Self {
8 t.0
9 }
10 }
11
12 impl From<$t> for i64 {
14 fn from(t: $t) -> Self {
15 t.0 as i64
16 }
17 }
18 )+
19 };
20}
21
22macro_rules! wrap_uuid {
35 (
36 $(#[$outer:meta])*
37 $vis:vis struct $t:ident
38 ) => {
39 #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, PathPart, Hash)]
40 $(#[$outer])*
41 #[cfg_attr(feature = "backend", derive(sqlx::Type))]
42 #[cfg_attr(feature = "backend", sqlx(transparent))]
43 $vis struct $t(pub uuid::Uuid);
44
45 impl From<$t> for uuid::Uuid {
46 fn from(t: $t) -> Self {
47 t.0
48 }
49 }
50
51 impl std::fmt::Display for $t {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "{}", self.0)
54 }
55 }
56
57 impl std::str::FromStr for $t {
58 type Err = uuid::Error;
59
60 #[inline]
61 fn from_str(value: &str) -> Result<Self, Self::Err> {
62 Ok(Self(uuid::Uuid::from_str(value)?))
63 }
64 }
65
66 impl $t {
67 #[inline]
69 #[must_use]
70 $vis const fn from_u128(v: u128) -> Self {
71 Self(uuid::Uuid::from_u128(v))
72 }
73 }
74 }
75}
76
77pub mod additional_resource;
78pub mod admin;
79pub mod animation;
80pub mod asset;
81pub mod audio;
82pub mod billing;
83pub mod category;
84pub mod circle;
85pub mod course;
86pub mod image;
87pub mod jig;
88pub mod locale;
89pub mod media;
90pub mod meta;
91pub mod module;
92pub mod pdf;
93pub mod playlist;
94pub mod resource;
95pub mod search;
96pub mod ser;
97pub mod session;
98pub mod user;
99
100#[deprecated]
101pub mod auth {
103
104 #[deprecated]
105 pub use super::session::AUTH_COOKIE_NAME;
106
107 #[deprecated]
108 pub use super::session::CSRF_HEADER_NAME;
109
110 #[deprecated]
111 pub use super::user::CreateProfileRequest as RegisterRequest;
112}
113
114use chrono::Utc;
115use ser::{csv_encode_i16_indices, csv_encode_uuids, deserialize_optional_field, from_csv, to_csv};
116use serde::{Deserialize, Serialize};
117use std::fmt::{Display, Formatter};
118use uuid::Uuid;
119
120#[derive(Debug)]
122pub struct Base64<T>(pub T);
123
124impl<T: Display> serde::Serialize for Base64<T> {
125 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126 where
127 S: serde::Serializer,
128 {
129 serializer.serialize_str(&base64::encode(&self.0.to_string()))
130 }
131}
132
133impl<'de, E: std::fmt::Debug, T: std::str::FromStr<Err = E>> serde::Deserialize<'de> for Base64<T> {
134 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135 where
136 D: serde::Deserializer<'de>,
137 {
138 Ok(Self(deserializer.deserialize_str(ser::FromStrVisitor(
139 std::marker::PhantomData,
140 ))?))
141 }
142}
143#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
145pub struct CreateResponse<T: Into<Uuid>> {
146 pub id: T,
148}
149
150#[derive(Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, Debug)]
152pub enum Publish {
153 At(chrono::DateTime<Utc>),
155 In(std::time::Duration),
157}
158
159impl Publish {
160 #[must_use]
162 #[allow(clippy::missing_const_for_fn)]
163 pub fn now() -> Self {
164 Self::In(std::time::Duration::new(0, 0))
166 }
167}
168
169impl From<Publish> for chrono::DateTime<Utc> {
170 fn from(publish: Publish) -> Self {
171 match publish {
172 Publish::At(t) => t,
173 Publish::In(d) => {
174 Utc::now() + chrono::Duration::from_std(d).expect("Really really big duration?")
176 }
177 }
178 }
179}
180
181#[derive(Clone, Debug, Serialize, Default)]
186#[serde(untagged)]
187pub enum UpdateNullable<T> {
188 #[default]
190 Keep,
191 Unset,
193 Change(T),
195}
196
197impl<T> From<Option<T>> for UpdateNullable<T> {
198 fn from(value: Option<T>) -> Self {
199 match value {
200 Some(value) => Self::Change(value),
201 None => Self::Unset,
202 }
203 }
204}
205
206impl<'de, T> Deserialize<'de> for UpdateNullable<T>
207where
208 T: Deserialize<'de>,
209{
210 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
211 where
212 D: serde::Deserializer<'de>,
213 {
214 #[derive(Debug, Deserialize)]
215 #[serde(untagged)]
216 enum UpdateMap<T> {
217 Unset,
218 Change(T),
219 }
220
221 let mapping = UpdateMap::deserialize(deserializer)?;
222
223 let update = match mapping {
224 UpdateMap::Unset => Self::Unset,
225 UpdateMap::Change(val) => Self::Change(val),
226 };
227
228 Ok(update)
229 }
230}
231
232impl<T> UpdateNullable<T> {
233 pub const fn is_keep(&self) -> bool {
235 matches!(self, Self::Keep)
236 }
237
238 pub const fn is_unset(&self) -> bool {
240 matches!(self, Self::Unset)
241 }
242
243 pub const fn is_change(&self) -> bool {
245 matches!(self, Self::Change(_))
246 }
247
248 pub fn into_option(self) -> Option<T> {
252 match self {
253 Self::Keep | Self::Unset => None,
254 Self::Change(v) => Some(v),
255 }
256 }
257}
258
259#[derive(Clone, Debug, Serialize, Default)]
264#[serde(untagged)]
265pub enum UpdateNonNullable<T> {
266 #[default]
268 Keep,
269 Change(T),
271}
272
273impl<'de, T> Deserialize<'de> for UpdateNonNullable<T>
274where
275 T: Deserialize<'de>,
276{
277 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
278 where
279 D: serde::Deserializer<'de>,
280 {
281 Ok(Self::Change(T::deserialize(deserializer)?))
282 }
283}
284
285impl<T> UpdateNonNullable<T> {
286 pub const fn is_keep(&self) -> bool {
288 matches!(self, Self::Keep)
289 }
290
291 pub const fn is_change(&self) -> bool {
293 matches!(self, Self::Change(_))
294 }
295
296 pub fn into_option(self) -> Option<T> {
300 match self {
301 Self::Keep => None,
302 Self::Change(v) => Some(v),
303 }
304 }
305}
306
307impl<T> From<Option<T>> for UpdateNonNullable<T> {
308 fn from(value: Option<T>) -> Self {
309 value.map(UpdateNonNullable::Change).unwrap_or_default()
310 }
311}
312
313#[derive(Copy, Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
315pub struct Page(usize);
316
317impl From<usize> for Page {
318 fn from(value: usize) -> Self {
319 Self(value)
320 }
321}
322
323impl From<Page> for usize {
324 fn from(value: Page) -> Self {
325 value.0
326 }
327}
328
329#[cfg(feature = "backend")]
330impl From<Page> for i64 {
331 fn from(value: Page) -> Self {
332 value.0 as i64
333 }
334}
335
336impl Display for Page {
337 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
338 write!(f, "{}", self.0)
339 }
340}
341
342impl Page {
343 pub fn next_page(self) -> Self {
345 Self(self.0.saturating_add(1))
346 }
347
348 pub fn prev_page(self) -> Self {
350 Self(self.0.saturating_sub(1))
351 }
352}
353
354const DEFAULT_PAGE_LIMIT: usize = 20;
355
356#[derive(Serialize, Deserialize, Copy, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
358pub struct PageLimit(usize);
359
360impl Default for PageLimit {
361 fn default() -> Self {
362 Self(DEFAULT_PAGE_LIMIT)
363 }
364}
365
366impl From<usize> for PageLimit {
367 fn from(value: usize) -> Self {
368 Self(value)
369 }
370}
371
372impl From<PageLimit> for usize {
373 fn from(value: PageLimit) -> Self {
374 value.0
375 }
376}
377
378#[cfg(feature = "backend")]
379impl From<PageLimit> for i64 {
380 fn from(value: PageLimit) -> Self {
381 value.0 as i64
382 }
383}
384
385impl PageLimit {
386 #[cfg(feature = "backend")]
388 pub fn offset(&self, page: Page) -> i64 {
389 (self.0 * page.0) as i64
390 }
391}
392
393#[derive(Serialize, Deserialize, Copy, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
395pub struct ItemCount(usize);
396
397impl From<usize> for ItemCount {
398 fn from(value: usize) -> Self {
399 Self(value)
400 }
401}
402
403impl From<ItemCount> for usize {
404 fn from(value: ItemCount) -> Self {
405 value.0
406 }
407}
408
409#[cfg(feature = "backend")]
410impl From<ItemCount> for i64 {
411 fn from(value: ItemCount) -> Self {
412 value.0 as i64
413 }
414}
415
416impl ItemCount {
417 pub fn paged(&self, limit: PageLimit) -> Self {
419 let page_count = self.0 / limit.0 + (self.0 % limit.0 != 0) as usize;
422 page_count.into()
423 }
424}
425
426#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
428pub struct Percent(f64);
429
430impl Display for Percent {
431 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
432 write!(f, "{}%", self.0 * 100.0)
433 }
434}
435
436impl From<f64> for Percent {
437 fn from(value: f64) -> Self {
438 Self(value)
439 }
440}
441
442impl From<Percent> for f64 {
443 fn from(value: Percent) -> Self {
444 value.0
445 }
446}
447
448#[cfg(feature = "backend")]
449impl From<sqlx::types::BigDecimal> for Percent {
450 fn from(value: sqlx::types::BigDecimal) -> Self {
451 use bigdecimal::ToPrimitive;
452 Self(value.to_f64().unwrap_or_default())
453 }
454}
455
456#[cfg(feature = "backend")]
457impl From<Percent> for sqlx::types::BigDecimal {
458 fn from(value: Percent) -> Self {
459 Self::try_from(value.0).ok().unwrap_or_default()
460 }
461}
462
463