···11+use crate::api::errors::{ErrorDetailResponse, JsonError, PackNotFoundError};
22+use crate::api::{errors::internal_server_error, middleware::authorization::Authorization};
33+use crate::dto::*;
44+use axum::{Extension, Json, extract::Path};
55+66+#[derive(serde::Serialize, utoipa::ToSchema)]
77+#[cfg_attr(test, derive(serde::Deserialize))]
88+pub struct GetPackResponse {
99+ pack: Pack,
1010+ pack_cards: Option<Vec<Card>>,
1111+}
1212+1313+#[utoipa::path(
1414+ get,
1515+ path = "/api/v1/packs/{pack_id}",
1616+ description = "Get a pack. The contents of unopened packs cannot be seen.",
1717+ tags = ["Pack", "Player"],
1818+ security(("trust" = [])),
1919+ responses(
2020+ (status = OK, description = "Successfully retrieved pack.", body = GetPackResponse),
2121+ (status = NOT_FOUND, description = "Pack not found, or not your pack.", body = ErrorDetailResponse),
2222+ ),
2323+ params(
2424+ ("pack_id" = i64, Path, description = "The ID of the pack to retrieve.")
2525+ )
2626+)]
2727+pub async fn get_pack(
2828+ db: Extension<sqlx::PgPool>,
2929+ authorization: Extension<Authorization>,
3030+ Path(pack_id): Path<i64>,
3131+) -> axum::response::Result<Json<GetPackResponse>> {
3232+ let account_id = authorization.authorized_account_id()?;
3333+ let mut conn = db.acquire().await.map_err(internal_server_error)?;
3434+3535+ let pack = sqlx::query_as!(
3636+ Pack,
3737+ r#"
3838+ SELECT packs.id, packs.pack_banner_id, packs.opened_at
3939+ FROM packs
4040+ WHERE id = $1 AND account_id = $2
4141+ "#,
4242+ pack_id,
4343+ account_id,
4444+ )
4545+ .fetch_optional(&mut *conn)
4646+ .await
4747+ .map_err(internal_server_error)?
4848+ .ok_or(JsonError(PackNotFoundError { pack_id }))?;
4949+5050+ if pack.opened_at.is_none() {
5151+ return Ok(Json(GetPackResponse {
5252+ pack,
5353+ pack_cards: None,
5454+ }));
5555+ }
5656+ let pack_cards = sqlx::query_as!(
5757+ Card,
5858+ r#"
5959+ SELECT id, card_type_id
6060+ FROM cards
6161+ INNER JOIN pack_contents ON pack_contents.card_id = cards.id
6262+ WHERE pack_contents.pack_id = $1
6363+ "#,
6464+ pack_id,
6565+ )
6666+ .fetch_all(&mut *conn)
6767+ .await
6868+ .map_err(internal_server_error)?;
6969+ Ok(Json(GetPackResponse {
7070+ pack,
7171+ pack_cards: Some(pack_cards),
7272+ }))
7373+}
7474+7575+#[cfg(test)]
7676+mod tests {
7777+ use crate::test::prelude::*;
7878+ use axum::http::{Request, StatusCode};
7979+ use sqlx::PgPool;
8080+8181+ use super::GetPackResponse;
8282+8383+ #[sqlx::test(
8484+ migrator = "MIGRATOR",
8585+ fixtures(path = "../../../fixtures", scripts("seed", "account", "packs"))
8686+ )]
8787+ pub fn get_pack_unopened(pool: PgPool) {
8888+ let pack = sqlx::query!(
8989+ "SELECT id FROM packs WHERE account_id = 'foxfriends' AND opened_at IS NULL"
9090+ )
9191+ .fetch_one(&pool)
9292+ .await
9393+ .unwrap();
9494+9595+ let app = crate::app::Config::test(pool).into_router();
9696+9797+ let request = Request::get(format!("/api/v1/packs/{}", pack.id))
9898+ .header("Authorization", "Trust foxfriends")
9999+ .empty()
100100+ .unwrap();
101101+102102+ let Ok(response) = app.oneshot(request).await;
103103+ assert_success!(response);
104104+105105+ let response: GetPackResponse = response.json().await.unwrap();
106106+ assert!(response.pack.opened_at.is_none());
107107+ assert!(response.pack_cards.is_none());
108108+ }
109109+110110+ #[sqlx::test(
111111+ migrator = "MIGRATOR",
112112+ fixtures(path = "../../../fixtures", scripts("seed", "account", "packs"))
113113+ )]
114114+ pub fn get_pack_opened(pool: PgPool) {
115115+ let pack = sqlx::query!(
116116+ "SELECT id FROM packs WHERE account_id = 'foxfriends' AND opened_at IS NOT NULL"
117117+ )
118118+ .fetch_one(&pool)
119119+ .await
120120+ .unwrap();
121121+122122+ let app = crate::app::Config::test(pool).into_router();
123123+124124+ let request = Request::get(format!("/api/v1/packs/{}", pack.id))
125125+ .header("Authorization", "Trust foxfriends")
126126+ .empty()
127127+ .unwrap();
128128+129129+ let Ok(response) = app.oneshot(request).await;
130130+ assert_success!(response);
131131+132132+ let response: GetPackResponse = response.json().await.unwrap();
133133+ assert!(response.pack.opened_at.is_some());
134134+ assert!(response.pack_cards.is_some());
135135+ assert_eq!(response.pack_cards.as_ref().unwrap().len(), 5);
136136+ }
137137+138138+ #[sqlx::test(
139139+ migrator = "MIGRATOR",
140140+ fixtures(path = "../../../fixtures", scripts("account"))
141141+ )]
142142+ pub fn get_pack_not_found(pool: PgPool) {
143143+ let app = crate::app::Config::test(pool).into_router();
144144+145145+ let request = Request::get("/api/v1/packs/1")
146146+ .header("Authorization", "Trust foxfriends")
147147+ .empty()
148148+ .unwrap();
149149+150150+ let Ok(response) = app.oneshot(request).await;
151151+ assert_eq!(response.status(), StatusCode::NOT_FOUND);
152152+ }
153153+154154+ #[sqlx::test(
155155+ migrator = "MIGRATOR",
156156+ fixtures(path = "../../../fixtures", scripts("seed", "account", "packs"))
157157+ )]
158158+ pub fn get_pack_not_owned(pool: PgPool) {
159159+ let pack = sqlx::query!(
160160+ "SELECT id FROM packs WHERE account_id = 'foxfriends' AND opened_at IS NULL"
161161+ )
162162+ .fetch_one(&pool)
163163+ .await
164164+ .unwrap();
165165+166166+ let app = crate::app::Config::test(pool).into_router();
167167+168168+ let request = Request::get(format!("/api/v1/packs/{}", pack.id))
169169+ .header("Authorization", "Trust not-foxfriends")
170170+ .empty()
171171+ .unwrap();
172172+173173+ let Ok(response) = app.oneshot(request).await;
174174+ assert_eq!(response.status(), StatusCode::NOT_FOUND);
175175+ }
176176+}
+2
packages/cartography/src/api/operations/mod.rs
···1111mod list_fields;
1212pub use list_fields::*;
13131414+mod get_pack;
1415mod list_packs;
1516mod open_pack;
1717+pub use get_pack::*;
1618pub use list_packs::*;
1719pub use open_pack::*;