Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API routes to request multiple of an item #70

Merged
merged 13 commits into from
Oct 5, 2020
376 changes: 376 additions & 0 deletions sqlx-data.json

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions src/database/models/mod_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,45 @@ impl Mod {
}
}

pub async fn get_many<'a, E>(mod_ids: Vec<i64>, exec: E) -> Result<Vec<Mod>, sqlx::Error>
Geometrically marked this conversation as resolved.
Show resolved Hide resolved
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;

let mods = sqlx::query!(
"
SELECT id, title, description, downloads,
icon_url, body_url, published,
issues_url, source_url, wiki_url,
team_id
FROM mods
WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))
",
&mod_ids
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|m| Mod {
id: ModId(m.id),
team_id: TeamId(m.team_id),
title: m.title,
description: m.description,
downloads: m.downloads,
body_url: m.body_url,
icon_url: m.icon_url,
published: m.published,
issues_url: m.issues_url,
source_url: m.source_url,
wiki_url: m.wiki_url,
}))
})
.try_collect::<Vec<Mod>>()
.await?;

Ok(mods)
}

pub async fn remove_full<'a, 'b, E>(
id: ModId,
exec: E,
Expand Down
59 changes: 58 additions & 1 deletion src/database/models/user_item.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::ids::UserId;
use super::ids::{ModId, UserId};

pub struct User {
pub id: UserId,
Expand Down Expand Up @@ -112,4 +112,61 @@ impl User {
Ok(None)
}
}

pub async fn get_many<'a, E>(user_ids: Vec<i64>, exec: E) -> Result<Vec<User>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;

let users = sqlx::query!(
"
SELECT u.id, u.github_id, u.name, u.email,
u.avatar_url, u.username, u.bio,
u.created, u.role FROM users u
WHERE u.id IN (SELECT * FROM UNNEST($1::bigint[]))
",
&user_ids
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|u| User {
id: UserId(u.id),
github_id: u.github_id,
name: u.name,
email: u.email,
avatar_url: u.avatar_url,
username: u.username,
bio: u.bio,
created: u.created,
role: u.role,
}))
})
.try_collect::<Vec<User>>()
.await?;

Ok(users)
}

pub async fn get_mods<'a, E>(user_id: UserId, exec: E) -> Result<Vec<ModId>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;

let mods = sqlx::query!(
"
SELECT m.id FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id
WHERE tm.user_id = $1
",
user_id as UserId,
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|m| ModId(m.id))) })
.try_collect::<Vec<ModId>>()
.await?;

Ok(mods)
}
}
39 changes: 39 additions & 0 deletions src/database/models/version_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,45 @@ impl Version {
}
}

pub async fn get_many<'a, E>(
version_ids: Vec<i64>,
exec: E,
) -> Result<Vec<Version>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;

let versions = sqlx::query!(
"
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
v.changelog_url, v.date_published, v.downloads,
v.release_channel
FROM versions v
WHERE v.id IN (SELECT * FROM UNNEST($1::bigint[]))
",
&version_ids
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|v| Version {
id: VersionId(v.id),
mod_id: ModId(v.mod_id),
author_id: UserId(v.author_id),
name: v.name,
version_number: v.version_number,
changelog_url: v.changelog_url,
date_published: v.date_published,
downloads: v.downloads,
release_channel: ChannelId(v.release_channel),
}))
})
.try_collect::<Vec<Version>>()
.await?;

Ok(versions)
}

