Show orgs in project card when a project is owned by an org (#5892)
* fix: link to user using id instead of username * feat: show org in project card * fix: account for outdated documents * refactor: use struct to store owner information * fix: default new fields * fix lint
This commit is contained in:
34
apps/labrinth/.sqlx/query-654ca85d28573de9b532d0768e5250baa3a5f200fcf9ae990575073d0a1043e0.json
generated
Normal file
34
apps/labrinth/.sqlx/query-654ca85d28573de9b532d0768e5250baa3a5f200fcf9ae990575073d0a1043e0.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT m.id mod_id, u.username, u.id uid\n FROM mods m\n INNER JOIN team_members tm ON tm.is_owner = TRUE and tm.team_id = m.team_id\n INNER JOIN users u ON u.id = tm.user_id\n WHERE m.id = ANY($1)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "mod_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "uid",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "654ca85d28573de9b532d0768e5250baa3a5f200fcf9ae990575073d0a1043e0"
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT m.id mod_id, u.username\n FROM mods m\n INNER JOIN team_members tm ON tm.is_owner = TRUE and tm.team_id = m.team_id\n INNER JOIN users u ON u.id = tm.user_id\n WHERE m.id = ANY($1)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "mod_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "80734c33c16aeacca980cf40070bac035931a0bab8c0d0cf63888c8e5616f847"
|
||||
}
|
||||
46
apps/labrinth/.sqlx/query-81b65298ddf4c58b884b15e64f1f4f5aa006944c05980a5b5da366135d17c912.json
generated
Normal file
46
apps/labrinth/.sqlx/query-81b65298ddf4c58b884b15e64f1f4f5aa006944c05980a5b5da366135d17c912.json
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT m.id mod_id, u.username, u.id uid, o.name orgname, o.id oid\n FROM mods m\n INNER JOIN organizations o ON o.id = m.organization_id\n INNER JOIN team_members tm ON tm.is_owner = TRUE and tm.team_id = o.team_id\n INNER JOIN users u ON u.id = tm.user_id\n WHERE m.id = ANY($1)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "mod_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "uid",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "orgname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "oid",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "81b65298ddf4c58b884b15e64f1f4f5aa006944c05980a5b5da366135d17c912"
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT m.id mod_id, u.username\n FROM mods m\n INNER JOIN organizations o ON o.id = m.organization_id\n INNER JOIN team_members tm ON tm.is_owner = TRUE and tm.team_id = o.team_id\n INNER JOIN users u ON u.id = tm.user_id\n WHERE m.id = ANY($1)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "mod_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e50e308826d1e7fa54cade7daf8120b4ae4068bd086dc08f572b33cfc2476354"
|
||||
}
|
||||
@@ -17,6 +17,12 @@ pub struct LegacyResultSearchProject {
|
||||
pub project_type: String,
|
||||
pub slug: Option<String>,
|
||||
pub author: String,
|
||||
#[serde(default)]
|
||||
pub author_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub organization: Option<String>,
|
||||
#[serde(default)]
|
||||
pub organization_id: Option<String>,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub categories: Vec<String>,
|
||||
@@ -139,6 +145,9 @@ impl LegacyResultSearchProject {
|
||||
project_id: result_search_project.project_id,
|
||||
slug: result_search_project.slug,
|
||||
author: result_search_project.author,
|
||||
author_id: result_search_project.author_id,
|
||||
organization: result_search_project.organization,
|
||||
organization_id: result_search_project.organization_id,
|
||||
title: result_search_project.name,
|
||||
description: result_search_project.summary,
|
||||
display_categories,
|
||||
|
||||
@@ -15,8 +15,8 @@ use crate::database::models::loader_fields::{
|
||||
VersionField,
|
||||
};
|
||||
use crate::database::models::{
|
||||
DBProjectId, DBVersionId, LoaderFieldEnumId, LoaderFieldEnumValueId,
|
||||
LoaderFieldId,
|
||||
DBOrganizationId, DBProjectId, DBUserId, DBVersionId, LoaderFieldEnumId,
|
||||
LoaderFieldEnumValueId, LoaderFieldId,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::exp;
|
||||
@@ -34,6 +34,13 @@ fn normalize_for_search(s: &str) -> String {
|
||||
SPECIAL_CHARS_RE.replace_all(s, "").to_kebab_case()
|
||||
}
|
||||
|
||||
struct ProjectOwner {
|
||||
username: String,
|
||||
user_id: DBUserId,
|
||||
org_name: Option<String>,
|
||||
org_id: Option<DBOrganizationId>,
|
||||
}
|
||||
|
||||
pub async fn index_local(
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
@@ -171,9 +178,9 @@ pub async fn index_local(
|
||||
|
||||
info!("Indexing local org owners!");
|
||||
|
||||
let mods_org_owners: DashMap<DBProjectId, String> = sqlx::query!(
|
||||
let mods_org_owners: DashMap<DBProjectId, ProjectOwner> = sqlx::query!(
|
||||
"
|
||||
SELECT m.id mod_id, u.username
|
||||
SELECT m.id mod_id, u.username, u.id uid, o.name orgname, o.id oid
|
||||
FROM mods m
|
||||
INNER JOIN organizations o ON o.id = m.organization_id
|
||||
INNER JOIN team_members tm ON tm.is_owner = TRUE and tm.team_id = o.team_id
|
||||
@@ -183,17 +190,22 @@ pub async fn index_local(
|
||||
&*project_ids,
|
||||
)
|
||||
.fetch(pool)
|
||||
.try_fold(DashMap::new(), |acc: DashMap<DBProjectId, String>, m| {
|
||||
acc.insert(DBProjectId(m.mod_id), m.username);
|
||||
.try_fold(DashMap::new(), |acc: DashMap<DBProjectId, ProjectOwner>, m| {
|
||||
acc.insert(DBProjectId(m.mod_id), ProjectOwner {
|
||||
username: m.username,
|
||||
user_id: DBUserId(m.uid),
|
||||
org_name: Some(m.orgname),
|
||||
org_id: Some(DBOrganizationId(m.oid)),
|
||||
});
|
||||
async move { Ok(acc) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
info!("Indexing local team owners!");
|
||||
|
||||
let mods_team_owners: DashMap<DBProjectId, String> = sqlx::query!(
|
||||
let mods_team_owners: DashMap<DBProjectId, ProjectOwner> = sqlx::query!(
|
||||
"
|
||||
SELECT m.id mod_id, u.username
|
||||
SELECT m.id mod_id, u.username, u.id uid
|
||||
FROM mods m
|
||||
INNER JOIN team_members tm ON tm.is_owner = TRUE and tm.team_id = m.team_id
|
||||
INNER JOIN users u ON u.id = tm.user_id
|
||||
@@ -202,8 +214,13 @@ pub async fn index_local(
|
||||
&project_ids,
|
||||
)
|
||||
.fetch(pool)
|
||||
.try_fold(DashMap::new(), |acc: DashMap<DBProjectId, String>, m| {
|
||||
acc.insert(DBProjectId(m.mod_id), m.username);
|
||||
.try_fold(DashMap::new(), |acc: DashMap<DBProjectId, ProjectOwner>, m| {
|
||||
acc.insert(DBProjectId(m.mod_id), ProjectOwner {
|
||||
username: m.username,
|
||||
user_id: DBUserId(m.uid),
|
||||
org_name: None,
|
||||
org_id: None,
|
||||
});
|
||||
async move { Ok(acc) }
|
||||
})
|
||||
.await?;
|
||||
@@ -262,21 +279,24 @@ pub async fn index_local(
|
||||
if count % 1000 == 0 {
|
||||
info!("projects index prog: {count}/{total_len}");
|
||||
}
|
||||
|
||||
let owner =
|
||||
if let Some((_, org_owner)) = mods_org_owners.remove(&project.id) {
|
||||
org_owner
|
||||
} else if let Some((_, team_owner)) =
|
||||
mods_team_owners.remove(&project.id)
|
||||
{
|
||||
team_owner
|
||||
} else {
|
||||
warn!(
|
||||
"org owner not found for project {} id: {}!",
|
||||
project.name, project.id.0
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some((
|
||||
_,
|
||||
ProjectOwner {
|
||||
username,
|
||||
user_id,
|
||||
org_name,
|
||||
org_id,
|
||||
},
|
||||
)) = mods_org_owners
|
||||
.remove(&project.id)
|
||||
.or_else(|| mods_team_owners.remove(&project.id))
|
||||
else {
|
||||
warn!(
|
||||
"org owner not found for project {} id: {}!",
|
||||
project.name, project.id.0
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let license = match project.license.split(' ').next() {
|
||||
Some(license) => license.to_string(),
|
||||
@@ -444,8 +464,13 @@ pub async fn index_local(
|
||||
downloads: project.downloads,
|
||||
log_downloads: (project.downloads.max(1) as f64).ln(),
|
||||
icon_url: project.icon_url.clone(),
|
||||
author: owner.clone(),
|
||||
indexed_author: normalize_for_search(&owner),
|
||||
author: username.clone(),
|
||||
author_id: ariadne::ids::UserId::from(user_id).to_string(),
|
||||
organization: org_name.clone(),
|
||||
organization_id: org_id.map(|e| {
|
||||
crate::models::ids::OrganizationId::from(e).to_string()
|
||||
}),
|
||||
indexed_author: normalize_for_search(&username),
|
||||
date_created: project.approved,
|
||||
created_timestamp: project.approved.timestamp(),
|
||||
date_modified: project.updated,
|
||||
|
||||
@@ -228,6 +228,9 @@ pub struct UploadSearchProject {
|
||||
pub project_types: Vec<String>,
|
||||
pub slug: Option<String>,
|
||||
pub author: String,
|
||||
pub author_id: String,
|
||||
pub organization: Option<String>,
|
||||
pub organization_id: Option<String>,
|
||||
pub indexed_author: String,
|
||||
pub name: String,
|
||||
pub indexed_name: String,
|
||||
@@ -279,6 +282,12 @@ pub struct ResultSearchProject {
|
||||
pub project_types: Vec<String>,
|
||||
pub slug: Option<String>,
|
||||
pub author: String,
|
||||
#[serde(default)]
|
||||
pub author_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub organization: Option<String>,
|
||||
#[serde(default)]
|
||||
pub organization_id: Option<String>,
|
||||
pub name: String,
|
||||
pub summary: String,
|
||||
pub categories: Vec<String>,
|
||||
@@ -315,6 +324,9 @@ impl From<UploadSearchProject> for ResultSearchProject {
|
||||
project_types: source.project_types,
|
||||
slug: source.slug,
|
||||
author: source.author,
|
||||
author_id: Some(source.author_id),
|
||||
organization: source.organization,
|
||||
organization_id: source.organization_id,
|
||||
name: source.name,
|
||||
summary: source.summary,
|
||||
categories: source.categories,
|
||||
|
||||
@@ -1133,6 +1133,9 @@ export namespace Labrinth {
|
||||
project_type: string
|
||||
slug: string | null
|
||||
author: string
|
||||
author_id: string | null
|
||||
organization: string | null
|
||||
organization_id: string | null
|
||||
title: string
|
||||
description: string
|
||||
categories: string[]
|
||||
@@ -1167,6 +1170,9 @@ export namespace Labrinth {
|
||||
project_types: string[]
|
||||
slug: string | null
|
||||
author: string
|
||||
author_id: string | null
|
||||
organization: string | null
|
||||
organization_id: string | null
|
||||
name: string
|
||||
summary: string
|
||||
categories: string[]
|
||||
|
||||
@@ -190,11 +190,15 @@ const maxResultsOptions = computed<ComboboxOption<number>[]>(() =>
|
||||
:title="result.title"
|
||||
:icon-url="result.icon_url"
|
||||
:author="{
|
||||
name: result.author,
|
||||
name: result.organization == null ? result.author : result.organization,
|
||||
link:
|
||||
ctx.variant === 'web'
|
||||
? `/user/${result.author}`
|
||||
: `https://modrinth.com/user/${result.author}`,
|
||||
result.organization_id == null
|
||||
? ctx.variant === 'web'
|
||||
? `/user/${result.author_id ?? result.author}`
|
||||
: `https://modrinth.com/user/${result.author_id ?? result.author}`
|
||||
: ctx.variant === 'web'
|
||||
? `/organization/${result.organization_id}`
|
||||
: `https://modrinth.com/organization/${result.organization_id}`,
|
||||
}"
|
||||
:date-updated="result.date_modified"
|
||||
:date-published="result.date_created"
|
||||
|
||||
Reference in New Issue
Block a user