rb/routing/pages/
market.rs

1use authbeam::layout::LayoutComponent;
2use reva_axum::Template;
3use axum::response::IntoResponse;
4use axum::{
5    extract::{State, Query, Path},
6    response::Html,
7};
8use axum_extra::extract::CookieJar;
9
10use authbeam::model::{FinePermission, Item, ItemStatus, ItemType, Profile};
11
12use crate::config::Config;
13use crate::database::Database;
14use crate::model::DatabaseError;
15use crate::ToHtml;
16
17use super::MarketQuery;
18
19#[derive(Template)]
20#[template(path = "market/homepage.html")]
21struct HomepageTemplate {
22    config: Config,
23    lang: langbeam::LangFile,
24    profile: Option<Box<Profile>>,
25    unread: usize,
26    notifs: usize,
27    page: i32,
28    query: String,
29    status: ItemStatus,
30    creator: String,
31    customer: String,
32    items: Vec<(Item, Box<Profile>)>,
33    is_helper: bool,
34}
35
36/// GET /market
37pub async fn homepage_request(
38    jar: CookieJar,
39    State(database): State<Database>,
40    Query(props): Query<MarketQuery>,
41) -> impl IntoResponse {
42    let auth_user = match jar.get("__Secure-Token") {
43        Some(c) => match database
44            .auth
45            .get_profile_by_unhashed(c.value_trimmed())
46            .await
47        {
48            Ok(ua) => ua,
49            Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
50        },
51        None => return Html(DatabaseError::NotAllowed.to_html(database)),
52    };
53
54    let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
55
56    let notifs = database
57        .auth
58        .get_notification_count_by_recipient(&auth_user.id)
59        .await;
60
61    // permissions
62    let group = match database.auth.get_group_by_id(auth_user.group).await {
63        Ok(g) => g,
64        Err(e) => return Html(e.to_string()),
65    };
66
67    if (props.status != ItemStatus::Approved) && (props.status != ItemStatus::Featured) {
68        // check permission to see unapproved items
69        if !group.permissions.check(FinePermission::ECON_MASTER) {
70            // we must have the "Manager" permission to edit other users
71            return Html(DatabaseError::NotAllowed.to_string());
72        }
73    }
74
75    let is_helper = group.permissions.check_helper();
76
77    if !props.customer.is_empty() && (props.customer != auth_user.id) && !is_helper {
78        // cannot view the owned items of anybody else (unless you're a helper)
79        return Html(DatabaseError::NotAllowed.to_html(database));
80    }
81
82    // data
83    let items = if props.creator.is_empty() {
84        if let Some(r#type) = props.r#type {
85            match database
86                .auth
87                .get_items_by_type_paginated(r#type, props.page)
88                .await
89            {
90                Ok(i) => i,
91                Err(e) => return Html(e.to_string()),
92            }
93        } else {
94            match database
95                .auth
96                .get_items_by_status_searched_paginated(props.status.clone(), props.page, &props.q)
97                .await
98            {
99                Ok(i) => i,
100                Err(e) => return Html(e.to_string()),
101            }
102        }
103    } else if !props.customer.is_empty() {
104        match database
105            .auth
106            .get_transactions_by_customer_paginated(&props.customer, props.page)
107            .await
108        {
109            Ok(i) => {
110                let mut out = Vec::new();
111
112                for x in i {
113                    out.push((
114                        match x.0 .1 {
115                            Some(i) => i.clone(),
116                            None => return Html(DatabaseError::NotFound.to_html(database)),
117                        },
118                        x.2.clone(),
119                    ))
120                }
121
122                out
123            }
124            Err(e) => return Html(e.to_string()),
125        }
126    } else {
127        if (auth_user.id != props.creator) && !is_helper {
128            // we cannot sort by somebody that isnt us if we arent helper
129            return Html(DatabaseError::NotAllowed.to_html(database));
130        }
131
132        if let Some(r#type) = props.r#type {
133            // creator and type
134            match database
135                .auth
136                .get_items_by_creator_type_paginated(&props.creator, r#type, props.page)
137                .await
138            {
139                Ok(i) => i,
140                Err(e) => return Html(e.to_string()),
141            }
142        } else {
143            // no type, just creator
144            match database
145                .auth
146                .get_items_by_creator_paginated(&props.creator, props.page)
147                .await
148            {
149                Ok(i) => i,
150                Err(e) => return Html(e.to_string()),
151            }
152        }
153    };
154
155    // ...
156    Html(
157        HomepageTemplate {
158            config: database.config.clone(),
159            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
160                c.value_trimmed()
161            } else {
162                ""
163            }),
164            profile: Some(auth_user),
165            unread,
166            notifs,
167            page: props.page,
168            query: props.q,
169            status: props.status,
170            creator: props.creator,
171            customer: props.customer,
172            items,
173            is_helper,
174        }
175        .render()
176        .unwrap(),
177    )
178}
179
180#[derive(Template)]
181#[template(path = "market/new.html")]
182struct CreateTemplate {
183    config: Config,
184    lang: langbeam::LangFile,
185    profile: Option<Box<Profile>>,
186    unread: usize,
187    notifs: usize,
188}
189
190/// GET /market/new
191pub async fn create_request(jar: CookieJar, State(database): State<Database>) -> impl IntoResponse {
192    let auth_user = match jar.get("__Secure-Token") {
193        Some(c) => match database
194            .auth
195            .get_profile_by_unhashed(c.value_trimmed())
196            .await
197        {
198            Ok(ua) => ua,
199            Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
200        },
201        None => return Html(DatabaseError::NotAllowed.to_html(database)),
202    };
203
204    let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
205
206    let notifs = database
207        .auth
208        .get_notification_count_by_recipient(&auth_user.id)
209        .await;
210
211    Html(
212        CreateTemplate {
213            config: database.config.clone(),
214            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
215                c.value_trimmed()
216            } else {
217                ""
218            }),
219            profile: Some(auth_user),
220            unread,
221            notifs,
222        }
223        .render()
224        .unwrap(),
225    )
226}
227
228#[derive(Template)]
229#[template(path = "market/item.html")]
230struct ItemTemplate {
231    config: Config,
232    lang: langbeam::LangFile,
233    profile: Option<Box<Profile>>,
234    unread: usize,
235    notifs: usize,
236    item: Item,
237    creator: Box<Profile>,
238    is_owned: bool,
239    is_helper: bool,
240    reaction_count: usize,
241}
242
243/// GET /market/item/{id}
244pub async fn item_request(
245    jar: CookieJar,
246    Path(id): Path<String>,
247    State(database): State<Database>,
248) -> impl IntoResponse {
249    let auth_user = match jar.get("__Secure-Token") {
250        Some(c) => match database
251            .auth
252            .get_profile_by_unhashed(c.value_trimmed())
253            .await
254        {
255            Ok(ua) => ua,
256            Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
257        },
258        None => return Html(DatabaseError::NotAllowed.to_html(database)),
259    };
260
261    let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
262
263    let notifs = database
264        .auth
265        .get_notification_count_by_recipient(&auth_user.id)
266        .await;
267
268    // permissions
269    let group = match database.auth.get_group_by_id(auth_user.group).await {
270        Ok(g) => g,
271        Err(e) => return Html(e.to_string()),
272    };
273
274    let is_helper = group.permissions.check_helper();
275
276    // data
277    let item = match database.auth.get_item(&id).await {
278        Ok(i) => i,
279        Err(e) => return Html(e.to_string()),
280    };
281
282    if !is_helper
283        && (item.status != ItemStatus::Approved)
284        && (item.status != ItemStatus::Featured)
285        && auth_user.id != item.creator
286    {
287        // users who aren't helpers cannot view unapproved items
288        return Html(DatabaseError::NotAllowed.to_string());
289    }
290
291    // ...
292    Html(
293        ItemTemplate {
294            config: database.config.clone(),
295            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
296                c.value_trimmed()
297            } else {
298                ""
299            }),
300            is_owned: database
301                .auth
302                .get_transaction_by_customer_item(&auth_user.id, &item.id)
303                .await
304                .is_ok(),
305            profile: Some(auth_user),
306            unread,
307            notifs,
308            creator: match database.auth.get_profile(&item.creator).await {
309                Ok(ua) => ua,
310                Err(e) => return Html(e.to_string()),
311            },
312            item,
313            is_helper,
314            reaction_count: database.get_reaction_count_by_asset(id).await,
315        }
316        .render()
317        .unwrap(),
318    )
319}
320
321#[derive(Template)]
322#[template(path = "partials/components/theme_playground.html")]
323struct ThemePlaygroundTemplate {
324    config: Config,
325    lang: langbeam::LangFile,
326    profile: Option<Box<Profile>>,
327    css: String,
328}
329
330/// GET /market/_app/theme_playground.html
331pub async fn theme_playground_request(
332    jar: CookieJar,
333    Path(id): Path<String>,
334    State(database): State<Database>,
335) -> impl IntoResponse {
336    let auth_user = match jar.get("__Secure-Token") {
337        Some(c) => match database
338            .auth
339            .get_profile_by_unhashed(c.value_trimmed())
340            .await
341        {
342            Ok(ua) => ua,
343            Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
344        },
345        None => return Html(DatabaseError::NotAllowed.to_html(database)),
346    };
347
348    // data
349    let item = match database.auth.get_item(&id).await {
350        Ok(i) => i,
351        Err(e) => return Html(e.to_string()),
352    };
353
354    if item.r#type != ItemType::UserTheme {
355        return Html(DatabaseError::ValueError.to_string());
356    }
357
358    // ...
359    Html(
360        ThemePlaygroundTemplate {
361            config: database.config.clone(),
362            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
363                c.value_trimmed()
364            } else {
365                ""
366            }),
367            profile: Some(auth_user),
368            css: item.content,
369        }
370        .render()
371        .unwrap(),
372    )
373}
374
375#[derive(Template)]
376#[template(path = "partials/components/layout_playground.html")]
377struct LayoutPlaygroundTemplate {
378    config: Config,
379    lang: langbeam::LangFile,
380    profile: Option<Box<Profile>>,
381    layout: LayoutComponent,
382}
383
384/// GET /market/_app/layout_playground.html
385pub async fn layout_playground_request(
386    jar: CookieJar,
387    Path(id): Path<String>,
388    State(database): State<Database>,
389) -> impl IntoResponse {
390    let auth_user = match jar.get("__Secure-Token") {
391        Some(c) => match database
392            .auth
393            .get_profile_by_unhashed(c.value_trimmed())
394            .await
395        {
396            Ok(ua) => ua,
397            Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
398        },
399        None => return Html(DatabaseError::NotAllowed.to_html(database)),
400    };
401
402    // data
403    let item = match database.auth.get_item(&id).await {
404        Ok(i) => i,
405        Err(e) => return Html(e.to_string()),
406    };
407
408    if item.r#type != ItemType::Layout {
409        return Html(DatabaseError::ValueError.to_string());
410    }
411
412    // ...
413    Html(
414        LayoutPlaygroundTemplate {
415            config: database.config.clone(),
416            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
417                c.value_trimmed()
418            } else {
419                ""
420            }),
421            profile: Some(auth_user),
422            layout: match serde_json::from_str(&item.content) {
423                Ok(l) => l,
424                Err(_) => return Html(DatabaseError::ValueError.to_string()),
425            },
426        }
427        .render()
428        .unwrap(),
429    )
430}