pub async fn get_full<'a, 'b, E>(
id: VersionId,
executor: E,
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ async fn main() -> std::io::Result<()> {
.configure(routes::auth_config)
.configure(routes::tags_config)
.configure(routes::mods_config)
.configure(routes::versions_config)
.configure(routes::users_config),
)
.default_service(web::get().to(routes::not_found))
Expand Down
1 change: 1 addition & 0 deletions src/models/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub use super::users::UserId;
///
/// This method panics if `n` is 0 or greater than 11, since a `u64`
/// can only represent up to 11 character base62 strings
#[allow(dead_code)]
pub fn random_base62(n: usize) -> u64 {
use rand::Rng;
assert!(n > 0 && n <= 11);
Expand Down
25 changes: 16 additions & 9 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,36 @@ pub use self::not_found::not_found;

pub fn mods_config(cfg: &mut web::ServiceConfig) {
cfg.service(mods::mod_search);
cfg.service(mods::mods_get);
cfg.service(mod_creation::mod_create);

cfg.service(
web::scope("mod")
.service(mods::mod_get)
.service(mods::mod_delete)
.service(web::scope("{mod_id}").configure(versions_config)),
.service(web::scope("{mod_id}").service(versions::version_list)),
);
}

pub fn versions_config(cfg: &mut web::ServiceConfig) {
cfg.service(versions::version_list)
.service(version_creation::version_create)
.service(
web::scope("version")
.service(versions::version_get)
.service(versions::version_delete)
.service(version_creation::upload_file_to_version),
);
cfg.service(versions::versions_get);
cfg.service(
web::scope("version")
.service(versions::version_get)
.service(version_creation::version_create)
.service(versions::version_delete)
.service(version_creation::upload_file_to_version),
);
}

pub fn users_config(cfg: &mut web::ServiceConfig) {
cfg.service(users::user_auth_get);

cfg.service(users::users_get);
cfg.service(
web::scope("user")
.service(users::user_get)
.service(users::mods_list)
.service(users::user_delete),
);
}
Expand All @@ -53,6 +56,8 @@ pub fn users_config(cfg: &mut web::ServiceConfig) {
pub enum ApiError {
#[error("Internal server error")]
DatabaseError(#[from] crate::database::models::DatabaseError),
#[error("Deserialization error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Authentication Error")]
AuthenticationError,
}
Expand All @@ -62,6 +67,7 @@ impl actix_web::ResponseError for ApiError {
match self {
ApiError::DatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::AuthenticationError => actix_web::http::StatusCode::UNAUTHORIZED,
ApiError::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST,
}
}

Expand All @@ -71,6 +77,7 @@ impl actix_web::ResponseError for ApiError {
error: match self {
ApiError::DatabaseError(..) => "database_error",
ApiError::AuthenticationError => "unauthorized",
ApiError::JsonError(..) => "json_error",
},
description: &self.to_string(),
},
Expand Down
41 changes: 40 additions & 1 deletion src/routes/mods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::models;
use crate::models::mods::SearchRequest;
use crate::search::{search_for_mod, SearchError};
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;

#[get("mod")]
Expand All @@ -15,6 +16,45 @@ pub async fn mod_search(
Ok(HttpResponse::Ok().json(results))
}

#[derive(Serialize, Deserialize)]
pub struct ModIds {
pub ids: String,
}

// TODO: Make this return the full mod struct
Geometrically marked this conversation as resolved.
Show resolved Hide resolved
#[get("mods")]
pub async fn mods_get(
web::Query(ids): web::Query<ModIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let mods_data =
database::models::Mod::get_many(serde_json::from_str::<Vec<i64>>(&*ids.ids)?, &**pool)
Geometrically marked this conversation as resolved.
Show resolved Hide resolved
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?;

let mods: Vec<models::mods::Mod> = mods_data
.into_iter()
.map(|m| models::mods::Mod {
id: m.id.into(),
team: m.team_id.into(),
title: m.title,
description: m.description,
body_url: m.body_url,
published: m.published,

downloads: m.downloads as u32,
categories: vec![],
versions: vec![],
icon_url: m.icon_url,
issues_url: m.issues_url,
source_url: m.source_url,
wiki_url: m.wiki_url,
})
.collect();

Ok(HttpResponse::Ok().json(mods))
}

#[get("{id}")]
pub async fn mod_get(
info: web::Path<(models::ids::ModId,)>,
Expand Down Expand Up @@ -48,7 +88,6 @@ pub async fn mod_get(
Ok(HttpResponse::NotFound().body(""))
}
}

// TODO: The mod remains in meilisearch's index until the index is deleted
#[delete("{id}")]
pub async fn mod_delete(
Expand Down
Loading