Skip to content

Commit 6720c2d

Browse files
committed
feat: add option for sorting members by recent apperance
in addition to the existing option of sorting by number of appearances
1 parent 4a8a207 commit 6720c2d

13 files changed

Lines changed: 342 additions & 97 deletions

File tree

database/.sqlx/query-05764f174d03456037b1be29b77f99e5d6acc583b63e6d87950172ee4191feb8.json renamed to database/.sqlx/query-e3b5e2a3943db6bc2360902da2db65fe759315968fa31f506d5ef2d6dc90b29b.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

database/src/models.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,78 @@ mod news;
66
mod occurrence;
77
mod token;
88

9+
use std::borrow::Borrow;
10+
911
pub use comic::*;
1012
pub use item::*;
1113
pub use item_type::*;
1214
pub use log_entry::*;
1315
pub use news::*;
1416
pub use occurrence::*;
1517
pub use token::*;
18+
19+
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
20+
pub struct ComicId(u16);
21+
22+
impl ComicId {
23+
#[inline]
24+
pub fn as_inner(&self) -> &u16 {
25+
&self.0
26+
}
27+
28+
#[inline]
29+
pub fn into_inner(self) -> u16 {
30+
self.0
31+
}
32+
}
33+
34+
impl PartialEq<u16> for ComicId {
35+
fn eq(&self, other: &u16) -> bool {
36+
self.0.eq(other)
37+
}
38+
}
39+
40+
impl From<u16> for ComicId {
41+
fn from(comic_id: u16) -> Self {
42+
Self(comic_id)
43+
}
44+
}
45+
46+
impl Borrow<u16> for ComicId {
47+
fn borrow(&self) -> &u16 {
48+
self.as_inner()
49+
}
50+
}
51+
52+
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
53+
pub struct ItemId(u16);
54+
55+
impl ItemId {
56+
#[inline]
57+
pub fn as_inner(&self) -> &u16 {
58+
&self.0
59+
}
60+
61+
#[inline]
62+
pub fn into_inner(self) -> u16 {
63+
self.0
64+
}
65+
}
66+
67+
impl PartialEq<u16> for ItemId {
68+
fn eq(&self, other: &u16) -> bool {
69+
self.0.eq(other)
70+
}
71+
}
72+
73+
impl From<u16> for ItemId {
74+
fn from(item_id: u16) -> Self {
75+
Self(item_id)
76+
}
77+
}
78+
79+
impl Borrow<u16> for ItemId {
80+
fn borrow(&self) -> &u16 {
81+
self.as_inner()
82+
}
83+
}

database/src/models/item.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use futures::TryStreamExt;
22

33
use std::collections::{BTreeMap, HashSet};
44

5-
use crate::models::ItemType;
5+
use crate::models::{ComicId, ItemId, ItemType};
66

