Merge commit '75e3994c6e57c2d3353084188b21f706d844ffb3' into beta

This commit is contained in:
2025-10-19 06:55:17 +03:00
7 changed files with 130 additions and 8 deletions

View File

@@ -48,7 +48,7 @@
] ]
}, },
"productName": "AstralRinth App", "productName": "AstralRinth App",
"version": "0.10.1101", "version": "0.10.1201",
"mainBinaryName": "AstralRinth App", "mainBinaryName": "AstralRinth App",
"identifier": "AstralRinthApp", "identifier": "AstralRinthApp",
"plugins": { "plugins": {
@@ -99,7 +99,7 @@
], ],
"csp": { "csp": {
"default-src": "'self' customprotocol: asset:", "default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: https://git.astralium.su https://authserver.ely.by http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs 'self' data: blob:", "connect-src": "ipc: https://git.astralium.su https://authserver.ely.by http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.sentry.io https://api.mclo.gs 'self' data: blob:",
"font-src": [ "font-src": [
"https://cdn-raw.modrinth.com/fonts/" "https://cdn-raw.modrinth.com/fonts/"
], ],

View File

@@ -1106,6 +1106,9 @@
"profile.button.billing": { "profile.button.billing": {
"message": "Manage user billing" "message": "Manage user billing"
}, },
"profile.button.edit-role": {
"message": "Edit role"
},
"profile.button.info": { "profile.button.info": {
"message": "View user details" "message": "View user details"
}, },

View File

@@ -2,6 +2,37 @@
<div v-if="user" class="experimental-styles-within"> <div v-if="user" class="experimental-styles-within">
<ModalCreation ref="modal_creation" /> <ModalCreation ref="modal_creation" />
<CollectionCreateModal ref="modal_collection_creation" /> <CollectionCreateModal ref="modal_collection_creation" />
<NewModal ref="editRoleModal" header="Edit role">
<div class="flex w-80 flex-col gap-4">
<div class="flex flex-col gap-2">
<TeleportDropdownMenu
v-model="selectedRole"
:options="roleOptions"
name="edit-role"
placeholder="Select a role"
/>
</div>
<div class="flex justify-end gap-2">
<ButtonStyled>
<button @click="cancelRoleEdit">
<XIcon />
Cancel
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button
:disabled="!selectedRole || selectedRole === user.role || isSavingRole"
@click="saveRoleEdit"
>
<template v-if="isSavingRole">
<SpinnerIcon class="animate-spin" /> Saving...
</template>
<template v-else> <SaveIcon /> Save changes </template>
</button>
</ButtonStyled>
</div>
</div>
</NewModal>
<NewModal v-if="auth.user && isStaff(auth.user)" ref="userDetailsModal" header="User details"> <NewModal v-if="auth.user && isStaff(auth.user)" ref="userDetailsModal" header="User details">
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
@@ -127,6 +158,10 @@
}, },
{ id: 'copy-id', action: () => copyId() }, { id: 'copy-id', action: () => copyId() },
{ id: 'copy-permalink', action: () => copyPermalink() }, { id: 'copy-permalink', action: () => copyPermalink() },
{
divider: true,
shown: auth.user && isAdmin(auth.user),
},
{ {
id: 'open-billing', id: 'open-billing',
action: () => navigateTo(`/admin/billing/${user.id}`), action: () => navigateTo(`/admin/billing/${user.id}`),
@@ -137,6 +172,11 @@
action: () => $refs.userDetailsModal.show(), action: () => $refs.userDetailsModal.show(),
shown: auth.user && isStaff(auth.user), shown: auth.user && isStaff(auth.user),
}, },
{
id: 'edit-role',
action: () => openRoleEditModal(),
shown: auth.user && isAdmin(auth.user),
},
]" ]"
aria-label="More options" aria-label="More options"
> >
@@ -165,6 +205,10 @@
<InfoIcon aria-hidden="true" /> <InfoIcon aria-hidden="true" />
{{ formatMessage(messages.infoButton) }} {{ formatMessage(messages.infoButton) }}
</template> </template>
<template #edit-role>
<EditIcon aria-hidden="true" />
{{ formatMessage(messages.editRoleButton) }}
</template>
</OverflowMenu> </OverflowMenu>
</ButtonStyled> </ButtonStyled>
</template> </template>
@@ -355,6 +399,8 @@ import {
LockIcon, LockIcon,
MoreVerticalIcon, MoreVerticalIcon,
ReportIcon, ReportIcon,
SaveIcon,
SpinnerIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
@@ -362,10 +408,13 @@ import {
ButtonStyled, ButtonStyled,
commonMessages, commonMessages,
ContentPageHeader, ContentPageHeader,
injectNotificationManager,
NewModal, NewModal,
OverflowMenu, OverflowMenu,
TeleportDropdownMenu,
useRelativeTime, useRelativeTime,
} from '@modrinth/ui' } from '@modrinth/ui'
import { isAdmin } from '@modrinth/utils'
import { IntlFormatted } from '@vintl/vintl/components' import { IntlFormatted } from '@vintl/vintl/components'
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component' import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
@@ -397,6 +446,8 @@ const formatCompactNumber = useCompactNumber(true)
const formatRelativeTime = useRelativeTime() const formatRelativeTime = useRelativeTime()
const { addNotification } = injectNotificationManager()
const messages = defineMessages({ const messages = defineMessages({
profileProjectsStats: { profileProjectsStats: {
id: 'profile.stats.projects', id: 'profile.stats.projects',
@@ -471,6 +522,10 @@ const messages = defineMessages({
id: 'profile.button.info', id: 'profile.button.info',
defaultMessage: 'View user details', defaultMessage: 'View user details',
}, },
editRoleButton: {
id: 'profile.button.edit-role',
defaultMessage: 'Edit role',
},
userNotFoundError: { userNotFoundError: {
id: 'profile.error.not-found', id: 'profile.error.not-found',
defaultMessage: 'User not found', defaultMessage: 'User not found',
@@ -647,6 +702,55 @@ const navLinks = computed(() => [
.slice() .slice()
.sort((a, b) => a.label.localeCompare(b.label)), .sort((a, b) => a.label.localeCompare(b.label)),
]) ])
const selectedRole = ref(user.value.role)
const isSavingRole = ref(false)
const roleOptions = ['developer', 'moderator', 'admin']
const editRoleModal = useTemplateRef('editRoleModal')
const openRoleEditModal = () => {
selectedRole.value = user.value.role
editRoleModal.value?.show()
}
const cancelRoleEdit = () => {
selectedRole.value = user.value.role
editRoleModal.value?.hide()
}
function saveRoleEdit() {
if (!selectedRole.value || selectedRole.value === user.value.role) {
return
}
isSavingRole.value = true
useBaseFetch(`user/${user.value.id}`, {
method: 'PATCH',
body: {
role: selectedRole.value,
},
})
.then(() => {
user.value.role = selectedRole.value
editRoleModal.value?.hide()
})
.catch(() => {
console.error('Failed to update user role:', error)
addNotification({
type: 'error',
title: 'Failed to update role',
message: 'An error occurred while updating the user role. Please try again.',
})
})
.finally(() => {
isSavingRole.value = false
})
}
</script> </script>
<script> <script>
export default defineNuxtComponent({ export default defineNuxtComponent({

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "SELECT TRUE FROM mods WHERE organization_id = $1 AND status IN ('public', 'archived') LIMIT 1", "query": "SELECT TRUE FROM mods WHERE organization_id = $1 AND status IN ('approved', 'archived') LIMIT 1",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -18,5 +18,5 @@
null null
] ]
}, },
"hash": "eb792d5033d7079fe3555593d8731f8853235275e4d5614636b5db524a4920d5" "hash": "829a4523233e957f8876d248a17ec04c245af41f43fa40aff9ee07e893dabf3a"
} }

View File

@@ -377,7 +377,7 @@ pub async fn is_visible_organization(
// This is meant to match the same projects as the `Project::is_searchable` method, but we're not using // This is meant to match the same projects as the `Project::is_searchable` method, but we're not using
// it here because that'd entail pulling in all projects for the organization // it here because that'd entail pulling in all projects for the organization
let has_searchable_projects = sqlx::query_scalar!( let has_searchable_projects = sqlx::query_scalar!(
"SELECT TRUE FROM mods WHERE organization_id = $1 AND status IN ('public', 'archived') LIMIT 1", "SELECT TRUE FROM mods WHERE organization_id = $1 AND status IN ('approved', 'archived') LIMIT 1",
organization.id as database::models::ids::DBOrganizationId organization.id as database::models::ids::DBOrganizationId
) )
.fetch_optional(pool) .fetch_optional(pool)

View File

@@ -80,11 +80,12 @@ impl CacheValueType {
} }
} }
/// Returns the expiry time for entries of this type of cache item, in seconds.
pub fn expiry(&self) -> i64 { pub fn expiry(&self) -> i64 {
match self { match self {
CacheValueType::File => 60 * 60 * 24 * 30, // 30 days CacheValueType::File => 30 * 24 * 60 * 60, // 30 days
CacheValueType::FileHash => 60 * 60 * 24 * 30, // 30 days CacheValueType::FileHash => 30 * 24 * 60 * 60, // 30 days
_ => 60 * 60 * 30, // 30 minutes _ => 30 * 60, // 30 minutes
} }
} }

View File

@@ -10,6 +10,20 @@ export type VersionEntry = {
} }
const VERSIONS: VersionEntry[] = [ const VERSIONS: VersionEntry[] = [
{
date: `2025-10-15T12:15:00-07:00`,
product: 'app',
version: '0.10.12',
body: `## Improvements
- Fixed cache sticking around for way too long (30 hours instead of 30 minutes).`,
},
{
date: `2025-10-15T04:11:00-07:00`,
product: 'app',
version: '0.10.11',
body: `## Improvements
- Fixed ads being able to play audio.`,
},
{ {
date: `2025-10-14T18:45:00-07:00`, date: `2025-10-14T18:45:00-07:00`,
product: 'servers', product: 'servers',