Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE TABLE minecraft_server_projects (
id bigint PRIMARY KEY NOT NULL
REFERENCES mods(id)
ON DELETE CASCADE,
max_players int NOT NULL
);

CREATE TABLE minecraft_java_server_projects (
id bigint PRIMARY KEY NOT NULL
REFERENCES mods(id)
ON DELETE CASCADE,
address varchar(255) NOT NULL
);

CREATE TABLE minecraft_bedrock_server_projects (
id bigint PRIMARY KEY NOT NULL
REFERENCES mods(id)
ON DELETE CASCADE,
address varchar(255) NOT NULL
);
41 changes: 37 additions & 4 deletions apps/labrinth/src/database/models/project_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::{DBUser, ids::*};
use crate::database::models;
use crate::database::models::DatabaseError;
use crate::database::redis::RedisPool;
use crate::models::exp;
use crate::models::projects::{
MonetizationStatus, ProjectStatus, SideTypesMigrationReviewStatus,
};
Expand Down Expand Up @@ -767,7 +768,7 @@ impl DBProject {
.await?;

let projects = sqlx::query!(
"
r#"
SELECT m.id id, m.name name, m.summary summary, m.downloads downloads, m.follows follows,
m.icon_url icon_url, m.raw_icon_url raw_icon_url, m.description description, m.published published,
m.approved approved, m.queued, m.status status, m.requested_status requested_status,
Expand All @@ -777,14 +778,28 @@ impl DBProject {
t.id thread_id, m.monetization_status monetization_status,
m.side_types_migration_review_status side_types_migration_review_status,
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,
-- components
COUNT(c1.id) > 0 AS minecraft_server_exists,
MAX(c1.max_players) AS minecraft_server_max_players,
COUNT(c2.id) > 0 AS minecraft_java_server_exists,
MAX(c2.address) AS minecraft_java_server_address,
COUNT(c3.id) > 0 AS minecraft_bedrock_server_exists,
MAX(c3.address) AS minecraft_bedrock_server_address

FROM mods m
INNER JOIN threads t ON t.mod_id = m.id
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
LEFT JOIN categories c ON mc.joining_category_id = c.id

-- components
LEFT JOIN minecraft_server_projects c1 ON c1.id = m.id
LEFT JOIN minecraft_java_server_projects c2 ON c2.id = m.id
LEFT JOIN minecraft_bedrock_server_projects c3 ON c3.id = m.id

WHERE m.id = ANY($1) OR m.slug = ANY($2)
GROUP BY t.id, m.id;
",
GROUP BY t.id, m.id
"#,
&project_ids_parsed,
&slugs,
)
Expand Down Expand Up @@ -858,6 +873,21 @@ impl DBProject {
urls,
aggregate_version_fields: VersionField::from_query_json(version_fields, &loader_fields, &loader_field_enum_values, true),
thread_id: DBThreadId(m.thread_id),
minecraft_server: if m.minecraft_server_exists.unwrap_or(false) {
Some(exp::minecraft::Server {
max_players: m.minecraft_server_max_players.unwrap().cast_unsigned(),
})
} else { None },
minecraft_java_server: if m.minecraft_java_server_exists.unwrap_or(false) {
Some(exp::minecraft::JavaServer {
address: m.minecraft_java_server_address.unwrap(),
})
} else { None },
minecraft_bedrock_server: if m.minecraft_bedrock_server_exists.unwrap_or(false) {
Some(exp::minecraft::BedrockServer {
address: m.minecraft_bedrock_server_address.unwrap(),
})
} else { None },
};

acc.insert(m.id, (m.slug, project));
Expand Down Expand Up @@ -983,4 +1013,7 @@ pub struct ProjectQueryResult {
pub gallery_items: Vec<DBGalleryItem>,
pub thread_id: DBThreadId,
pub aggregate_version_fields: Vec<VersionField>,
pub minecraft_server: Option<exp::minecraft::Server>,
pub minecraft_java_server: Option<exp::minecraft::JavaServer>,
pub minecraft_bedrock_server: Option<exp::minecraft::BedrockServer>,
}
26 changes: 26 additions & 0 deletions apps/labrinth/src/models/exp/base.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
use validator::Validate;

define! {
#[derive(Debug, Clone, Serialize, Deserialize, Validate, utoipa::ToSchema)]
pub struct Project {
/// Human-readable friendly name of the project.
#[validate(
length(min = 3, max = 64),
custom(function = "crate::util::validate::validate_name")
)]
pub name: String,
/// Slug of the project, used in vanity URLs.
#[validate(
length(min = 3, max = 64),
regex(path = *crate::util::validate::RE_URL_SAFE)
)]
pub slug: String,
/// Short description of the project.
#[validate(length(min = 3, max = 255))]
pub summary: String,
/// A long description of the project, in markdown.
#[validate(length(max = 65536))]
pub description: String,
}
}
210 changes: 210 additions & 0 deletions apps/labrinth/src/models/exp/minecraft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use std::sync::LazyLock;