77
#[derive(Debug)]
88
pub struct Item {
@@ -205,11 +205,12 @@ impl Item {
205205
comic_id: u16,
206206
include_guest_comics: Option<bool>,
207207
include_non_canon_comics: Option<bool>,
208-
) -> sqlx::Result<BTreeMap<u16, u16>>
208+
) -> sqlx::Result<PreviousAppearances>
209209
where
210210
E: 'e + sqlx::Executor<'c, Database = crate::DatabaseDriver>,
211211
{
212-
sqlx::query_as!(
212+
let mut order = Vec::new();
213+
let map = sqlx::query_as!(
213214
PrevNext,
214215
r#"
215216
SELECT
@@ -222,6 +223,7 @@ impl Item {
222223
AND (? is NULL OR `c`.`is_guest_comic` = ?)
223224
AND (? is NULL OR `c`.`is_non_canon` = ?)
224225
GROUP BY `i`.`id`
226+
ORDER BY `comic` DESC
225227
"#,
226228
comic_id,
227229
include_guest_comics,
@@ -230,15 +232,23 @@ impl Item {
230232
include_non_canon_comics,
231233
)
232234
.fetch(executor)
233-
.try_filter_map(|pn| async move {
234-
if let Some(comic) = pn.comic {
235-
Ok(Some((pn.id, comic)))
236-
} else {
237-
Ok(None)
235+
.try_filter_map(|pn| {
236+
if pn.comic.is_some() {
237+
order.push(ItemId::from(pn.id));
238+
}
239+
async move {
240+
Ok(pn
241+
.comic
242+
.map(|comic| (ItemId::from(pn.id), ComicId::from(comic))))
238243
}
239244
})
240245
.try_collect()
241-
.await
246+
.await?;
247+
248+
Ok(PreviousAppearances {
249+
appearances: map,
250+
order,
251+
})
242252
}
243253

244254
#[tracing::instrument(skip(executor))]
@@ -751,6 +761,11 @@ struct PrevNext {
751761
comic: Option<u16>,
752762
}
753763

764+
pub struct PreviousAppearances {
765+
pub appearances: BTreeMap<ItemId, ComicId>,
766+
pub order: Vec<ItemId>,
767+
}
768+
754769
// Can't deprecate these fields because `sqlx::FromRow` causes them to be
755770
// used and trigger warnings.
756771
#[derive(Debug, sqlx::FromRow)]

src/api/v1/controllers/comic/navigation_data.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::api::v1::models::{NavigationData, UnhydratedItemNavigationData};
22
use crate::models::ComicId;
33
use actix_web::{error, Result};
4-
use database::models::Item as DatabaseItem;
4+
use database::models::{Item as DatabaseItem, PreviousAppearances};
55
use database::DbPoolConnection;
66
use std::convert::TryInto;
77

@@ -21,7 +21,10 @@ pub async fn fetch_all_item_navigation_data(
2121
.await
2222
.map_err(error::ErrorInternalServerError)?;
2323

24-
let previous = DatabaseItem::previous_apperances_by_comic_id_mapped_by_id(
24+
let PreviousAppearances {
25+
appearances: previous,
26+
..
27+
} = DatabaseItem::previous_apperances_by_comic_id_mapped_by_id(
2528
&mut **conn,
2629
comic_id.into_inner(),
2730
include_guest_comics,

src/api/v2/controllers/comic/by_id.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::api::v2::controllers::comic::editor_data::fetch_editor_data_for_comic;
22
use crate::api::v2::controllers::comic::navigation_data::{
3-
fetch_all_item_navigation_data, fetch_comic_item_navigation_data,
3+
fetch_all_item_navigation_data, fetch_comic_item_navigation_data, ItemNavigationDataSorting,
44
};
55
use crate::api::v2::models::{
66
Comic, ComicData, EditorData, Exclusion, Inclusion, ItemNavigationData, MissingComic,
7-
MissingEditorData, PresentComic,
7+
MissingEditorData, PresentComic, Sorting,
88
};
99
use crate::models::{ComicId, False, True};
1010
use crate::util::NewsUpdater;
@@ -86,6 +86,10 @@ pub(crate) async fn by_id(
8686
comic_id,
8787
include_guest_comics,
8888
include_non_canon_comics,
89+
match query.sorting {
90+
Some(Sorting::ByLastAppearance) => ItemNavigationDataSorting::ByLastAppearance,
91+
Some(Sorting::ByCount) | None => ItemNavigationDataSorting::ByCount,
92+
},
8993
)
9094
.await?
9195
.into_iter()
@@ -173,4 +177,5 @@ pub(crate) async fn by_id(
173177
pub(crate) struct ByIdQuery {
174178
exclude: Option<Exclusion>,
175179
include: Option<Inclusion>,
180+
sorting: Option<Sorting>,
176181
}

src/api/v2/controllers/comic/navigation_data.rs

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
use crate::api::v2::models::{NavigationData, UnhydratedItemNavigationData};
22
use crate::models::ComicId;
33
use actix_web::{error, Result};
4-
use database::models::Item as DatabaseItem;
4+
use database::models::{Item as DatabaseItem, ItemId as DatabaseItemId, PreviousAppearances};
55
use database::DbPoolConnection;
66
use std::convert::TryInto;
77

8+
#[derive(Clone, Copy, Debug)]
9+
pub enum ItemNavigationDataSorting {
10+
ByCount,
11+
ByLastAppearance,
12+
}
13+
814
#[tracing::instrument(skip(conn))]
915
#[allow(clippy::too_many_lines)]
1016
pub async fn fetch_all_item_navigation_data(
1117
conn: &mut DbPoolConnection,
1218
comic_id: ComicId,
1319
include_guest_comics: Option<bool>,
1420
include_non_canon_comics: Option<bool>,
21+
sorting: ItemNavigationDataSorting,
1522
) -> Result<Vec<UnhydratedItemNavigationData>> {
1623
let first_last_counts = DatabaseItem::first_and_last_apperances_and_count(
1724
&mut **conn,
@@ -21,7 +28,10 @@ pub async fn fetch_all_item_navigation_data(
2128
.await
2229
.map_err(error::ErrorInternalServerError)?;
2330

24-
let previous = DatabaseItem::previous_apperances_by_comic_id_mapped_by_id(
31+
let PreviousAppearances {
32+
appearances: previous,
33+
order: last_appearance_order,
34+
} = DatabaseItem::previous_apperances_by_comic_id_mapped_by_id(
2535
&mut **conn,
2636
comic_id.into_inner(),
2737
include_guest_comics,
@@ -39,37 +49,83 @@ pub async fn fetch_all_item_navigation_data(
3949
.await
4050
.map_err(error::ErrorInternalServerError)?;
4151

42-
Ok(first_last_counts
43-
.into_iter()
44-
.map(|flc| UnhydratedItemNavigationData {
45-
id: flc.id.into(),
46-
navigation_data: NavigationData {
47-
first: flc
48-
.first
49-
.map(TryInto::try_into)
50-
.transpose()
51-
.expect("database has valid comicIds"),
52-
previous: previous
53-
.get(&flc.id)
54-
.copied()
55-
.map(TryInto::try_into)
56-
.transpose()
57-
.expect("database has valid comicIds"),
58-
next: next
59-
.get(&flc.id)
60-
.copied()
61-
.map(TryInto::try_into)
62-
.transpose()
63-
.expect("database has valid comicIds"),
64-
last: flc
65-
.last
66-
.map(TryInto::try_into)
67-
.transpose()
68-
.expect("database has valid comicIds"),
69-
},
70-
count: flc.count,
71-
})
72-
.collect())
52+
match sorting {
53+
ItemNavigationDataSorting::ByCount => Ok(first_last_counts
54+
.into_iter()
55+
.map(|flc| UnhydratedItemNavigationData {
56+
id: flc.id.into(),
57+
navigation_data: NavigationData {
58+
first: flc
59+
.first
60+
.map(TryInto::try_into)
61+
.transpose()
62+
.expect("database has valid comicIds"),
63+
previous: previous
64+
.get(&flc.id)
65+
.copied()
66+
.map(TryInto::try_into)
67+
.transpose()
68+
.expect("database has valid comicIds"),
69+
next: next
70+
.get(&flc.id)
71+
.copied()
72+
.map(TryInto::try_into)
73+
.transpose()
74+
.expect("database has valid comicIds"),
75+
last: flc
76+
.last
77+
.map(TryInto::try_into)
78+
.transpose()
79+
.expect("database has valid comicIds"),
80+
},
81+
count: flc.count,
82+
})
83+
.collect()),
84+
ItemNavigationDataSorting::ByLastAppearance => Ok(last_appearance_order
85+
.iter()
86+
.map(DatabaseItemId::as_inner)
87+
.chain(
88+
first_last_counts
89+
.iter()
90+
.filter(|flc| !last_appearance_order.iter().any(|&i| i == flc.id))
91+
.map(|flc| &flc.id),
92+
)
93+
.map(|&id| {
94+
first_last_counts
95+
.iter()
96+
.find(|flc| flc.id == id)
97+
.expect("database has valid itemIds")
98+
})
99+
.map(|flc| UnhydratedItemNavigationData {
100+
id: flc.id.into(),
101+
navigation_data: NavigationData {
102+
first: flc
103+
.first
104+
.map(TryInto::try_into)
105+
.transpose()
106+
.expect("database has valid comicIds"),
107+
previous: previous
108+
.get(&flc.id)
109+
.copied()
110+
.map(TryInto::try_into)
111+
.transpose()
112+
.expect("database has valid comicIds"),
113+
next: next
114+
.get(&flc.id)
115+
.copied()
116+
.map(TryInto::try_into)
117+
.transpose()
118+
.expect("database has valid comicIds"),
119+
last: flc
120+
.last
121+
.map(TryInto::try_into)
122+
.transpose()
123+
.expect("database has valid comicIds"),
124+
},
125+
count: flc.count,
126+
})
127+
.collect()),
128+
}
73129
}
74130

75131
#[tracing::instrument(skip(conn))]

0 commit comments

Comments
 (0)