forked from didirus/AstralRinth
Fetch more data for moderation endpoints (#4727)
* Moderation endpoints fetch ownership data * fix up endpoint configs * add some docs
This commit is contained in:
@@ -350,7 +350,8 @@ pub fn utoipa_app_config(
|
|||||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||||
_labrinth_config: LabrinthConfig,
|
_labrinth_config: LabrinthConfig,
|
||||||
) {
|
) {
|
||||||
cfg.configure(routes::v3::utoipa_config);
|
cfg.configure(routes::v3::utoipa_config)
|
||||||
|
.configure(routes::internal::utoipa_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is so that env vars not used immediately don't panic at runtime
|
// This is so that env vars not used immediately don't panic at runtime
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// A project returned from the API
|
/// A project returned from the API
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
/// The ID of the project, encoded as a base62 string.
|
/// The ID of the project, encoded as a base62 string.
|
||||||
pub id: ProjectId,
|
pub id: ProjectId,
|
||||||
@@ -370,7 +370,7 @@ impl Project {
|
|||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)]
|
||||||
pub struct GalleryItem {
|
pub struct GalleryItem {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub raw_url: String,
|
pub raw_url: String,
|
||||||
@@ -381,7 +381,7 @@ pub struct GalleryItem {
|
|||||||
pub ordering: i64,
|
pub ordering: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)]
|
||||||
pub struct ModeratorMessage {
|
pub struct ModeratorMessage {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
@@ -389,14 +389,23 @@ pub struct ModeratorMessage {
|
|||||||
|
|
||||||
pub const DEFAULT_LICENSE_ID: &str = "LicenseRef-All-Rights-Reserved";
|
pub const DEFAULT_LICENSE_ID: &str = "LicenseRef-All-Rights-Reserved";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct License {
|
pub struct License {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate, Clone, Eq, PartialEq)]
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Validate,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
utoipa::ToSchema,
|
||||||
|
)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
pub platform: String,
|
pub platform: String,
|
||||||
pub donation: bool,
|
pub donation: bool,
|
||||||
@@ -425,7 +434,9 @@ impl From<LinkUrl> for Link {
|
|||||||
/// Processing - Project is not displayed on search, and not accessible by URL (Temporary state, project under review)
|
/// Processing - Project is not displayed on search, and not accessible by URL (Temporary state, project under review)
|
||||||
/// Scheduled - Project is scheduled to be released in the future
|
/// Scheduled - Project is scheduled to be released in the future
|
||||||
/// Private - Project is approved, but is not viewable to the public
|
/// Private - Project is approved, but is not viewable to the public
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(
|
||||||
|
Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ProjectStatus {
|
pub enum ProjectStatus {
|
||||||
Approved,
|
Approved,
|
||||||
@@ -564,7 +575,9 @@ impl ProjectStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(
|
||||||
|
Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum MonetizationStatus {
|
pub enum MonetizationStatus {
|
||||||
ForceDemonetized,
|
ForceDemonetized,
|
||||||
@@ -599,7 +612,9 @@ impl MonetizationStatus {
|
|||||||
|
|
||||||
/// Represents the status of the manual review of the migration of side types of this
|
/// Represents the status of the manual review of the migration of side types of this
|
||||||
/// project to the new environment field.
|
/// project to the new environment field.
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(
|
||||||
|
Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum SideTypesMigrationReviewStatus {
|
pub enum SideTypesMigrationReviewStatus {
|
||||||
/// The project has been reviewed to use the new environment side types appropriately.
|
/// The project has been reviewed to use the new environment side types appropriately.
|
||||||
|
|||||||
@@ -773,20 +773,20 @@ impl AutomatedModerationQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct MissingMetadata {
|
pub struct MissingMetadata {
|
||||||
pub identified: HashMap<String, IdentifiedFile>,
|
pub identified: HashMap<String, IdentifiedFile>,
|
||||||
pub flame_files: HashMap<String, MissingMetadataFlame>,
|
pub flame_files: HashMap<String, MissingMetadataFlame>,
|
||||||
pub unknown_files: HashMap<String, String>,
|
pub unknown_files: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct IdentifiedFile {
|
pub struct IdentifiedFile {
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub status: ApprovalType,
|
pub status: ApprovalType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct MissingMetadataFlame {
|
pub struct MissingMetadataFlame {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
@@ -794,7 +794,9 @@ pub struct MissingMetadataFlame {
|
|||||||
pub id: u32,
|
pub id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(
|
||||||
|
Deserialize, Serialize, Copy, Clone, PartialEq, Eq, Debug, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum ApprovalType {
|
pub enum ApprovalType {
|
||||||
Yes,
|
Yes,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
|||||||
.configure(session::config)
|
.configure(session::config)
|
||||||
.configure(flows::config)
|
.configure(flows::config)
|
||||||
.configure(pats::config)
|
.configure(pats::config)
|
||||||
.configure(moderation::config)
|
|
||||||
.configure(billing::config)
|
.configure(billing::config)
|
||||||
.configure(gdpr::config)
|
.configure(gdpr::config)
|
||||||
.configure(gotenberg::config)
|
.configure(gotenberg::config)
|
||||||
@@ -36,3 +35,13 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
|||||||
.configure(mural::config),
|
.configure(mural::config),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn utoipa_config(
|
||||||
|
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||||
|
) {
|
||||||
|
cfg.service(
|
||||||
|
utoipa_actix_web::scope("/_internal/moderation")
|
||||||
|
.wrap(default_cors())
|
||||||
|
.configure(moderation::config),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
|
use crate::database::models::{DBOrganization, DBTeamId, DBTeamMember, DBUser};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::projects::ProjectStatus;
|
use crate::models::ids::{OrganizationId, TeamId};
|
||||||
|
use crate::models::projects::{Project, ProjectStatus};
|
||||||
use crate::queue::moderation::{ApprovalType, IdentifiedFile, MissingMetadata};
|
use crate::queue::moderation::{ApprovalType, IdentifiedFile, MissingMetadata};
|
||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
|
use crate::util::error::Context;
|
||||||
use crate::{auth::check_is_moderator_from_headers, models::pats::Scopes};
|
use crate::{auth::check_is_moderator_from_headers, models::pats::Scopes};
|
||||||
use actix_web::{HttpRequest, HttpResponse, web};
|
use actix_web::{HttpRequest, get, post, web};
|
||||||
use ariadne::ids::random_base62;
|
use ariadne::ids::{UserId, random_base62};
|
||||||
use serde::Deserialize;
|
use eyre::eyre;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||||
cfg.route("moderation/projects", web::get().to(get_projects));
|
cfg.service(get_projects)
|
||||||
cfg.route("moderation/project/{id}", web::get().to(get_project_meta));
|
.service(get_project_meta)
|
||||||
cfg.route("moderation/project", web::post().to(set_project_meta));
|
.service(set_project_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ProjectsRequestOptions {
|
pub struct ProjectsRequestOptions {
|
||||||
|
/// How many projects to fetch.
|
||||||
#[serde(default = "default_count")]
|
#[serde(default = "default_count")]
|
||||||
pub count: u16,
|
pub count: u16,
|
||||||
|
/// How many projects to skip.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub offset: u32,
|
pub offset: u32,
|
||||||
}
|
}
|
||||||
@@ -29,13 +35,63 @@ fn default_count() -> u16 {
|
|||||||
100
|
100
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_projects(
|
/// Project with extra information fetched from the database, to avoid having
|
||||||
|
/// clients make more round trips.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
|
pub struct FetchedProject {
|
||||||
|
/// Project info.
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub project: Project,
|
||||||
|
/// Who owns the project.
|
||||||
|
pub ownership: Ownership,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetched information on who owns a project.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum Ownership {
|
||||||
|
/// Project is owned by a team, and this is the team owner.
|
||||||
|
User {
|
||||||
|
/// ID of the team owner.
|
||||||
|
id: UserId,
|
||||||
|
/// Name of the team owner.
|
||||||
|
name: String,
|
||||||
|
/// URL of the team owner's icon.
|
||||||
|
icon_url: Option<String>,
|
||||||
|
},
|
||||||
|
/// Project is owned by an organization.
|
||||||
|
Organization {
|
||||||
|
/// ID of the organization.
|
||||||
|
id: OrganizationId,
|
||||||
|
/// Name of the organization.
|
||||||
|
name: String,
|
||||||
|
/// URL of the organization's icon.
|
||||||
|
icon_url: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch all projects which are in the moderation queue.
|
||||||
|
#[utoipa::path(
|
||||||
|
responses((status = OK, body = inline(Vec<FetchedProject>)))
|
||||||
|
)]
|
||||||
|
#[get("/projects")]
|
||||||
|
async fn get_projects(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
request_opts: web::Query<ProjectsRequestOptions>,
|
request_opts: web::Query<ProjectsRequestOptions>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<web::Json<Vec<FetchedProject>>, ApiError> {
|
||||||
|
get_projects_internal(req, pool, redis, request_opts, session_queue).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_projects_internal(
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
redis: web::Data<RedisPool>,
|
||||||
|
request_opts: web::Query<ProjectsRequestOptions>,
|
||||||
|
session_queue: web::Data<AuthQueue>,
|
||||||
|
) -> Result<web::Json<Vec<FetchedProject>>, ApiError> {
|
||||||
check_is_moderator_from_headers(
|
check_is_moderator_from_headers(
|
||||||
&req,
|
&req,
|
||||||
&**pool,
|
&**pool,
|
||||||
@@ -62,25 +118,100 @@ pub async fn get_projects(
|
|||||||
.fetch(&**pool)
|
.fetch(&**pool)
|
||||||
.map_ok(|m| database::models::DBProjectId(m.id))
|
.map_ok(|m| database::models::DBProjectId(m.id))
|
||||||
.try_collect::<Vec<database::models::DBProjectId>>()
|
.try_collect::<Vec<database::models::DBProjectId>>()
|
||||||
.await?;
|
.await
|
||||||
|
.wrap_internal_err("failed to fetch projects awaiting review")?;
|
||||||
|
|
||||||
let projects: Vec<_> =
|
let projects =
|
||||||
database::DBProject::get_many_ids(&project_ids, &**pool, &redis)
|
database::DBProject::get_many_ids(&project_ids, &**pool, &redis)
|
||||||
.await?
|
.await
|
||||||
|
.wrap_internal_err("failed to fetch projects")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(crate::models::projects::Project::from)
|
.map(crate::models::projects::Project::from)
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
let team_ids = projects
|
||||||
|
.iter()
|
||||||
|
.map(|project| project.team_id)
|
||||||
|
.map(DBTeamId::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let org_ids = projects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|project| project.organization)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let team_members =
|
||||||
|
DBTeamMember::get_from_team_full_many(&team_ids, &**pool, &redis)
|
||||||
|
.await
|
||||||
|
.wrap_internal_err("failed to fetch team members")?;
|
||||||
|
let users = DBUser::get_many_ids(
|
||||||
|
&team_members
|
||||||
|
.iter()
|
||||||
|
.map(|member| member.user_id)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.wrap_internal_err("failed to fetch user data of team members")?;
|
||||||
|
let orgs = DBOrganization::get_many(&org_ids, &**pool, &redis)
|
||||||
|
.await
|
||||||
|
.wrap_internal_err("failed to fetch organizations")?;
|
||||||
|
|
||||||
|
let map_project = |project: Project| -> Result<FetchedProject, ApiError> {
|
||||||
|
let project_id = project.id;
|
||||||
|
let ownership = if let Some(org_id) = project.organization {
|
||||||
|
let org = orgs
|
||||||
|
.iter()
|
||||||
|
.find(|org| OrganizationId::from(org.id) == org_id)
|
||||||
|
.wrap_internal_err_with(|| {
|
||||||
|
eyre!(
|
||||||
|
"project {project_id} is owned by an invalid organization {org_id}"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ownership::Organization {
|
||||||
|
id: OrganizationId::from(org.id),
|
||||||
|
name: org.name.clone(),
|
||||||
|
icon_url: org.icon_url.clone(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let team_id = project.team_id;
|
||||||
|
let team_owner = team_members.iter().find(|member| TeamId::from(member.team_id) == team_id && member.is_owner)
|
||||||
|
.wrap_internal_err_with(|| eyre!("project {project_id} is owned by a team {team_id} which has no valid owner"))?;
|
||||||
|
let team_owner_id = team_owner.user_id;
|
||||||
|
let user = users.iter().find(|user| user.id == team_owner_id)
|
||||||
|
.wrap_internal_err_with(|| eyre!("project {project_id} is owned by a team {team_id} which has owner {} which does not exist", UserId::from(team_owner_id)))?;
|
||||||
|
|
||||||
|
Ownership::User {
|
||||||
|
id: UserId::from(user.id),
|
||||||
|
name: user.username.clone(),
|
||||||
|
icon_url: user.avatar_url.clone(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(FetchedProject { ownership, project })
|
||||||
|
};
|
||||||
|
|
||||||
|
let projects = projects
|
||||||
|
.into_iter()
|
||||||
|
.map(map_project)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(web::Json(projects))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_project_meta(
|
/// Fetch moderation metadata for a specific project.
|
||||||
|
#[utoipa::path(
|
||||||
|
responses((status = OK, body = inline(Vec<Project>)))
|
||||||
|
)]
|
||||||
|
#[get("/project/{id}")]
|
||||||
|
async fn get_project_meta(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<web::Json<MissingMetadata>, ApiError> {
|
||||||
check_is_moderator_from_headers(
|
check_is_moderator_from_headers(
|
||||||
&req,
|
&req,
|
||||||
&**pool,
|
&**pool,
|
||||||
@@ -202,13 +333,13 @@ pub async fn get_project_meta(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(merged))
|
Ok(web::Json(merged))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum Judgement {
|
pub enum Judgement {
|
||||||
Flame {
|
Flame {
|
||||||
@@ -225,13 +356,16 @@ pub enum Judgement {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_project_meta(
|
/// Update moderation judgements for projects in the review queue.
|
||||||
|
#[utoipa::path]
|
||||||
|
#[post("/project")]
|
||||||
|
async fn set_project_meta(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
judgements: web::Json<HashMap<String, Judgement>>,
|
judgements: web::Json<HashMap<String, Judgement>>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
check_is_moderator_from_headers(
|
check_is_moderator_from_headers(
|
||||||
&req,
|
&req,
|
||||||
&**pool,
|
&**pool,
|
||||||
@@ -302,11 +436,11 @@ pub async fn set_project_meta(
|
|||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"
|
"
|
||||||
INSERT INTO moderation_external_files (sha1, external_license_id)
|
INSERT INTO moderation_external_files (sha1, external_license_id)
|
||||||
SELECT * FROM UNNEST ($1::bytea[], $2::bigint[])
|
SELECT * FROM UNNEST ($1::bytea[], $2::bigint[])
|
||||||
ON CONFLICT (sha1)
|
ON CONFLICT (sha1)
|
||||||
DO NOTHING
|
DO NOTHING
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(&file_hashes[..])
|
.bind(&file_hashes[..])
|
||||||
.bind(&ids[..])
|
.bind(&ids[..])
|
||||||
@@ -315,5 +449,5 @@ pub async fn set_project_meta(
|
|||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ pub async fn get_projects(
|
|||||||
count: web::Query<ResultCount>,
|
count: web::Query<ResultCount>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let response = internal::moderation::get_projects(
|
let response = internal::moderation::get_projects_internal(
|
||||||
req,
|
req,
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
redis.clone(),
|
redis.clone(),
|
||||||
@@ -41,6 +41,7 @@ pub async fn get_projects(
|
|||||||
session_queue,
|
session_queue,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(|resp| HttpResponse::Ok().json(resp))
|
||||||
.or_else(v2_reroute::flatten_404_error)?;
|
.or_else(v2_reroute::flatten_404_error)?;
|
||||||
|
|
||||||
// Convert to V2 projects
|
// Convert to V2 projects
|
||||||
|
|||||||
Reference in New Issue
Block a user