use serde::{Deserialize, Serialize};
use sqlx::{PgTransaction, postgres::PgQueryResult};
use validator::Validate;

use crate::{
database::models::DBProjectId,
models::exp::{
ComponentKindArrayExt, ComponentKindExt, ComponentRelation,
ProjectComponent, ProjectComponentEdit, ProjectComponentKind,
},
};

pub(super) static RELATIONS: LazyLock<Vec<ComponentRelation>> =
LazyLock::new(|| {
use ProjectComponentKind as C;

vec![
[C::MinecraftMod].only(),
[
C::MinecraftServer,
C::MinecraftJavaServer,
C::MinecraftBedrockServer,
]
.only(),
C::MinecraftJavaServer.requires(C::MinecraftServer),
C::MinecraftBedrockServer.requires(C::MinecraftServer),
]
});

define! {
#[derive(Debug, Clone, Serialize, Deserialize, Validate, utoipa::ToSchema)]
pub struct Mod {}

#[derive(Debug, Clone, Serialize, Deserialize, Validate, utoipa::ToSchema)]
pub struct Server {
pub max_players: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize, Validate, utoipa::ToSchema)]
pub struct JavaServer {
#[validate(length(max = 255))]
pub address: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Validate, utoipa::ToSchema)]
pub struct BedrockServer {
#[validate(length(max = 255))]
pub address: String,
}
}

// impl

impl ProjectComponent for Mod {
fn kind() -> ProjectComponentKind {
ProjectComponentKind::MinecraftMod
}

async fn insert(
&self,
_txn: &mut PgTransaction<'_>,
_project_id: DBProjectId,
) -> Result<(), sqlx::Error> {
unimplemented!();
}
}

impl ProjectComponentEdit for ModEdit {
async fn update(
&self,
_txn: &mut PgTransaction<'_>,
_project_id: DBProjectId,
) -> Result<PgQueryResult, sqlx::Error> {
unimplemented!();
}
}

impl ProjectComponent for Server {
fn kind() -> ProjectComponentKind {
ProjectComponentKind::MinecraftServer
}

async fn insert(
&self,
txn: &mut PgTransaction<'_>,
project_id: DBProjectId,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
INSERT INTO minecraft_server_projects (id, max_players)
VALUES ($1, $2)
",
project_id as _,
self.max_players.cast_signed(),
)
.execute(&mut **txn)
.await?;
Ok(())
}
}

impl ProjectComponentEdit for ServerEdit {
async fn update(
&self,
txn: &mut PgTransaction<'_>,
project_id: DBProjectId,
) -> Result<PgQueryResult, sqlx::Error> {
sqlx::query!(
"
UPDATE minecraft_server_projects
SET max_players = COALESCE($2, max_players)
WHERE id = $1
",
project_id as _,
self.max_players.map(|n| n.cast_signed()),
)
.execute(&mut **txn)
.await
}
}

impl ProjectComponent for JavaServer {
fn kind() -> ProjectComponentKind {
ProjectComponentKind::MinecraftJavaServer
}

async fn insert(
&self,
txn: &mut PgTransaction<'_>,
project_id: DBProjectId,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
INSERT INTO minecraft_java_server_projects (id, address)
VALUES ($1, $2)
",
project_id as _,
self.address,
)
.execute(&mut **txn)
.await?;
Ok(())
}
}

impl ProjectComponentEdit for JavaServerEdit {
async fn update(
&self,
txn: &mut PgTransaction<'_>,
project_id: DBProjectId,
) -> Result<PgQueryResult, sqlx::Error> {
sqlx::query!(
"
UPDATE minecraft_java_server_projects
SET address = COALESCE($2, address)
WHERE id = $1
",
project_id as _,
self.address,
)
.execute(&mut **txn)
.await
}
}

impl ProjectComponent for BedrockServer {
fn kind() -> ProjectComponentKind {
ProjectComponentKind::MinecraftBedrockServer
}

async fn insert(
&self,
txn: &mut PgTransaction<'_>,
project_id: DBProjectId,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
INSERT INTO minecraft_bedrock_server_projects (id, address)
VALUES ($1, $2)
",
project_id as _,
self.address,
)
.execute(&mut **txn)
.await?;
Ok(())
}
}

impl ProjectComponentEdit for BedrockServerEdit {
async fn update(
&self,
txn: &mut PgTransaction<'_>,
project_id: DBProjectId,
) -> Result<PgQueryResult, sqlx::Error> {
sqlx::query!(
"
UPDATE minecraft_bedrock_server_projects
SET address = COALESCE($2, address)
WHERE id = $1
",
project_id as _,
self.address,
)
.execute(&mut **txn)
.await
}
}
Loading
Loading