Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
fd28721
Begin external projects moderator database frontend
Prospector Mar 28, 2026
804f6ca
add copy link button
Prospector Mar 28, 2026
2d2598b
begin project page permissions settings
Prospector Apr 3, 2026
873a12b
MEL database backend routes
aecsocket Apr 6, 2026
81abcf6
include filename in external files
aecsocket Apr 19, 2026
e1f44b8
wip: when uploading a version file, fetch its overrides as a list
aecsocket Apr 22, 2026
bc9e586
wip: override license checks
aecsocket Apr 23, 2026
8d4076f
improve FileHost ref counting
aecsocket Apr 23, 2026
d846ef7
file host read capability
aecsocket Apr 23, 2026
15f67ea
scan files when inserting version file
aecsocket Apr 23, 2026
796049f
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 1, 2026
e33b1dc
add dependency sha1 field
aecsocket May 1, 2026
220733f
clean up version files
aecsocket May 2, 2026
b93f6c6
wip: attributions
aecsocket May 2, 2026
f01d149
update s3 file host
aecsocket May 2, 2026
d983f11
attribution scanning basic works
aecsocket May 2, 2026
1b26f55
works
aecsocket May 2, 2026
e30cf26
insert attribution info after resolving
aecsocket May 3, 2026
d53d5c4
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 3, 2026
bfd5924
add routes
aecsocket May 3, 2026
a0471da
Merge remote-tracking branch 'origin/main' into boris/dev-886-modpack…
Prospector May 5, 2026
91d74e7
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 6, 2026
84888dc
remove dep sha1 stuff
aecsocket May 6, 2026
1fdaf8e
prepr
Prospector May 7, 2026
9819611
wip: override file sources
aecsocket May 7, 2026
2ee217b
add files_missing_attributions to versions
aecsocket May 7, 2026
b6b5271
return extended version info + attributed at/by
aecsocket May 7, 2026
867aadc
hook up frontend to backend (mostly)
Prospector May 9, 2026
0d488d1
Merge remote-tracking branch 'origin/main' into boris/dev-886-modpack…
Prospector May 9, 2026
c07b7af
expose version date published
aecsocket May 9, 2026
7315c35
withholding version visibility
aecsocket May 14, 2026
c75e17e
frontend work
Prospector May 15, 2026
3801039
prepr
Prospector May 15, 2026
030dc2b
Merge branch 'main' into boris/dev-886-modpack-project
Prospector May 15, 2026
1f28196
use api-client for img upload
Prospector May 15, 2026
00993a3
moar frontend
Prospector May 16, 2026
3eb76da
prepr
Prospector May 16, 2026
2d3b4ab
Add schema to attribution resolution and Flame project results
aecsocket May 16, 2026
8d82bf3
sqlx prepare
aecsocket May 16, 2026
61ff32a
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 16, 2026
cf02d15
changes
aecsocket May 16, 2026
9ae1ddc
remove feature flag, fix optional proof images
Prospector May 16, 2026
e11cd51
fix schema
aecsocket May 16, 2026
e16884a
fmt
aecsocket May 17, 2026
876a4b6
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 19, 2026
5d7a77c
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 19, 2026
0bfa162
fix deletion and file fetch
aecsocket May 19, 2026
6de97cc
prepare
aecsocket May 19, 2026
50eaecf
Merge remote-tracking branch 'origin/main' into boris/dev-886-modpack…
Prospector May 20, 2026
9071f7d
fix admonition
Prospector May 20, 2026
61209ac
update frontend stuff to new schema
Prospector May 20, 2026
25c63b5
prepr
Prospector May 20, 2026
549e9d5
attribution on dependencies
aecsocket May 21, 2026
611e308
fixes
aecsocket May 21, 2026
578b8df
sqlx prepare
aecsocket May 21, 2026
2fecbe9
fixes
aecsocket May 21, 2026
84bcf36
routes
aecsocket May 21, 2026
cee57ff
fix routes
aecsocket May 21, 2026
690f5c1
Version grandfathering
aecsocket May 21, 2026
145c79f
prepare
aecsocket May 21, 2026
b97f338
feat: implement access tab with dummy data
IMB11 May 4, 2026
7ca96c3
fix: spacing
IMB11 May 4, 2026
5689c2b
feat: qa
IMB11 May 6, 2026
224befa
feat: implement backend
IMB11 May 6, 2026
33400c7
qa: qa pass
IMB11 May 8, 2026
a1ebba5
feat: fix user "search"
IMB11 May 8, 2026
773f142
fix: lint
IMB11 May 8, 2026
cf25e6e
feat: change to bitfield
IMB11 May 8, 2026
054a9c4
feat: fix fields
IMB11 May 8, 2026
c4f093b
fix: lint
IMB11 May 8, 2026
3687ade
fix: lint
IMB11 May 8, 2026
bb8e85e
feat: hook up api
IMB11 May 16, 2026
7606329
feat: fix permissions
IMB11 May 19, 2026
a94e498
feat: audit log table event start
IMB11 May 19, 2026
cdced21
feat: better mobile mode for audit log table
IMB11 May 19, 2026
96d0898
feat: i18n
IMB11 May 19, 2026
740331f
feat: qa
IMB11 May 19, 2026
c7d95c3
feat: enforce permissions
IMB11 May 19, 2026
645b632
feat: email template start
IMB11 May 20, 2026
1ea2a75
feat: qa
IMB11 May 21, 2026
6612695
fix: tooltip bug
IMB11 May 21, 2026
4db6236
feat: qa
IMB11 May 21, 2026
13eb82a
impl: sse support in api-client
IMB11 May 21, 2026
9db8c55
feat: sse impl
IMB11 May 21, 2026
39a529f
fix: desync path
IMB11 May 21, 2026
ec17c40
feat: time frame picker from analytics
IMB11 May 22, 2026
498a719
feat: QA
IMB11 May 22, 2026
747c0c6
fix: spacing
IMB11 May 22, 2026
43128d4
fix: permisison audit log entries
IMB11 May 22, 2026
aff6d1d
fix: hosting manage page shared server detection
IMB11 May 22, 2026
fb4d3f8
fix: lint
IMB11 May 22, 2026
d7b6625
feat: qa + lint
IMB11 May 22, 2026
a0b644f
feat: audit log table sort by time
IMB11 May 22, 2026
5c68463
feat: finish frontend panel stuff
IMB11 May 22, 2026
5380ae5
fix: lint
IMB11 May 22, 2026
a4d93c2
fix: backend alignment
IMB11 May 22, 2026
a305464
fix: lint
IMB11 May 22, 2026
0e0dda5
Merge branch 'main' into cal/sharing-tab-panel
IMB11 May 22, 2026
9ed9873
fix: supress friend errors
IMB11 May 22, 2026
3cd1a06
wip: bulk routes
aecsocket May 23, 2026
e8114a3
Merge remote-tracking branch 'origin/main' into boris/dev-886-modpack…
Prospector May 24, 2026
8658dbf
Merge remote-tracking branch 'origin/main' into boris/dev-886-modpack…
Prospector May 24, 2026
c8b64df
pushing what i've got rn
Prospector May 25, 2026
803aa49
feat: qa
IMB11 May 25, 2026
fdecfee
Merge branch 'main' into cal/sharing-tab-panel
IMB11 May 25, 2026
af2224d
include link in NoPermission
aecsocket May 25, 2026
99c77d7
change hash insert to bulk route
aecsocket May 25, 2026
8bc26f2
query flame even if entry in MEL
aecsocket May 25, 2026
34c1a22
fix: qa
IMB11 May 25, 2026
185d4e3
fix: lint
IMB11 May 25, 2026
418a35f
Merge branch 'cal/sharing-tab-panel' of https://github.com/modrinth/c…
IMB11 May 25, 2026
ecd7781
delete file with weird name
Prospector May 25, 2026
ccae589
feat: redirect `/hosting` to archon
Sychic May 25, 2026
a140ba4
feat: server invite notification type
Sychic May 26, 2026
1caff07
feat: direct email notification endpoint
Sychic May 26, 2026
d350e0c
feat: revoke notification endpoint
Sychic May 26, 2026
8d1af7b
feat: specify users to remove notifications from
Sychic May 26, 2026
5997529
refactor: insert notifications before sending emails
Sychic May 27, 2026
4901830
refactor: rename endpoint
Sychic May 27, 2026
7a420f0
Prioritise putting override files in existing groups even with Extern…
aecsocket May 27, 2026
4653b0c
fix how hex bytes are handled in route
aecsocket May 27, 2026
edb82b5
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket May 27, 2026
2175be5
feat: coolbot moderation changes (#6215)
Prospector May 28, 2026
dc1c3c8
QA
Prospector May 28, 2026
78b617c
Merge remote-tracking branch 'origin/main' into boris/dev-886-modpack…
Prospector May 29, 2026
04bdbd2
prepr
Prospector May 29, 2026
888d70f
Merge branch 'main' into boris/dev-886-modpack-project
aecsocket Jun 1, 2026
fa49520
Merge remote-tracking branch 'origin/main' into cal/sharing-tab-panel
IMB11 Jun 1, 2026
2e2234e
fix: utils barrel
IMB11 Jun 1, 2026
ac7ae47
fix: safari cookies in dev
IMB11 Jun 1, 2026
d76d2e6
Merge remote-tracking branch 'origin/cal/sharing-tab-panel' into tmp-…
fetchfern Jun 1, 2026
bab00fc
fix: pin nuxt
IMB11 Jun 2, 2026
6cd407b
feat: fixes + notif fix
IMB11 Jun 2, 2026
248cfe6
refactor: remove archon redirect
Sychic Jun 2, 2026
1e9ff0f
Merge branch 'cal/sharing-tab-panel' of https://github.com/modrinth/c…
IMB11 Jun 2, 2026
35cdc32
style: mark field unused
Sychic Jun 2, 2026
471c34d
feat: dedup external notifications
Sychic Jun 2, 2026
8e277b2
Merge remote-tracking branch 'origin/main' into cal/sharing-tab-panel
IMB11 Jun 2, 2026
d6c76fb
Merge branch 'cal/sharing-tab-panel' into tmp-staging-access-attrib
IMB11 Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"files.insertFinalNewline": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "always"
"source.organizeImports": "never"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[vue]": {
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ bool_to_int_with_if = "warn"
borrow_as_ptr = "warn"
cfg_not_test = "warn"
clear_with_drain = "warn"
type_complexity = "allow"
cloned_instead_of_copied = "warn"
collection_is_never_read = "warn"
dbg_macro = "warn"
Expand Down
33 changes: 33 additions & 0 deletions apps/app-frontend/src/pages/hosting/manage/Access.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
import {
injectModrinthClient,
injectModrinthServerContext,
ServersManageAccessPage,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'

const client = injectModrinthClient()
const { serverId } = injectModrinthServerContext()
const queryClient = useQueryClient()

try {
await Promise.all([
queryClient.ensureQueryData({
queryKey: ['servers', 'users', 'v1', serverId],
queryFn: () => client.archon.server_users_v1.list(serverId),
staleTime: 30_000,
}),
queryClient.ensureQueryData({
queryKey: ['servers', 'v1', 'detail', serverId],
queryFn: () => client.archon.servers_v1.get(serverId),
staleTime: 30_000,
}),
])
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
</script>

<template>
<ServersManageAccessPage />
</template>
3 changes: 2 additions & 1 deletion apps/app-frontend/src/pages/hosting/manage/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Access from './Access.vue'
import Backups from './Backups.vue'
import Content from './Content.vue'
import Files from './Files.vue'
import Index from './Index.vue'
import Overview from './Overview.vue'

export { Backups, Content, Files, Index, Overview }
export { Access, Backups, Content, Files, Index, Overview }
8 changes: 8 additions & 0 deletions apps/app-frontend/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ export default new createRouter({
breadcrumb: [{ name: '?Server' }],
},
},
{
path: 'access',
name: 'ServerManageAccess',
component: Hosting.Access,
meta: {
breadcrumb: [{ name: '?Server' }],
},
},
],
},
{
Expand Down
1 change: 0 additions & 1 deletion apps/frontend/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,3 @@ These composables are deprecated and should not be used in new code:

- **`useAsyncData`** - we use tanstack, not nuxt's built in async data utility.
- **`useBaseFetch`** (`src/composables/fetch.js`) — legacy Labrinth fetch wrapper. Use `client.labrinth.*` modules instead.
- **`useServersFetch`** (`src/composables/servers/servers-fetch.ts`) — legacy Archon fetch wrapper with manual retry/circuit-breaker. Use `client.archon.*` modules instead — refer to the `packages/api-client/CLAUDE.md` for more information.
1 change: 1 addition & 0 deletions apps/frontend/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export default defineNuxtConfig({
globalThis.INTERCOM_APP_ID ||
'ykeritl9',
production: isProduction(),
cookieSecure: isProduction(),
buildEnv: process.env.BUILD_ENV,
preview: process.env.PREVIEW === 'true',
featureFlagOverrides: getFeatureFlagOverrides(),
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@types/semver": "^7.7.1",
"autoprefixer": "^10.4.19",
"glob": "^10.2.7",
"nuxt": "^3.20.2",
"nuxt": "=3.20.2",
"postcss": "^8.4.39",
"prettier-plugin-tailwindcss": "^0.6.5",
"sass": "^1.58.0",
Expand Down
199 changes: 151 additions & 48 deletions apps/frontend/src/components/ui/NotificationItem.vue
Original file line number Diff line number Diff line change
@@ -1,52 +1,123 @@
<template>
<div
class="notification"
:class="{
'has-body': hasBody,
compact: compact,
read: notification.read,
}"
:class="
type === 'server_invite'
? { read: notification.read }
: {
notification: true,
'has-body': hasBody,
compact: compact,
read: notification.read,
}
"
>
<nuxt-link
v-if="!type"
:to="notification.link"
class="notification__icon backed-svg"
:class="{ raised: raised }"
>
<BellIcon />
</nuxt-link>
<DoubleIcon v-else class="notification__icon">
<template #primary>
<nuxt-link v-if="project" :to="getProjectLink(project)" tabindex="-1">
<Avatar size="xs" :src="project.icon_url" :raised="raised" no-shadow />
</nuxt-link>
<nuxt-link
v-else-if="organization"
:to="`/organization/${organization.slug}`"
tabindex="-1"
<template v-if="type === 'server_invite'">
<div class="flex flex-col gap-4">
<ModrinthServersIcon class="h-auto w-56 max-w-full text-[var(--color-heading)]" />
<div
class="flex flex-wrap items-center gap-x-1.5 gap-y-2 text-lg leading-tight text-[var(--color-heading)]"
>
<Avatar size="xs" :src="organization.icon_url" :raised="raised" no-shadow />
</nuxt-link>
<nuxt-link v-else-if="user" :to="getUserLink(user)" tabindex="-1">
<Avatar size="xs" :src="user.avatar_url" :raised="raised" no-shadow />
</nuxt-link>
<Avatar v-else size="xs" :raised="raised" no-shadow />
</template>
<template #secondary>
<ScaleIcon
v-if="type === 'moderator_message' || type === 'status_change'"
class="moderation-color"
/>
<UserPlusIcon v-else-if="type === 'team_invite' && project" class="creator-color" />
<UserPlusIcon
v-else-if="type === 'organization_invite' && organization"
class="creator-color"
/>
<VersionIcon v-else-if="type === 'project_update' && project && version" />
<BellIcon v-else />
</template>
</DoubleIcon>
<div class="notification__title">
<nuxt-link
v-if="invitedBy"
:to="`/user/${invitedBy.username}`"
class="inline-flex items-center font-bold text-[var(--color-heading)] hover:underline"
>
<Avatar
:src="invitedBy.avatar_url"
circle
size="xxs"
no-shadow
:raised="raised"
class="mr-1.5 inline-flex"
/>
<span>{{ invitedBy.username }}</span>
</nuxt-link>
<span v-if="invitedBy">has invited you to join</span>
<span v-else>You have been invited to join</span>
<span class="font-bold text-[var(--color-heading)]">
{{ notification.body.server_name }}
</span>
<span>.</span>
</div>
<div
v-if="!notification.read"
class="flex flex-wrap items-center gap-3"
:class="{ 'gap-2': compact }"
>
<ButtonStyled :circular="compact" color="brand" :type="compact ? 'transparent' : null">
<button
v-tooltip="compact ? 'Accept' : null"
@click="performActionByTitle(notification, 'Accept')"
>
<CheckIcon />
<span v-if="!compact">Accept</span>
</button>
</ButtonStyled>
<ButtonStyled :circular="compact" color="red" :type="compact ? 'transparent' : null">
<button
v-tooltip="compact ? 'Decline' : null"
@click="performActionByTitle(notification, 'Deny')"
>
<XIcon />
<span v-if="!compact">Decline</span>
</button>
</ButtonStyled>
</div>
<div
class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[var(--color-text-secondary)]"
>
<span v-if="notification.read" class="inline-flex items-center font-bold text-[var(--color-text)]">
<CheckCircleIcon /> Read
</span>
<span v-tooltip="formatDateTime(notification.created)" class="inline-flex items-center">
<CalendarIcon class="mr-1" /> Received
{{ formatRelativeTime(notification.created) }}
</span>
<CopyCode v-if="flags.developerMode" :text="notification.id" />
</div>
</div>
</template>
<template v-else>
<nuxt-link
v-if="!type"
:to="notification.link"
class="notification__icon backed-svg"
:class="{ raised: raised }"
>
<BellIcon />
</nuxt-link>
<DoubleIcon v-else class="notification__icon">
<template #primary>
<nuxt-link v-if="project" :to="getProjectLink(project)" tabindex="-1">
<Avatar size="xs" :src="project.icon_url" :raised="raised" no-shadow />
</nuxt-link>
<nuxt-link
v-else-if="organization"
:to="`/organization/${organization.slug}`"
tabindex="-1"
>
<Avatar size="xs" :src="organization.icon_url" :raised="raised" no-shadow />
</nuxt-link>
<nuxt-link v-else-if="user" :to="getUserLink(user)" tabindex="-1">
<Avatar size="xs" :src="user.avatar_url" :raised="raised" no-shadow />
</nuxt-link>
<Avatar v-else size="xs" :raised="raised" no-shadow />
</template>
<template #secondary>
<ScaleIcon
v-if="type === 'moderator_message' || type === 'status_change'"
class="moderation-color"
/>
<UserPlusIcon v-else-if="type === 'team_invite' && project" class="creator-color" />
<UserPlusIcon
v-else-if="type === 'organization_invite' && organization"
class="creator-color"
/>
<VersionIcon v-else-if="type === 'project_update' && project && version" />
<BellIcon v-else />
</template>
</DoubleIcon>
<div class="notification__title">
<template v-if="type === 'project_update' && project && version">
A project you follow,
<nuxt-link :to="getProjectLink(project)" class="title-link">{{ project.title }}</nuxt-link>
Expand Down Expand Up @@ -295,6 +366,7 @@
<CopyCode v-if="flags.developerMode" :text="notification.id" />
</div>
</div>
</template>
</div>
</template>

Expand Down Expand Up @@ -328,6 +400,7 @@ import { markAsRead } from '~/helpers/platform-notifications'
import { getProjectLink, getVersionLink } from '~/helpers/projects'
import { acceptTeamInvite, removeSelfFromTeam } from '~/helpers/teams'

import ModrinthServersIcon from '../brand/ModrinthServersIcon.vue'
import ThreadSummary from './thread/ThreadSummary.vue'

const client = injectModrinthClient()
Expand Down Expand Up @@ -415,9 +488,25 @@ async function performAction(notification, actionIndex) {
await read()

if (actionIndex !== null) {
await useBaseFetch(`${notification.actions[actionIndex].action_route[1]}`, {
method: notification.actions[actionIndex].action_route[0].toUpperCase(),
})
const [method, route] = notification.actions[actionIndex].action_route

// TODO: fix all this jank shit
if (route.startsWith('hosting/') || route.startsWith('/hosting/')) {
const archonRoute = route
.replace(/^\/?hosting/, '')
.replace('/invite/accept', '/invites/accept')
.replace('/invite/deny', '/invites/decline')

await client.request(archonRoute, {
api: 'archon',
version: 1,
method: method.toUpperCase(),
})
} else {
await useBaseFetch(route, {
method: method.toUpperCase(),
})
}
}
} catch (err) {
addNotification({
Expand All @@ -429,6 +518,20 @@ async function performAction(notification, actionIndex) {
stopLoading()
}

function performActionByTitle(notification, title) {
const actionIndex = notification.actions.findIndex((action) => action.title === title)
if (actionIndex === -1) {
addNotification({
title: 'An error occurred',
text: `Missing ${title.toLowerCase()} action for notification.`,
type: 'error',
})
return
}

return performAction(notification, actionIndex)
}

function getMessages() {
const messages = []
if (props.notification.body.message_id) {
Expand Down
Loading
Loading