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:
Sychic
2026-04-23 13:32:19 -04:00
committed by GitHub
parent 16e1bf4611
commit 6862cf5ab2
9 changed files with 167 additions and 87 deletions

View 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"
}

View File

@@ -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"
}

View 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"
}

View File

@@ -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"
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,