mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
feat: more details linked account info for support/moderation (#6897)
* feat: show discord + steam ID + github profile in "Show details" user modal * pnpm intl:extract
This commit is contained in:
@@ -3374,6 +3374,15 @@
|
|||||||
"profile.collection.projects-count": {
|
"profile.collection.projects-count": {
|
||||||
"message": "{count, plural, one {# project} other {# projects}}"
|
"message": "{count, plural, one {# project} other {# projects}}"
|
||||||
},
|
},
|
||||||
|
"profile.details.error.github-popup-blocked": {
|
||||||
|
"message": "Allow pop-ups for Modrinth, then try again."
|
||||||
|
},
|
||||||
|
"profile.details.error.github-profile-message": {
|
||||||
|
"message": "The GitHub profile could not be retrieved. Please try again."
|
||||||
|
},
|
||||||
|
"profile.details.error.github-profile-title": {
|
||||||
|
"message": "Unable to open GitHub profile"
|
||||||
|
},
|
||||||
"profile.details.label.auth-providers": {
|
"profile.details.label.auth-providers": {
|
||||||
"message": "Auth providers"
|
"message": "Auth providers"
|
||||||
},
|
},
|
||||||
@@ -3389,9 +3398,15 @@
|
|||||||
"profile.details.label.has-totp": {
|
"profile.details.label.has-totp": {
|
||||||
"message": "Has TOTP"
|
"message": "Has TOTP"
|
||||||
},
|
},
|
||||||
|
"profile.details.label.loading-github-profile": {
|
||||||
|
"message": "Loading..."
|
||||||
|
},
|
||||||
"profile.details.label.payment-methods": {
|
"profile.details.label.payment-methods": {
|
||||||
"message": "Payment methods"
|
"message": "Payment methods"
|
||||||
},
|
},
|
||||||
|
"profile.details.label.view-github-profile": {
|
||||||
|
"message": "View profile"
|
||||||
|
},
|
||||||
"profile.details.tooltip.email-not-verified": {
|
"profile.details.tooltip.email-not-verified": {
|
||||||
"message": "Email not verified"
|
"message": "Email not verified"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,7 +71,37 @@
|
|||||||
<span class="text-lg font-bold text-primary">{{
|
<span class="text-lg font-bold text-primary">{{
|
||||||
formatMessage(messages.authProvidersLabel)
|
formatMessage(messages.authProvidersLabel)
|
||||||
}}</span>
|
}}</span>
|
||||||
<span>{{ user.auth_providers.join(', ') }}</span>
|
<div class="flex flex-col">
|
||||||
|
<span
|
||||||
|
v-for="provider in user.auth_providers ?? []"
|
||||||
|
:key="provider"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<span>{{ authProviderNames[provider] ?? provider }}</span>
|
||||||
|
<span v-if="provider === 'discord' && user.discord_id" class="ml-1">
|
||||||
|
({{ user.discord_id }})
|
||||||
|
</span>
|
||||||
|
<template v-else-if="provider === 'github' && user.github_id">
|
||||||
|
<span class="ml-1">(</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="m-0 appearance-none border-0 bg-transparent p-0 font-[inherit] text-link disabled:cursor-wait disabled:opacity-70"
|
||||||
|
:disabled="isLoadingGithubProfile"
|
||||||
|
@click="openGithubProfile"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
isLoadingGithubProfile
|
||||||
|
? formatMessage(messages.loadingGithubProfileLabel)
|
||||||
|
: formatMessage(messages.viewGithubProfileLabel)
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
<span>)</span>
|
||||||
|
</template>
|
||||||
|
<span v-else-if="provider === 'steam' && user.steam_id" class="ml-1">
|
||||||
|
({{ user.steam_id }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isAdmin(auth.user)" class="flex flex-col gap-1">
|
<div v-if="isAdmin(auth.user)" class="flex flex-col gap-1">
|
||||||
@@ -437,6 +467,26 @@ const messages = defineMessages({
|
|||||||
id: 'profile.details.label.auth-providers',
|
id: 'profile.details.label.auth-providers',
|
||||||
defaultMessage: 'Auth providers',
|
defaultMessage: 'Auth providers',
|
||||||
},
|
},
|
||||||
|
viewGithubProfileLabel: {
|
||||||
|
id: 'profile.details.label.view-github-profile',
|
||||||
|
defaultMessage: 'View profile',
|
||||||
|
},
|
||||||
|
loadingGithubProfileLabel: {
|
||||||
|
id: 'profile.details.label.loading-github-profile',
|
||||||
|
defaultMessage: 'Loading...',
|
||||||
|
},
|
||||||
|
githubProfileErrorTitle: {
|
||||||
|
id: 'profile.details.error.github-profile-title',
|
||||||
|
defaultMessage: 'Unable to open GitHub profile',
|
||||||
|
},
|
||||||
|
githubProfileErrorMessage: {
|
||||||
|
id: 'profile.details.error.github-profile-message',
|
||||||
|
defaultMessage: 'The GitHub profile could not be retrieved. Please try again.',
|
||||||
|
},
|
||||||
|
githubPopupBlockedMessage: {
|
||||||
|
id: 'profile.details.error.github-popup-blocked',
|
||||||
|
defaultMessage: 'Allow pop-ups for Modrinth, then try again.',
|
||||||
|
},
|
||||||
paymentMethodsLabel: {
|
paymentMethodsLabel: {
|
||||||
id: 'profile.details.label.payment-methods',
|
id: 'profile.details.label.payment-methods',
|
||||||
defaultMessage: 'Payment methods',
|
defaultMessage: 'Payment methods',
|
||||||
@@ -518,6 +568,16 @@ const messages = defineMessages({
|
|||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
|
|
||||||
const userId = useRouteId('user')
|
const userId = useRouteId('user')
|
||||||
|
const authProviderNames = {
|
||||||
|
github: 'GitHub',
|
||||||
|
discord: 'Discord',
|
||||||
|
microsoft: 'Microsoft',
|
||||||
|
gitlab: 'GitLab',
|
||||||
|
google: 'Google',
|
||||||
|
steam: 'Steam',
|
||||||
|
paypal: 'PayPal',
|
||||||
|
}
|
||||||
|
const isLoadingGithubProfile = ref(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: user,
|
data: user,
|
||||||
@@ -664,6 +724,50 @@ async function copyPermalink() {
|
|||||||
await navigator.clipboard.writeText(`${config.public.siteUrl}/user/${user.value.id}`)
|
await navigator.clipboard.writeText(`${config.public.siteUrl}/user/${user.value.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openGithubProfile() {
|
||||||
|
const githubId = user.value?.github_id
|
||||||
|
if (!githubId || isLoadingGithubProfile.value) return
|
||||||
|
|
||||||
|
const profileWindow = window.open('about:blank', '_blank')
|
||||||
|
if (!profileWindow) {
|
||||||
|
addNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: formatMessage(messages.githubProfileErrorTitle),
|
||||||
|
message: formatMessage(messages.githubPopupBlockedMessage),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profileWindow.opener = null
|
||||||
|
isLoadingGithubProfile.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const githubUser = await client.request(`/${githubId}`, {
|
||||||
|
api: 'https://api.github.com',
|
||||||
|
version: 'user',
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': '' },
|
||||||
|
skipAuth: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!githubUser?.login) {
|
||||||
|
throw new Error('GitHub user response did not include a login')
|
||||||
|
}
|
||||||
|
|
||||||
|
profileWindow.location.replace(`https://github.com/${encodeURIComponent(githubUser.login)}`)
|
||||||
|
} catch (error) {
|
||||||
|
profileWindow.close()
|
||||||
|
console.error('Failed to retrieve GitHub profile:', error)
|
||||||
|
addNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: formatMessage(messages.githubProfileErrorTitle),
|
||||||
|
message: formatMessage(messages.githubProfileErrorMessage),
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
isLoadingGithubProfile.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function reportProfileFromHeader() {
|
function reportProfileFromHeader() {
|
||||||
if (!user.value) return
|
if (!user.value) return
|
||||||
if (auth.value.user) {
|
if (auth.value.user) {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ pub struct LegacyUser {
|
|||||||
pub has_totp: Option<bool>,
|
pub has_totp: Option<bool>,
|
||||||
pub payout_data: Option<UserPayoutData>, // this was changed in v3, but not ones we want to keep out of v2
|
pub payout_data: Option<UserPayoutData>, // this was changed in v3, but not ones we want to keep out of v2
|
||||||
|
|
||||||
// DEPRECATED. Always returns None
|
|
||||||
pub github_id: Option<u64>,
|
pub github_id: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,11 @@ pub struct User {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub moderation_notes: Option<Option<ModerationNote>>,
|
pub moderation_notes: Option<Option<ModerationNote>>,
|
||||||
|
|
||||||
// DEPRECATED. Always returns None
|
|
||||||
pub github_id: Option<u64>,
|
pub github_id: Option<u64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub discord_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub steam_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
@@ -119,6 +122,8 @@ impl From<DBUser> for User {
|
|||||||
has_password: None,
|
has_password: None,
|
||||||
has_totp: None,
|
has_totp: None,
|
||||||
github_id: None,
|
github_id: None,
|
||||||
|
discord_id: None,
|
||||||
|
steam_id: None,
|
||||||
stripe_customer_id: None,
|
stripe_customer_id: None,
|
||||||
allow_friend_requests: None,
|
allow_friend_requests: None,
|
||||||
eligibility_verified_at: None,
|
eligibility_verified_at: None,
|
||||||
@@ -180,6 +185,8 @@ impl User {
|
|||||||
has_password: Some(db_user.password.is_some()),
|
has_password: Some(db_user.password.is_some()),
|
||||||
has_totp: Some(db_user.totp_secret.is_some()),
|
has_totp: Some(db_user.totp_secret.is_some()),
|
||||||
github_id: None,
|
github_id: None,
|
||||||
|
discord_id: None,
|
||||||
|
steam_id: None,
|
||||||
payout_data: Some(UserPayoutData {
|
payout_data: Some(UserPayoutData {
|
||||||
paypal_address: db_user.paypal_email,
|
paypal_address: db_user.paypal_email,
|
||||||
paypal_country: db_user.paypal_country,
|
paypal_country: db_user.paypal_country,
|
||||||
|
|||||||
@@ -481,7 +481,15 @@ pub async fn user_get(
|
|||||||
let user_id = data.id;
|
let user_id = data.id;
|
||||||
|
|
||||||
let mut response: crate::models::users::User = if is_admin {
|
let mut response: crate::models::users::User = if is_admin {
|
||||||
crate::models::users::User::from_full(data)
|
let github_id =
|
||||||
|
data.github_id.and_then(|id| u64::try_from(id).ok());
|
||||||
|
let discord_id = data.discord_id.map(|id| id.to_string());
|
||||||
|
let steam_id = data.steam_id.map(|id| id.to_string());
|
||||||
|
let mut user = crate::models::users::User::from_full(data);
|
||||||
|
user.github_id = github_id;
|
||||||
|
user.discord_id = discord_id;
|
||||||
|
user.steam_id = steam_id;
|
||||||
|
user
|
||||||
} else {
|
} else {
|
||||||
data.into()
|
data.into()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1663,6 +1663,8 @@ export namespace Labrinth {
|
|||||||
allow_friend_requests?: boolean
|
allow_friend_requests?: boolean
|
||||||
moderation_notes?: Common.ModerationNote | null
|
moderation_notes?: Common.ModerationNote | null
|
||||||
github_id?: number
|
github_id?: number
|
||||||
|
discord_id?: string
|
||||||
|
steam_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchUser = {
|
export type SearchUser = {
|
||||||
|
|||||||
Reference in New Issue
Block a user