refactor: remove useBaseFetch for @modrinth/api-client (#5596)

* Reapply "fix: start swapping useBaseFetch usages to api-client"

This reverts commit f4f33db7019ea861addb2c66c204d736800b7b6c.

* fix: bugs

* fix: analytics

* fix: lint
This commit is contained in:
Calum H.
2026-03-17 20:06:19 +00:00
committed by GitHub
parent 58c1e225c8
commit 87c86c7d0d
64 changed files with 2073 additions and 691 deletions

View File

@@ -30,7 +30,7 @@ This is the Modrinth monorepo — it contains all Modrinth projects, both fronte
| `api-client` | API client for Nuxt, Tauri, and Node/browser |
| `app-lib` | Shared app library |
| `blog` | Blog system and changelog data |
| `utils` | Shared utility functions |
| `utils` | Shared utility functions (mostly deprecated) |
| `moderation` | Moderation utilities |
| `daedalus` | Daedalus protocol |
| `tooling-config` | ESLint, Prettier, TypeScript configs |
@@ -85,6 +85,7 @@ Each project may have its own `CLAUDE.md` with detailed instructions:
### General
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
- For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc.
- Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client`
## Edit Tool - Whitespace Handling (CLAUDE ONLY)

View File

@@ -328,6 +328,7 @@ import {
Categories,
CopyCode,
DoubleIcon,
injectModrinthClient,
injectNotificationManager,
ProjectStatusBadge,
useFormatDateTime,
@@ -341,6 +342,7 @@ import { acceptTeamInvite, removeSelfFromTeam } from '~/helpers/teams'
import ThreadSummary from './thread/ThreadSummary.vue'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const emit = defineEmits(['update:notifications'])
const formatRelativeTime = useRelativeTime()
@@ -407,7 +409,7 @@ async function read() {
? props.notification.grouped_notifs.map((notif) => notif.id)
: []),
]
const updateNotifs = await markAsRead(ids)
const updateNotifs = await markAsRead(client, ids)
const newNotifs = updateNotifs(props.notifications)
emit('update:notifications', newNotifs)
} catch (err) {

View File

@@ -357,7 +357,7 @@ const props = withDefaults(
},
)
const projects = ref(props.projects || [])
const projects = computed(() => props.projects || [])
// const selectedChart = ref('downloads')
const selectedChart = computed({
@@ -389,6 +389,13 @@ const tinyRevenueChart = ref()
const selectedDisplayProjects = ref(props.projects || [])
watch(
() => props.projects,
(newProjects) => {
selectedDisplayProjects.value = newProjects || []
},
)
const removeProjectFromDisplay = (id: string) => {
selectedDisplayProjects.value = selectedDisplayProjects.value.filter((p) => p.id !== id)
}

View File

@@ -42,13 +42,18 @@
<script setup lang="ts">
import { MessageIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import {
Admonition,
ButtonStyled,
defineMessages,
injectModrinthClient,
useVIntl,
} from '@modrinth/ui'
import { capitalizeString } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import { computed, watch } from 'vue'
import { useBaseFetch } from '~/composables/fetch.js'
const client = injectModrinthClient()
const { formatMessage } = useVIntl()
const messages = defineMessages({
@@ -100,33 +105,24 @@ const messages = defineMessages({
},
})
interface UserLimits {
current: number
max: number
}
const props = defineProps<{
type: 'project' | 'org' | 'collection'
}>()
const model = defineModel<boolean>()
const apiEndpoint = computed(() => {
switch (props.type) {
case 'project':
return 'limits/projects'
case 'org':
return 'limits/organizations'
case 'collection':
return 'limits/collections'
default:
return 'limits/projects'
}
})
const { data: limits } = useQuery({
queryKey: computed(() => ['limits', props.type]),
queryFn: () => useBaseFetch(apiEndpoint.value, { apiVersion: 3 }) as Promise<UserLimits>,
queryFn: () => {
switch (props.type) {
case 'org':
return client.labrinth.limits_v3.getOrganizationLimits()
case 'collection':
return client.labrinth.limits_v3.getCollectionLimits()
default:
return client.labrinth.limits_v3.getProjectLimits()
}
},
})
const typeName = computed<{ singular: string; plural: string }>(() => {

View File

@@ -107,6 +107,7 @@
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
ArrowLeftRightIcon,
ChevronRightIcon,
@@ -131,7 +132,6 @@ import {
getTaxThreshold,
getTaxThresholdActual,
type PaymentProvider,
type PayoutMethod,
provideWithdrawContext,
type WithdrawStage,
} from '@/providers/creator-withdraw.ts'
@@ -146,21 +146,9 @@ import MuralpayKycStage from './withdraw-stages/MuralpayKycStage.vue'
import TaxFormStage from './withdraw-stages/TaxFormStage.vue'
import TremendousDetailsStage from './withdraw-stages/TremendousDetailsStage.vue'
type FormCompletionStatus = 'unknown' | 'unrequested' | 'unsigned' | 'tin-mismatch' | 'complete'
interface UserBalanceResponse {
available: number
withdrawn_lifetime: number
withdrawn_ytd: number
pending: number
dates: Record<string, number>
requested_form_type: string | null
form_completion_status: FormCompletionStatus | null
}
const props = defineProps<{
balance: UserBalanceResponse | null
preloadedPaymentData?: { country: string; methods: PayoutMethod[] } | null
balance: Labrinth.Payout.v3.PayoutBalance | null | undefined
preloadedPaymentData?: { country: string; methods: Labrinth.Payout.v3.PayoutMethod[] } | null
}>()
const emit = defineEmits<{

View File

@@ -66,6 +66,7 @@
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
ArrowDownIcon,
ArrowUpIcon,
@@ -89,31 +90,7 @@ import { Tooltip } from 'floating-vue'
import { useGeneratedState } from '~/composables/generated'
import { findRail } from '~/utils/muralpay-rails'
type PayoutStatus = 'in-transit' | 'cancelling' | 'cancelled' | 'success' | 'failed'
type PayoutMethodType = 'paypal' | 'venmo' | 'tremendous' | 'muralpay'
type PayoutSource = 'creator_rewards' | 'affilites'
type WithdrawalTransaction = {
type: 'withdrawal'
id: string
status: PayoutStatus
created: string
amount: number
fee?: number | null
method_type?: PayoutMethodType | null
method?: string
method_id?: string
method_address?: string | null
}
type PayoutAvailableTransaction = {
type: 'payout_available'
created: string
payout_source: PayoutSource
amount: number
}
type Transaction = WithdrawalTransaction | PayoutAvailableTransaction
type Transaction = Labrinth.Payout.v3.TransactionItem
const props = defineProps<{
transaction: Transaction

View File

@@ -21,13 +21,13 @@
</div>
</template>
<script setup>
import { injectModrinthClient } from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed } from 'vue'
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
import ReportInfo from '~/components/ui/report/ReportInfo.vue'
import ConversationThread from '~/components/ui/thread/ConversationThread.vue'
import { useBaseFetch } from '~/composables/fetch.js'
import { addReportMessage } from '~/helpers/threads.js'
const props = defineProps({
@@ -45,16 +45,13 @@ const props = defineProps({
},
})
const client = injectModrinthClient()
const queryClient = useQueryClient()
// Fetch raw report
const { data: rawReport } = useQuery({
queryKey: computed(() => ['report', props.reportId]),
queryFn: async () => {
const data = await useBaseFetch(`report/${props.reportId}`)
data.item_id = data.item_id.replace(/"/g, '')
return data
},
queryFn: () => client.labrinth.reports_v3.get(props.reportId),
})
// Compute user IDs needed
@@ -70,7 +67,7 @@ const userIds = computed(() => {
// Fetch users
const { data: users } = useQuery({
queryKey: computed(() => ['users', userIds.value]),
queryFn: () => useBaseFetch(`users?ids=${encodeURIComponent(JSON.stringify(userIds.value))}`),
queryFn: () => client.labrinth.users_v2.getMultiple(userIds.value),
enabled: computed(() => userIds.value.length > 0),
})
@@ -82,7 +79,7 @@ const versionId = computed(() =>
// Fetch version
const { data: version } = useQuery({
queryKey: computed(() => ['version', versionId.value]),
queryFn: () => useBaseFetch(`version/${versionId.value}`),
queryFn: () => client.labrinth.versions_v2.getVersion(versionId.value),
enabled: computed(() => !!versionId.value),
})
@@ -96,7 +93,7 @@ const projectId = computed(() => {
// Fetch project
const { data: project } = useQuery({
queryKey: computed(() => ['project', projectId.value]),
queryFn: () => useBaseFetch(`project/${projectId.value}`),
queryFn: () => client.labrinth.projects_v2.get(projectId.value),
enabled: computed(() => !!projectId.value),
})
@@ -118,7 +115,7 @@ const report = computed(() => {
// Fetch thread
const { data: rawThread } = useQuery({
queryKey: computed(() => ['thread', report.value?.thread_id]),
queryFn: () => useBaseFetch(`thread/${report.value.thread_id}`),
queryFn: () => client.labrinth.threads_v3.getThread(report.value.thread_id),
enabled: computed(() => !!report.value?.thread_id),
})

View File

@@ -24,14 +24,13 @@
<p v-if="filteredReports.length === 0">You don't have any active reports.</p>
</template>
<script setup>
import { Chips } from '@modrinth/ui'
import { Chips, injectModrinthClient } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import ReportInfo from '~/components/ui/report/ReportInfo.vue'
import { useBaseFetch } from '~/composables/fetch.js'
import { addReportMessage } from '~/helpers/threads.js'
import { asEncodedJsonArray, fetchSegmented } from '~/utils/fetch-helpers.ts'
import { fetchSegmentedWith } from '~/utils/fetch-helpers.ts'
const props = defineProps({
moderation: {
@@ -44,6 +43,7 @@ const props = defineProps({
},
})
const client = injectModrinthClient()
const viewMode = ref('open')
const reasonFilter = ref('All')
@@ -51,16 +51,11 @@ const MAX_REPORTS = 1500
const { data: rawReportsData } = useQuery({
queryKey: ['reports', MAX_REPORTS],
queryFn: () => useBaseFetch(`report?count=${MAX_REPORTS}`),
queryFn: () => client.labrinth.reports_v3.list({ count: MAX_REPORTS }),
placeholderData: [],
})
const rawReports = computed(() =>
rawReportsData.value.map((report) => ({
...report,
item_id: report.item_id.replace(/"/g, ''),
})),
)
const rawReports = computed(() => rawReportsData.value)
const reporterUsers = computed(() => rawReports.value.map((report) => report.reporter))
const reportedUsers = computed(() =>
@@ -85,7 +80,8 @@ const reasons = computed(() => [
const { data: users } = useQuery({
queryKey: computed(() => ['users', userIds.value]),
queryFn: () => fetchSegmented(userIds.value, (ids) => `users?ids=${asEncodedJsonArray(ids)}`),
queryFn: () =>
fetchSegmentedWith(userIds.value, (ids) => client.labrinth.users_v2.getMultiple(ids)),
enabled: computed(() => userIds.value.length > 0),
placeholderData: [],
})
@@ -93,14 +89,15 @@ const { data: users } = useQuery({
const { data: versions } = useQuery({
queryKey: computed(() => ['versions', versionIds.value]),
queryFn: () =>
fetchSegmented(versionIds.value, (ids) => `versions?ids=${asEncodedJsonArray(ids)}`),
fetchSegmentedWith(versionIds.value, (ids) => client.labrinth.versions_v2.getVersions(ids)),
enabled: computed(() => versionIds.value.length > 0),
placeholderData: [],
})
const { data: threads } = useQuery({
queryKey: computed(() => ['threads', threadIds.value]),
queryFn: () => fetchSegmented(threadIds.value, (ids) => `threads?ids=${asEncodedJsonArray(ids)}`),
queryFn: () =>
fetchSegmentedWith(threadIds.value, (ids) => client.labrinth.threads_v3.getMultiple(ids)),
enabled: computed(() => threadIds.value.length > 0),
placeholderData: [],
})
@@ -118,7 +115,7 @@ const projectIds = computed(() => [
const { data: projects } = useQuery({
queryKey: computed(() => ['projects', projectIds.value]),
queryFn: () =>
fetchSegmented(projectIds.value, (ids) => `projects?ids=${asEncodedJsonArray(ids)}`),
fetchSegmentedWith(projectIds.value, (ids) => client.labrinth.projects_v2.getMultiple(ids)),
enabled: computed(() => projectIds.value.length > 0),
placeholderData: [],
})

View File

@@ -1,47 +1,15 @@
import type { Organization, Project, Report, User, Version } from '@modrinth/utils'
import type { AbstractModrinthClient, Labrinth } from '@modrinth/api-client'
type Thread = { id: string }
type Notification = Labrinth.Notifications.v2.Notification
export type PlatformNotificationAction = {
title: string
action_route: [string, string]
}
export type PlatformNotificationBody = {
project_id?: string
version_id?: string
report_id?: string
thread_id?: string
invited_by?: string
organization_id?: string
}
export type PlatformNotification = {
id: string
user_id: string
type: 'project_update' | 'team_invite' | 'status_change' | 'moderator_message'
title: string
text: string
link: string
read: boolean
created: string
actions: PlatformNotificationAction[]
body?: PlatformNotificationBody
export type PlatformNotification = Notification & {
extra_data?: Record<string, unknown>
grouped_notifs?: PlatformNotification[]
}
async function getBulk<T extends { id: string }>(
type: string,
ids: string[],
apiVersion = 2,
): Promise<T[]> {
if (!ids || ids.length === 0) {
return []
}
const url = `${type}?ids=${encodeURIComponent(JSON.stringify([...new Set(ids)]))}`
async function safeBulkFetch<T>(fn: () => Promise<T[]>): Promise<T[]> {
try {
const res = await useBaseFetch(url, { apiVersion })
const res = await fn()
return Array.isArray(res) ? res : []
} catch {
return []
@@ -49,6 +17,7 @@ async function getBulk<T extends { id: string }>(
}
export async function fetchExtraNotificationData(
client: AbstractModrinthClient,
notifications: PlatformNotification[],
): Promise<PlatformNotification[]> {
const bulk = {
@@ -72,7 +41,14 @@ export async function fetchExtraNotificationData(
}
}
const reports = (await getBulk<Report>('reports', bulk.reports)).filter(Boolean)
const reports = (
await safeBulkFetch(() =>
bulk.reports.length > 0
? client.labrinth.reports_v3.getMultiple([...new Set(bulk.reports)])
: Promise.resolve([]),
)
).filter(Boolean)
for (const r of reports) {
if (!r?.item_type) continue
if (r.item_type === 'project') bulk.projects.push(r.item_id)
@@ -80,16 +56,42 @@ export async function fetchExtraNotificationData(
else if (r.item_type === 'version') bulk.versions.push(r.item_id)
}
const versions = (await getBulk<Version>('versions', bulk.versions)).filter(Boolean)
const versions = (
await safeBulkFetch(() =>
bulk.versions.length > 0
? client.labrinth.versions_v2.getVersions([...new Set(bulk.versions)])
: Promise.resolve([]),
)
).filter(Boolean)
for (const v of versions) bulk.projects.push(v.project_id)
const [projects, threads, users, organizations] = await Promise.all([
getBulk<Project>('projects', bulk.projects),
getBulk<Thread>('threads', bulk.threads),
getBulk<User>('users', bulk.users),
getBulk<Organization>('organizations', bulk.organizations, 3),
safeBulkFetch(() =>
bulk.projects.length > 0
? client.labrinth.projects_v2.getMultiple([...new Set(bulk.projects)])
: Promise.resolve([]),
),
safeBulkFetch(() =>
bulk.threads.length > 0
? client.labrinth.threads_v3.getMultiple([...new Set(bulk.threads)])
: Promise.resolve([]),
),
safeBulkFetch(() =>
bulk.users.length > 0
? client.labrinth.users_v2.getMultiple([...new Set(bulk.users)])
: Promise.resolve([]),
),
safeBulkFetch(() =>
bulk.organizations.length > 0
? client.labrinth.organizations_v3.getMultiple([...new Set(bulk.organizations)])
: Promise.resolve([]),
),
])
type Report = Labrinth.Reports.v3.Report
type Version = Labrinth.Versions.v2.Version
for (const n of notifications) {
n.extra_data = {}
if (n.body) {
@@ -153,11 +155,10 @@ function isSimilar(a: PlatformNotification, b: PlatformNotification | undefined)
}
export async function markAsRead(
client: AbstractModrinthClient,
ids: string[],
): Promise<(notifications: PlatformNotification[]) => PlatformNotification[]> {
await useBaseFetch(`notifications?ids=${JSON.stringify([...new Set(ids)])}`, {
method: 'PATCH',
})
await client.labrinth.notifications_v2.markMultipleAsRead(ids)
return (notifications: PlatformNotification[]) => {
const newNotifs = notifications ?? []
newNotifs.forEach((n) => {

View File

@@ -1541,7 +1541,7 @@ async function getLicenseData(event) {
modalLicense.value.show(event)
try {
const text = await useBaseFetch(`tag/license/${project.value.license.id}`)
const text = await client.labrinth.tags_v2.getLicenseText(project.value.license.id)
licenseText.value = text.body || formatMessage(messages.licenseErrorMessage)
} catch {
licenseText.value = formatMessage(messages.licenseErrorMessage)
@@ -1895,10 +1895,7 @@ async function invalidateProject() {
// Mutation for patching project data
const patchProjectMutation = useMutation({
mutationFn: async ({ projectId, data }) => {
await useBaseFetch(`project/${projectId}`, {
method: 'PATCH',
body: data,
})
await client.labrinth.projects_v2.edit(projectId, data)
return data
},
@@ -1942,10 +1939,7 @@ const patchProjectMutation = useMutation({
// Mutation for changing project status (setProcessing)
const patchStatusMutation = useMutation({
mutationFn: async ({ projectId, status }) => {
await useBaseFetch(`project/${projectId}`, {
method: 'PATCH',
body: { status },
})
await client.labrinth.projects_v2.edit(projectId, { status })
},
onMutate: async ({ projectId, status }) => {
@@ -2038,13 +2032,8 @@ const patchProjectV3Mutation = useMutation({
// Mutation for patching project icon
const patchIconMutation = useMutation({
mutationFn: async ({ projectId, icon }) => {
await useBaseFetch(
`project/${projectId}/icon?ext=${icon.type.split('/')[icon.type.split('/').length - 1]}`,
{
method: 'PATCH',
body: icon,
},
)
const ext = icon.type.split('/')[icon.type.split('/').length - 1]
await client.labrinth.projects_v3.changeIcon(projectId, icon, ext)
},
onSuccess: () => {
@@ -2070,23 +2059,13 @@ const patchIconMutation = useMutation({
const createGalleryItemMutation = useMutation({
mutationFn: async ({ projectId, file, title, description, featured, ordering }) => {
let url = `project/${projectId}/gallery?ext=${
file.type.split('/')[file.type.split('/').length - 1]
}&featured=${featured ?? false}`
if (title != null) {
url += `&title=${encodeURIComponent(title)}`
}
if (description != null) {
url += `&description=${encodeURIComponent(description)}`
}
if (ordering !== null && ordering !== undefined) {
url += `&ordering=${ordering}`
}
await useBaseFetch(url, {
method: 'POST',
body: file,
const ext = file.type.split('/')[file.type.split('/').length - 1]
await client.labrinth.projects_v2.createGalleryImage(projectId, file, {
ext,
featured: featured ?? false,
title,
description,
ordering,
})
},
@@ -2133,20 +2112,11 @@ const createGalleryItemMutation = useMutation({
const editGalleryItemMutation = useMutation({
mutationFn: async ({ projectId, imageUrl, title, description, featured, ordering }) => {
let url = `project/${projectId}/gallery?url=${encodeURIComponent(imageUrl)}&featured=${featured ?? false}`
if (title != null) {
url += `&title=${encodeURIComponent(title)}`
}
if (description != null) {
url += `&description=${encodeURIComponent(description)}`
}
if (ordering !== null && ordering !== undefined) {
url += `&ordering=${ordering}`
}
await useBaseFetch(url, {
method: 'PATCH',
await client.labrinth.projects_v2.editGalleryImage(projectId, imageUrl, {
featured: featured ?? false,
title,
description,
ordering,
})
},
@@ -2195,9 +2165,7 @@ const editGalleryItemMutation = useMutation({
const deleteGalleryItemMutation = useMutation({
mutationFn: async ({ projectId, imageUrl }) => {
await useBaseFetch(`project/${projectId}/gallery?url=${encodeURIComponent(imageUrl)}`, {
method: 'DELETE',
})
await client.labrinth.projects_v2.deleteGalleryImage(projectId, imageUrl)
},
onMutate: async ({ imageUrl }) => {
@@ -2562,9 +2530,7 @@ async function deleteVersion(id) {
startLoading()
await useBaseFetch(`version/${id}`, {
method: 'DELETE',
})
await client.labrinth.versions_v3.deleteVersion(id)
await invalidateProject()

View File

@@ -100,12 +100,16 @@
</template>
<script setup>
import { CheckIcon, IssuesIcon, XIcon } from '@modrinth/assets'
import { Badge, injectNotificationManager, injectProjectPageContext } from '@modrinth/ui'
import {
Badge,
injectModrinthClient,
injectNotificationManager,
injectProjectPageContext,
} from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed } from 'vue'
import ConversationThread from '~/components/ui/thread/ConversationThread.vue'
import { useBaseFetch } from '~/composables/fetch.js'
import {
getProjectLink,
isApproved,
@@ -119,11 +123,12 @@ const { addNotification } = injectNotificationManager()
const { projectV2: project, currentMember, invalidate } = injectProjectPageContext()
const auth = await useAuth()
const client = injectModrinthClient()
const queryClient = useQueryClient()
const { data: thread } = useQuery({
queryKey: computed(() => ['thread', project.value?.thread_id]),
queryFn: () => useBaseFetch(`thread/${project.value.thread_id}`),
queryFn: () => client.labrinth.threads_v3.getThread(project.value.thread_id),
enabled: computed(() => !!project.value?.thread_id),
})
@@ -131,12 +136,7 @@ async function setStatus(status) {
startLoading()
try {
const data = {}
data.status = status
await useBaseFetch(`project/${project.value.id}`, {
method: 'PATCH',
body: data,
})
await client.labrinth.projects_v2.edit(project.value.id, { status })
project.value.status = status
await invalidate()

View File

@@ -558,6 +558,7 @@ import {
Checkbox,
Combobox,
ConfirmModal,
injectModrinthClient,
injectNotificationManager,
injectProjectPageContext,
StyledInput,
@@ -566,9 +567,9 @@ import {
import { useQuery } from '@tanstack/vue-query'
import ConfirmTransferProjectModal from '~/components/ui/ConfirmTransferProjectModal.vue'
import { useBaseFetch } from '~/composables/fetch.js'
import { removeSelfFromTeam } from '~/helpers/teams.js'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const {
projectV2: project,
@@ -623,10 +624,7 @@ const transferModal = ref(null)
const { data: organizations } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'organizations']),
queryFn: () =>
useBaseFetch('user/' + auth.value?.user.id + '/organizations', {
apiVersion: 3,
}),
queryFn: () => client.labrinth.users_v2.getOrganizations(auth.value?.user.id),
enabled: computed(() => !!auth.value?.user?.id),
})
@@ -655,12 +653,8 @@ const VIEW_PAYOUTS = 1 << 9
const onAddToOrg = useClientTry(async () => {
if (!selectedOrganizationId.value) return
await useBaseFetch(`organization/${selectedOrganizationId.value}/projects`, {
method: 'POST',
body: JSON.stringify({
project_id: project.value.id,
}),
apiVersion: 3,
await client.labrinth.organizations_v3.addProject(selectedOrganizationId.value, {
project_id: project.value.id,
})
await updateMembers()
@@ -675,13 +669,11 @@ const onAddToOrg = useClientTry(async () => {
const onRemoveFromOrg = useClientTry(async () => {
if (!project.value.organization || !auth.value?.user?.id) return
await useBaseFetch(`organization/${project.value.organization}/projects/${project.value.id}`, {
method: 'DELETE',
body: JSON.stringify({
new_owner: auth.value.user.id,
}),
apiVersion: 3,
})
await client.labrinth.organizations_v3.removeProject(
project.value.organization,
project.value.id,
{ new_owner: auth.value.user.id },
)
await updateMembers()
@@ -701,13 +693,9 @@ const inviteTeamMember = async () => {
startLoading()
try {
const user = await useBaseFetch(`user/${currentUsername.value}`)
const data = {
const user = await client.labrinth.users_v2.get(currentUsername.value)
await client.labrinth.teams_v2.addMember(project.value.team, {
user_id: user.id.trim(),
}
await useBaseFetch(`team/${project.value.team}/members`, {
method: 'POST',
body: data,
})
currentUsername.value = ''
await updateMembers()
@@ -726,11 +714,9 @@ const removeTeamMember = async (index) => {
startLoading()
try {
await useBaseFetch(
`team/${project.value.team}/members/${allTeamMembers.value[index].user.id}`,
{
method: 'DELETE',
},
await client.labrinth.teams_v2.removeMember(
project.value.team,
allTeamMembers.value[index].user.id,
)
await updateMembers()
addNotification({
@@ -764,12 +750,10 @@ const updateTeamMember = async (index) => {
role: allTeamMembers.value[index].role,
}
await useBaseFetch(
`team/${project.value.team}/members/${allTeamMembers.value[index].user.id}`,
{
method: 'PATCH',
body: data,
},
await client.labrinth.teams_v2.editMember(
project.value.team,
allTeamMembers.value[index].user.id,
data,
)
await updateMembers()
addNotification({
@@ -820,11 +804,8 @@ const transferOwnership = async (index) => {
startLoading()
try {
await useBaseFetch(`team/${project.value.team}/owner`, {
method: 'PATCH',
body: {
user_id: allTeamMembers.value[index].user.id,
},
await client.labrinth.teams_v2.transferOwnership(project.value.team, {
user_id: allTeamMembers.value[index].user.id,
})
addNotification({
title: 'Member ownership transferred',
@@ -848,32 +829,25 @@ async function updateOrgMember(index) {
try {
if (allOrgMembers.value[index].override && !allOrgMembers.value[index].oldOverride) {
await useBaseFetch(`team/${project.value.team}/members`, {
method: 'POST',
body: {
await client.labrinth.teams_v2.addMember(project.value.team, {
permissions: allOrgMembers.value[index].permissions,
role: allOrgMembers.value[index].role,
payouts_split: allOrgMembers.value[index].payouts_split,
user_id: allOrgMembers.value[index].user.id,
})
} else if (!allOrgMembers.value[index].override && allOrgMembers.value[index].oldOverride) {
await client.labrinth.teams_v2.removeMember(
project.value.team,
allOrgMembers.value[index].user.id,
)
} else {
await client.labrinth.teams_v2.editMember(
project.value.team,
allOrgMembers.value[index].user.id,
{
permissions: allOrgMembers.value[index].permissions,
role: allOrgMembers.value[index].role,
payouts_split: allOrgMembers.value[index].payouts_split,
user_id: allOrgMembers.value[index].user.id,
},
})
} else if (!allOrgMembers.value[index].override && allOrgMembers.value[index].oldOverride) {
await useBaseFetch(
`team/${project.value.team}/members/${allOrgMembers.value[index].user.id}`,
{
method: 'DELETE',
},
)
} else {
await useBaseFetch(
`team/${project.value.team}/members/${allOrgMembers.value[index].user.id}`,
{
method: 'PATCH',
body: {
permissions: allOrgMembers.value[index].permissions,
role: allOrgMembers.value[index].role,
payouts_split: allOrgMembers.value[index].payouts_split,
},
},
)
}

View File

@@ -82,6 +82,7 @@
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { PlusIcon, SearchIcon, XCircleIcon } from '@modrinth/assets'
import {
Accordion,
@@ -91,20 +92,20 @@ import {
Avatar,
ButtonStyled,
ConfirmModal,
injectModrinthClient,
injectNotificationManager,
StyledInput,
} from '@modrinth/ui'
import type { AffiliateLink, User } from '@modrinth/utils'
import type { User } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import { useBaseFetch } from '~/composables/fetch.js'
const client = injectModrinthClient()
const { handleError } = injectNotificationManager()
type UserGroup = {
user: User
affiliates: AffiliateLink[]
affiliates: Labrinth.Affiliate.Internal.AffiliateCode[]
}
const createModal = useTemplateRef<typeof AffiliateLinkCreateModal>('createModal')
@@ -116,8 +117,7 @@ const {
refetch,
} = useQuery({
queryKey: ['affiliate'],
queryFn: () =>
useBaseFetch('affiliate', { method: 'GET', internal: true }) as Promise<AffiliateLink[]>,
queryFn: () => client.labrinth.affiliate_internal.getAll(),
})
const filterQuery = ref('')
@@ -130,7 +130,9 @@ const userIds = computed(() => {
const ids = new Set<string>()
affiliateCodes.value.forEach((code) => {
ids.add(code.affiliate)
ids.add(code.created_by)
if (code.created_by) {
ids.add(code.created_by)
}
})
return Array.from(ids)
})
@@ -139,7 +141,7 @@ const { data: users } = useQuery({
queryKey: computed(() => ['users-bulk', userIds.value]),
queryFn: () => {
if (userIds.value.length === 0) return Promise.resolve([])
return useBaseFetch(`users?ids=${JSON.stringify(userIds.value)}`) as Promise<User[]>
return client.labrinth.users_v2.getMultiple(userIds.value)
},
})
@@ -189,7 +191,8 @@ const filteredGroupedAffiliates = computed(() => {
)
})
function getCreatedByUsername(createdBy: string): string {
function getCreatedByUsername(createdBy: string | null): string {
if (!createdBy) return 'Unknown'
const user = userMap.value.get(createdBy)
return user?.username || 'Unknown'
}
@@ -207,7 +210,7 @@ async function createAffiliateCode(data: { sourceName: string; username?: string
if (!user) {
try {
user = (await useBaseFetch(`user/${data.username}`)) as User
user = await client.labrinth.users_v2.get(data.username)
if (users.value) {
users.value.push(user)
@@ -218,19 +221,15 @@ async function createAffiliateCode(data: { sourceName: string; username?: string
}
}
await useBaseFetch('affiliate', {
method: 'PUT',
body: {
affiliate: user.id,
source_name: data.sourceName,
},
internal: true,
await client.labrinth.affiliate_internal.create({
affiliate: user.id,
source_name: data.sourceName,
})
await refetch()
createModal.value?.close()
} catch (err) {
handleError(err)
handleError(err as Error)
} finally {
creatingLink.value = false
}
@@ -239,7 +238,7 @@ async function createAffiliateCode(data: { sourceName: string; username?: string
const revokingAffiliateUsername = ref<string | null>(null)
const revokingAffiliateId = ref<string | null>(null)
function revokeAffiliateCode(affiliate: AffiliateLink) {
function revokeAffiliateCode(affiliate: Labrinth.Affiliate.Internal.AffiliateCode) {
const user = userMap.value.get(affiliate.affiliate)
revokingAffiliateUsername.value = user?.username || 'Unknown'
revokingAffiliateId.value = affiliate.id
@@ -252,10 +251,7 @@ async function confirmRevokeAffiliateCode() {
}
try {
await useBaseFetch(`affiliate/${revokingAffiliateId.value}`, {
method: 'DELETE',
internal: true,
})
await client.labrinth.affiliate_internal.delete(revokingAffiliateId.value)
await refetch()
revokeModal.value?.hide()

View File

@@ -328,6 +328,7 @@ import {
CopyCode,
defineMessages,
DropdownSelect,
injectModrinthClient,
injectNotificationManager,
NewModal,
StyledInput,
@@ -343,9 +344,9 @@ import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
import { useBaseFetch } from '~/composables/fetch.js'
const { addNotification } = injectNotificationManager()
const client = injectModrinthClient()
const formatPrice = useFormatPrice()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
@@ -374,7 +375,7 @@ const messages = defineMessages({
const { data: user, error: userError } = useQuery({
queryKey: ['user', route.params.id],
queryFn: () => useBaseFetch(`user/${route.params.id}`),
queryFn: () => client.labrinth.users_v2.get(route.params.id),
})
watch(userError, (error) => {
@@ -389,20 +390,14 @@ watch(userError, (error) => {
const { data: subscriptions } = useQuery({
queryKey: computed(() => ['billing', 'subscriptions', user.value?.id]),
queryFn: () =>
useBaseFetch(`billing/subscriptions?user_id=${user.value.id}`, {
internal: true,
}),
queryFn: () => client.labrinth.billing_internal.getSubscriptions(user.value.id),
enabled: computed(() => !!user.value?.id),
placeholderData: [],
})
const { data: charges, refetch: refreshCharges } = useQuery({
queryKey: computed(() => ['billing', 'payments', user.value?.id]),
queryFn: () =>
useBaseFetch(`billing/payments?user_id=${user.value.id}`, {
internal: true,
}),
queryFn: () => client.labrinth.billing_internal.getPayments(user.value.id),
enabled: computed(() => !!user.value?.id),
placeholderData: [],
})
@@ -463,15 +458,11 @@ async function applyCredit() {
crediting.value = true
try {
const daysParsed = Math.max(1, Math.floor(Number(creditDays.value) || 1))
await useBaseFetch('billing/credit', {
method: 'POST',
body: JSON.stringify({
subscription_ids: [selectedSubscription.value.id],
days: daysParsed,
send_email: creditSendEmail.value,
message: DEFAULT_CREDIT_EMAIL_MESSAGE,
}),
internal: true,
await client.labrinth.billing_internal.credit({
subscription_ids: [selectedSubscription.value.id],
days: daysParsed,
send_email: creditSendEmail.value,
message: DEFAULT_CREDIT_EMAIL_MESSAGE,
})
addNotification({
title: 'Credit applied',
@@ -501,11 +492,7 @@ async function refundCharge() {
? { type: 'none', unprovision: unprovision.value }
: { type: 'full', unprovision: unprovision.value }
await useBaseFetch(`billing/charge/${selectedCharge.value.id}/refund`, {
method: 'POST',
body: JSON.stringify(payload),
internal: true,
})
await client.labrinth.billing_internal.refundCharge(selectedCharge.value.id, payload)
await refreshCharges()
refundModal.value.hide()
} catch (err) {
@@ -521,12 +508,8 @@ async function refundCharge() {
async function modifyCharge() {
modifying.value = true
try {
await useBaseFetch(`billing/subscription/${selectedSubscription.value.id}`, {
method: 'PATCH',
body: JSON.stringify({
cancelled: cancel.value,
}),
internal: true,
await client.labrinth.billing_internal.editSubscription(selectedSubscription.value.id, {
cancelled: cancel.value,
})
addNotification({
title: 'Modifications made',

View File

@@ -86,6 +86,7 @@ import {
Button,
commonMessages,
defineMessages,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
normalizeChildren,
@@ -96,8 +97,8 @@ import { computed } from 'vue'
import { useAuth } from '@/composables/auth.js'
import { useScopes } from '@/composables/auth/scopes.ts'
import { useBaseFetch } from '@/composables/fetch.js'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -139,20 +140,16 @@ const scope = router.query?.scope || false
const state = router.query?.state || false
const getFlowIdAuthorization = async () => {
const query = {
const params = {
client_id: clientId,
redirect_uri: redirectUri,
scope,
}
if (state) {
query.state = state
params.state = state
}
const authorization = await useBaseFetch('oauth/authorize', {
method: 'GET',
internal: true,
query,
}) // This will contain the flow_id and oauth_client_id for accepting the oauth on behalf of the user
const authorization = await client.labrinth.oauth_internal.authorize(params)
if (typeof authorization === 'string') {
await navigateTo(authorization, {
@@ -175,21 +172,13 @@ const {
const { data: app } = useQuery({
queryKey: computed(() => ['oauth/app', clientId]),
queryFn: () =>
useBaseFetch('oauth/app/' + clientId, {
method: 'GET',
internal: true,
}),
queryFn: () => client.labrinth.oauth_internal.getApp(clientId),
enabled: computed(() => !!clientId),
})
const { data: createdBy } = useQuery({
queryKey: computed(() => ['user', app.value?.created_by]),
queryFn: () =>
useBaseFetch('user/' + app.value.created_by, {
method: 'GET',
apiVersion: 3,
}),
queryFn: () => client.labrinth.users_v2.get(app.value.created_by),
enabled: computed(() => !!app.value?.created_by),
})
@@ -199,12 +188,8 @@ const scopeDefinitions = computed(() =>
const onAuthorize = async () => {
try {
const res = await useBaseFetch('oauth/accept', {
method: 'POST',
internal: true,
body: {
flow: authorizationData.value.flow_id,
},
const res = await client.labrinth.oauth_internal.accept({
flow: authorizationData.value.flow_id,
})
if (typeof res === 'string') {
@@ -215,7 +200,7 @@ const onAuthorize = async () => {
}
throw new Error(formatMessage(messages.noRedirectUrlError))
} catch {
} catch (err) {
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
@@ -226,11 +211,8 @@ const onAuthorize = async () => {
const onReject = async () => {
try {
const res = await useBaseFetch('oauth/reject', {
method: 'POST',
body: {
flow: authorizationData.value.flow_id,
},
const res = await client.labrinth.oauth_internal.reject({
flow: authorizationData.value.flow_id,
})
if (typeof res === 'string') {
@@ -241,7 +223,7 @@ const onReject = async () => {
}
throw new Error(formatMessage(messages.noRedirectUrlError))
} catch {
} catch (err) {
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,

View File

@@ -69,6 +69,7 @@ import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
import {
commonMessages,
defineMessages,
injectModrinthClient,
injectNotificationManager,
StyledInput,
useVIntl,
@@ -77,6 +78,7 @@ import { useQuery } from '@tanstack/vue-query'
import HCaptcha from '@/components/ui/HCaptcha.vue'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -167,10 +169,10 @@ const { data: globals } = useQuery({
queryKey: ['auth-globals'],
queryFn: async () => {
try {
return await useBaseFetch('globals', { internal: true })
return await client.labrinth.globals_internal.get()
} catch (err) {
console.error('Error fetching globals:', err)
return { captcha_enabled: true }
return { captcha_enabled: true, tax_compliance_thresholds: {} }
}
},
})
@@ -181,12 +183,9 @@ const token = ref('')
async function recovery() {
startLoading()
try {
await useBaseFetch('auth/password/reset', {
method: 'POST',
body: {
username: email.value,
challenge: token.value,
},
await client.labrinth.auth_v2.resetPasswordBegin({
username: email.value,
challenge: token.value,
})
addNotification({
@@ -211,12 +210,9 @@ const confirmNewPassword = ref('')
async function changePassword() {
startLoading()
try {
await useBaseFetch('auth/password', {
method: 'PATCH',
body: {
new_password: newPassword.value,
flow: route.query.flow,
},
await client.labrinth.auth_v2.changePassword({
new_password: newPassword.value,
flow: route.query.flow,
})
addNotification({

View File

@@ -139,6 +139,7 @@ import {
import {
commonMessages,
defineMessages,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
StyledInput,
@@ -149,6 +150,7 @@ import { useQuery, useQueryClient } from '@tanstack/vue-query'
import HCaptcha from '@/components/ui/HCaptcha.vue'
import { getAuthUrl, getLauncherRedirectUrl } from '@/composables/auth.js'
const client = injectModrinthClient()
const queryClient = useQueryClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -211,10 +213,10 @@ const { data: globals } = useQuery({
queryKey: ['auth-globals'],
queryFn: async () => {
try {
return await useBaseFetch('globals', { internal: true })
return await client.labrinth.globals_internal.get()
} catch (err) {
console.error('Error fetching globals:', err)
return { captcha_enabled: true }
return { captcha_enabled: true, tax_compliance_thresholds: {} }
}
},
})
@@ -228,13 +230,10 @@ const flow = ref(route.query.flow)
async function beginPasswordSignIn() {
startLoading()
try {
const res = await useBaseFetch('auth/login', {
method: 'POST',
body: {
username: email.value,
password: password.value,
challenge: token.value,
},
const res = await client.labrinth.auth_v2.login({
username: email.value,
password: password.value,
challenge: token.value,
})
if (res.flow) {
@@ -257,12 +256,9 @@ const twoFactorCode = ref(null)
async function begin2FASignIn() {
startLoading()
try {
const res = await useBaseFetch('auth/login/2fa', {
method: 'POST',
body: {
flow: flow.value,
code: twoFactorCode.value ? twoFactorCode.value.toString() : twoFactorCode.value,
},
const res = await client.labrinth.auth_v2.login2FA({
flow: flow.value,
code: twoFactorCode.value ? twoFactorCode.value.toString() : twoFactorCode.value,
})
await finishSignIn(res.session)

View File

@@ -141,6 +141,7 @@ import {
Checkbox,
commonMessages,
defineMessages,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
StyledInput,
@@ -151,6 +152,7 @@ import { useQuery } from '@tanstack/vue-query'
import HCaptcha from '@/components/ui/HCaptcha.vue'
import { getAuthUrl } from '@/composables/auth.js'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -205,10 +207,10 @@ const { data: globals } = useQuery({
queryKey: ['auth-globals'],
queryFn: async () => {
try {
return await useBaseFetch('globals', { internal: true })
return await client.labrinth.globals_internal.get()
} catch (err) {
console.error('Error fetching globals:', err)
return { captcha_enabled: true }
return { captcha_enabled: true, tax_compliance_thresholds: {} }
}
},
})
@@ -235,15 +237,12 @@ async function createAccount() {
captcha.value?.reset()
}
const res = await useBaseFetch('auth/create', {
method: 'POST',
body: {
username: username.value,
password: password.value,
email: email.value,
challenge: token.value,
sign_up_newsletter: subscribe.value,
},
const res = await client.labrinth.auth_v2.createAccount({
username: username.value,
password: password.value,
email: email.value,
challenge: token.value,
sign_up_newsletter: subscribe.value,
})
await useAuth(res.session)

View File

@@ -418,7 +418,6 @@ import dayjs from 'dayjs'
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
import NavTabs from '~/components/ui/NavTabs.vue'
import { asEncodedJsonArray, fetchSegmented } from '~/utils/fetch-helpers.ts'
const { handleError } = injectNotificationManager()
const api = injectModrinthClient()
@@ -589,7 +588,7 @@ const refreshCollection = async () => {
// Query for creator (only for regular collections)
const { data: fetchedCreator, isPending: creatorIsPending } = useQuery({
queryKey: computed(() => ['user', collection.value?.user]),
queryFn: () => useBaseFetch(`user/${collection.value?.user}`),
queryFn: () => api.labrinth.users_v2.get(collection.value.user),
enabled: computed(() => !isFollowingCollection.value && !!collection.value?.user),
})
@@ -606,7 +605,7 @@ const {
} = useQuery({
queryKey: computed(() => ['user', auth.value.user?.id, 'follows']),
queryFn: async () => {
const projects = await useBaseFetch(`user/${auth.value.user.id}/follows`)
const projects = await api.labrinth.users_v2.getFollowedProjects(auth.value.user.id)
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
}
@@ -624,10 +623,16 @@ const {
} = useQuery({
queryKey: computed(() => ['projects', collection.value?.projects]),
queryFn: async () => {
const projects = await fetchSegmented(
collection.value.projects,
(ids) => `projects?ids=${asEncodedJsonArray(ids)}`,
const projectIds = collection.value.projects
const segmentSize = 800
const segments = []
for (let i = 0; i < projectIds.length; i += segmentSize) {
segments.push(projectIds.slice(i, i + segmentSize))
}
const results = await Promise.all(
segments.map((ids) => api.labrinth.projects_v2.getMultiple(ids)),
)
const projects = results.flat()
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
}

View File

@@ -57,6 +57,7 @@
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { PlusIcon, SearchIcon, XCircleIcon } from '@modrinth/assets'
import {
Admonition,
@@ -65,11 +66,11 @@ import {
ButtonStyled,
ConfirmModal,
defineMessages,
injectModrinthClient,
injectNotificationManager,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import type { AffiliateLink } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
const createModal = useTemplateRef<typeof AffiliateLinkCreateModal>('createModal')
@@ -77,6 +78,7 @@ const revokeModal = useTemplateRef<typeof ConfirmModal>('revokeModal')
const auth = await useAuth()
const client = injectModrinthClient()
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -87,41 +89,35 @@ const {
refetch,
} = useQuery({
queryKey: ['affiliate'],
queryFn: () =>
useBaseFetch('affiliate', { method: 'GET', internal: true }) as Promise<AffiliateLink[]>,
queryFn: () => client.labrinth.affiliate_internal.getAll(),
})
const filterQuery = ref('')
const creatingLink = ref(false)
const filteredAffiliates = computed(() =>
affiliateLinks
? affiliateLinks.value?.filter(
(link: AffiliateLink) =>
link.affiliate === auth.value?.user?.id &&
(filterQuery.value.trim()
? link.source_name.trim().toLowerCase().includes(filterQuery.value.trim().toLowerCase())
: true),
)
: [],
const filteredAffiliates = computed(
() =>
affiliateLinks.value?.filter(
(link: Labrinth.Affiliate.Internal.AffiliateCode) =>
link.affiliate === auth.value?.user?.id &&
(filterQuery.value.trim()
? link.source_name.trim().toLowerCase().includes(filterQuery.value.trim().toLowerCase())
: true),
) ?? [],
)
async function createAffiliateCode(data: { sourceName: string }) {
creatingLink.value = true
try {
await useBaseFetch('affiliate', {
method: 'PUT',
body: {
source_name: data.sourceName,
},
internal: true,
await client.labrinth.affiliate_internal.create({
source_name: data.sourceName,
})
await refetch()
createModal.value?.close()
} catch (err) {
handleError(err)
handleError(err as Error)
} finally {
creatingLink.value = false
}
@@ -130,7 +126,7 @@ async function createAffiliateCode(data: { sourceName: string }) {
const revokingTitle = ref<string | null>(null)
const revokingId = ref<string | null>(null)
function revokeAffiliateLink(affiliate: AffiliateLink) {
function revokeAffiliateLink(affiliate: Labrinth.Affiliate.Internal.AffiliateCode) {
revokingTitle.value = affiliate.source_name
revokingId.value = affiliate.id
revokeModal.value?.show()
@@ -142,10 +138,7 @@ async function confirmRevokeAffiliateLink() {
}
try {
await useBaseFetch(`affiliate/${revokingId.value}`, {
method: 'DELETE',
internal: true,
})
await client.labrinth.affiliate_internal.delete(revokingId.value)
await refetch()
revokeModal.value?.hide()

View File

@@ -5,6 +5,7 @@
</template>
<script setup>
import { injectModrinthClient } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue'
@@ -18,11 +19,12 @@ useHead({
})
const auth = await useAuth()
const client = injectModrinthClient()
const id = auth.value?.user?.id
const { data: projects } = useQuery({
queryKey: computed(() => ['user', id, 'projects']),
queryFn: () => useBaseFetch(`user/${id}/projects`),
queryFn: () => client.labrinth.users_v2.getProjects(id),
enabled: computed(() => !!id),
})
</script>

View File

@@ -155,6 +155,7 @@ import {
commonMessages,
defineMessages,
DropdownSelect,
injectModrinthClient,
StyledInput,
useCompactNumber,
useVIntl,
@@ -162,7 +163,6 @@ import {
import { useQuery } from '@tanstack/vue-query'
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
import { useBaseFetch } from '~/composables/fetch.js'
const { formatMessage } = useVIntl()
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
@@ -216,6 +216,7 @@ useHead({
const auth = await useAuth()
const user = await useUser()
const client = injectModrinthClient()
if (import.meta.client) {
await initUserFollows()
@@ -225,7 +226,7 @@ const filterQuery = ref('')
const { data: collections } = useQuery({
queryKey: ['user', auth.value.user.id, 'collections'],
queryFn: () => useBaseFetch(`user/${auth.value.user.id}/collections`, { apiVersion: 3 }),
queryFn: () => client.labrinth.users_v2.getCollections(auth.value.user.id),
})
const route = useNativeRoute()

View File

@@ -97,7 +97,7 @@
</template>
<script setup>
import { ChevronRightIcon, HistoryIcon } from '@modrinth/assets'
import { Avatar } from '@modrinth/ui'
import { Avatar, injectModrinthClient } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import NotificationItem from '~/components/ui/NotificationItem.vue'
@@ -108,10 +108,11 @@ useHead({
})
const auth = await useAuth()
const client = injectModrinthClient()
const { data: projects } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'projects']),
queryFn: async () => await useBaseFetch(`user/${auth.value?.user?.id}/projects`),
queryFn: () => client.labrinth.users_v2.getProjects(auth.value?.user?.id),
placeholderData: [],
})
@@ -125,12 +126,14 @@ const followersProjectCount = computed(
const { data, refetch } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'notifications']),
queryFn: async () => {
const notifications = await useBaseFetch(`user/${auth.value?.user?.id}/notifications`)
const notifications = await client.labrinth.notifications_v2.getUserNotifications(
auth.value?.user?.id,
)
const filteredNotifications = notifications.filter((notif) => !notif.read)
const slice = filteredNotifications.slice(0, 30)
return fetchExtraNotificationData(slice).then((notifications) => {
return fetchExtraNotificationData(client, slice).then((notifications) => {
notifications = groupNotifications(notifications).slice(0, 3)
return { notifications, extraNotifs: filteredNotifications.length - slice.length }
})

View File

@@ -57,7 +57,7 @@
</template>
<script setup>
import { CheckCheckIcon, HistoryIcon } from '@modrinth/assets'
import { Button, Chips, Pagination } from '@modrinth/ui'
import { Button, Chips, injectModrinthClient, Pagination } from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
@@ -73,6 +73,7 @@ useHead({
title: 'Notifications - Modrinth',
})
const client = injectModrinthClient()
const auth = await useAuth()
const route = useNativeRoute()
const router = useNativeRouter()
@@ -94,7 +95,9 @@ const { data, isPending, error, refetch } = useQuery({
queryFn: async () => {
const pageNum = page.value - 1
const showRead = history.value
const notifications = await useBaseFetch(`user/${auth.value?.user?.id}/notifications`)
const notifications = await client.labrinth.notifications_v2.getUserNotifications(
auth.value?.user?.id,
)
const typesInFeed = [
...new Set(notifications.filter((n) => showRead || !n.read).map((n) => n.type)),
@@ -108,6 +111,7 @@ const { data, isPending, error, refetch } = useQuery({
const pages = Math.max(1, Math.ceil(filtered.length / perPage.value))
return fetchExtraNotificationData(
client,
filtered.slice(pageNum * perPage.value, pageNum * perPage.value + perPage.value),
).then((notifs) => ({
notifications: notifs,
@@ -121,7 +125,7 @@ const { data, isPending, error, refetch } = useQuery({
})
const notifications = computed(() =>
data.value ? groupNotifications(data.value.notifications, history.value) : [],
data.value ? groupNotifications(data.value.notifications) : [],
)
const notifTypes = computed(() => data.value?.notifTypes || [])
@@ -139,7 +143,7 @@ async function readAll() {
...(n.grouped_notifs ? n.grouped_notifs.map((g) => g.id) : []),
])
await markAsRead(ids)
await markAsRead(client, ids)
await refetch()
}

View File

@@ -50,7 +50,7 @@
<script setup>
import { PlusIcon, UsersIcon } from '@modrinth/assets'
import { Avatar } from '@modrinth/ui'
import { Avatar, injectModrinthClient } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
@@ -59,14 +59,12 @@ import { useAuth } from '~/composables/auth.js'
const createOrgModal = ref(null)
const auth = await useAuth()
const client = injectModrinthClient()
const uid = computed(() => auth.value.user?.id || null)
const { data: orgs, error } = useQuery({
queryKey: computed(() => ['user', uid.value, 'organizations']),
queryFn: () =>
useBaseFetch('user/' + uid.value + '/organizations', {
apiVersion: 3,
}),
queryFn: () => client.labrinth.users_v2.getOrganizations(uid.value),
enabled: computed(() => !!uid.value),
})

View File

@@ -188,7 +188,7 @@
<div v-if="sortedPayouts.length > 0" class="flex flex-col gap-3 md:gap-4">
<RevenueTransaction
v-for="transaction in sortedPayouts.slice(0, 3)"
:key="transaction.id || transaction.created"
:key="('id' in transaction && transaction.id) || transaction.created"
:transaction="transaction"
@cancelled="refreshPayouts"
/>
@@ -260,13 +260,18 @@
<script setup lang="ts">
import { ArrowUpRightIcon, InProgressIcon, UnknownIcon } from '@modrinth/assets'
import { defineMessages, useFormatDateTime, useFormatMoney, useVIntl } from '@modrinth/ui'
import {
defineMessages,
injectModrinthClient,
useFormatDateTime,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import { Tooltip } from 'floating-vue'
import { useUserCountry } from '@/composables/country.ts'
import type { PayoutMethod } from '@/providers/creator-withdraw.ts'
import CreatorWithdrawModal from '~/components/ui/dashboard/CreatorWithdrawModal.vue'
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
@@ -276,20 +281,7 @@ const formatDate = useFormatDateTime({ dateStyle: 'medium' })
await useAuth()
// TODO: Deduplicate these types & interfaces in @modrinth/api-client PR.
type FormCompletionStatus = 'unknown' | 'unrequested' | 'unsigned' | 'tin-mismatch' | 'complete'
type UserBalanceResponse = {
available: number
withdrawn_lifetime: number
withdrawn_ytd: number
pending: number
// ISO 8601 date string -> amount
dates: Record<string, number>
// backend returns null when not applicable
requested_form_type: string | null
form_completion_status: FormCompletionStatus | null
}
const client = injectModrinthClient()
type RevenueBarSegment = {
key: string
@@ -359,26 +351,12 @@ const messages = defineMessages({
const { data: userBalance, refetch: refreshUserBalance } = useQuery({
queryKey: ['payout', 'balance'],
queryFn: async () => {
const response = (await useBaseFetch(`payout/balance`, {
apiVersion: 3,
})) as UserBalanceResponse
return {
...response,
available: Number(response.available),
withdrawn_lifetime: Number(response.withdrawn_lifetime),
withdrawn_ytd: Number(response.withdrawn_ytd),
pending: Number(response.pending),
}
},
queryFn: () => client.labrinth.payout_v3.getBalance(),
})
const { data: payouts, refetch: refreshPayouts } = useQuery({
queryKey: ['payout', 'history'],
queryFn: () =>
useBaseFetch(`payout/history`, {
apiVersion: 3,
}),
queryFn: () => client.labrinth.payout_v3.getHistory(),
})
const userCountry = useUserCountry()
@@ -389,10 +367,7 @@ const { data: preloadedPaymentMethods } = useQuery({
try {
return {
country: defaultCountry,
methods: (await useBaseFetch('payout/methods', {
apiVersion: 3,
query: { country: defaultCountry },
})) as PayoutMethod[],
methods: await client.labrinth.payout_v3.getMethods(defaultCountry),
}
} catch {
return null

View File

@@ -92,6 +92,7 @@ import {
Combobox,
defineMessages,
EmptyState,
injectModrinthClient,
useFormatDateTime,
useFormatMoney,
useVIntl,
@@ -111,6 +112,7 @@ const formatMonth = useFormatDateTime({
month: 'long',
})
const client = injectModrinthClient()
const generatedState = useGeneratedState()
useHead({
@@ -119,10 +121,7 @@ useHead({
const { data: transactions, refetch } = useQuery({
queryKey: ['payout', 'history'],
queryFn: () =>
useBaseFetch(`payout/history`, {
apiVersion: 3,
}),
queryFn: () => client.labrinth.payout_v3.getHistory(),
})
const allTransactions = computed(() => {

View File

@@ -10,8 +10,7 @@
ref="purchaseModal"
:publishable-key="config.public.stripePublishableKey"
:initiate-payment="
async (body) =>
await useBaseFetch('billing/payment', { internal: true, method: 'POST', body })
async (body) => await client.labrinth.billing_internal.initiatePayment(body)
"
:available-products="pyroProducts"
:on-error="handleError"
@@ -641,6 +640,7 @@ import {
ButtonStyled,
commonMessages,
defineMessages,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
ModrinthServersPurchaseModal,
@@ -651,16 +651,15 @@ import { monthsInInterval } from '@modrinth/ui/src/utils/billing.ts'
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'
import { useBaseFetch } from '@/composables/fetch.js'
import OptionGroup from '~/components/ui/OptionGroup.vue'
import LoaderIcon from '~/components/ui/servers/icons/LoaderIcon.vue'
import MedalPlanPromotion from '~/components/ui/servers/marketing/MedalPlanPromotion.vue'
import ServerPlanSelector from '~/components/ui/servers/marketing/ServerPlanSelector.vue'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
import { products } from '~/generated/state.json'
const route = useRoute()
const router = useRouter()
const client = injectModrinthClient()
const { setAffiliateCode, getAffiliateCode } = useAffiliates()
@@ -1017,7 +1016,7 @@ const { data: hasServers } = useQuery({
queryFn: async () => {
try {
if (!auth.value.user) return false
const response = await useServersFetch('servers')
const response = await client.archon.servers_v0.list()
return response.servers && response.servers.length > 0
} catch {
return false
@@ -1027,13 +1026,7 @@ const { data: hasServers } = useQuery({
})
function fetchStock(region, request) {
return useServersFetch(`stock?region=${region.shortcode}`, {
method: 'POST',
body: {
...request,
},
bypassAuth: true,
}).then((res) => res.available)
return client.archon.servers_v0.checkStock(region.shortcode, request).then((res) => res.available)
}
async function fetchCapacityStatuses(customProduct = null) {
@@ -1049,15 +1042,11 @@ async function fetchCapacityStatuses(customProduct = null) {
const capacityChecks = []
for (const product of productsToCheck) {
capacityChecks.push(
useServersFetch('stock', {
method: 'POST',
body: {
cpu: product.metadata.cpu,
memory_mb: product.metadata.ram,
swap_mb: product.metadata.swap,
storage_mb: product.metadata.storage,
},
bypassAuth: true,
client.archon.servers_v0.checkStockGlobal({
cpu: product.metadata.cpu,
memory_mb: product.metadata.ram,
swap_mb: product.metadata.swap,
storage_mb: product.metadata.storage,
}),
)
}
@@ -1129,8 +1118,8 @@ async function fetchPaymentData() {
if (!auth.value.user) return
try {
const [customerData, paymentMethodsData] = await Promise.all([
useBaseFetch('billing/customer', { internal: true }),
useBaseFetch('billing/payment_methods', { internal: true }),
client.labrinth.billing_internal.getCustomer(),
client.labrinth.billing_internal.getPaymentMethods(),
])
customer.value = customerData
paymentMethods.value = paymentMethodsData
@@ -1248,11 +1237,7 @@ const regions = ref([])
const regionPings = ref([])
function pingRegions() {
useServersFetch('regions', {
method: 'GET',
version: 1,
bypassAuth: true,
}).then((res) => {
client.archon.servers_v1.getRegions().then((res) => {
regions.value = res
regions.value.forEach((region) => {
runPingTest(region)

View File

@@ -118,7 +118,7 @@ import {
StyledInput,
Toggle,
} from '@modrinth/ui'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import Fuse from 'fuse.js'
import { computed, ref, watch } from 'vue'

View File

@@ -146,7 +146,7 @@
</thead>
<tbody>
<tr v-for="item in platformRevenueData" :key="item.time">
<td>{{ formatDate(dayjs.unix(item.time)) }}</td>
<td>{{ formatDate(dayjs.unix(item.time).toDate()) }}</td>
<td>{{ formatMoney(Number(item.revenue) + Number(item.creator_revenue)) }}</td>
<td>{{ formatMoney(Number(item.creator_revenue)) }}</td>
<td>{{ formatMoney(Number(item.revenue)) }}</td>
@@ -162,11 +162,12 @@
</template>
<script lang="ts" setup>
import { StyledInput, useFormatDateTime, useFormatMoney } from '@modrinth/ui'
import { injectModrinthClient, StyledInput, useFormatDateTime, useFormatMoney } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import { computed, ref } from 'vue'
const client = injectModrinthClient()
const formatMoney = useFormatMoney()
const formatDate = useFormatDateTime({
month: 'long',
@@ -191,10 +192,7 @@ const withdrawalDate = computed(() => endOfMonthDate.value.add(60, 'days'))
const { data: transparencyInformation } = useQuery({
queryKey: ['payout', 'platform_revenue'],
queryFn: () =>
useBaseFetch('payout/platform_revenue', {
apiVersion: 3,
}),
queryFn: () => client.labrinth.payouts_v3.getPlatformRevenue(),
})
const platformRevenue = (transparencyInformation.value as any)?.all_time

View File

@@ -1,10 +1,12 @@
<script setup lang="ts">
import { injectModrinthClient } from '@modrinth/ui'
import type { Report } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import ModerationReportCard from '~/components/ui/moderation/ModerationReportCard.vue'
import { enrichReportBatch } from '~/helpers/moderation.ts'
const client = injectModrinthClient()
const { params } = useRoute()
const reportId = params.id as string
@@ -12,7 +14,7 @@ const { data: report } = useQuery({
queryKey: computed(() => ['report', reportId]),
queryFn: async () => {
try {
const report = (await useBaseFetch(`report/${reportId}`, { apiVersion: 3 })) as Report
const report = (await client.labrinth.reports_v3.get(reportId)) as Report
const enrichedReport = (await enrichReportBatch([report]))[0]
return enrichedReport
} catch (error) {

View File

@@ -327,6 +327,7 @@ import {
commonMessages,
CopyCode,
DropdownSelect,
injectModrinthClient,
injectNotificationManager,
NewModal,
ProjectStatusBadge,
@@ -341,6 +342,7 @@ import OrganizationProjectTransferModal from '~/components/ui/OrganizationProjec
import { getProjectTypeForUrl } from '~/helpers/projects.js'
import { injectOrganizationContext } from '~/providers/organization-context.ts'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -350,7 +352,7 @@ const auth = await useAuth()
const { data: userProjects, refetch: refreshUserProjects } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'projects']),
queryFn: () => useBaseFetch(`user/${auth.value.user.id}/projects`),
queryFn: () => client.labrinth.users_v2.getProjects(auth.value.user.id),
enabled: computed(() => !!auth.value?.user?.id),
placeholderData: [],
})
@@ -366,11 +368,7 @@ watch(
const projects = userProjects.value.filter((project) => project.organization === null)
const teamIds = projects.map((project) => project?.team).filter((x) => x)
// Shape of teams is member[][]
const teams = await useBaseFetch(`teams?ids=${JSON.stringify(teamIds)}`, {
apiVersion: 3,
})
// for each team id, figure out if the user is a member, and is_owner. Then filter the projects to only include those that are owned by the user
const teams = await client.labrinth.teams_v3.getMultiple(teamIds)
const ownedTeamIds = teamIds.filter((_tid, i) => {
const team = teams?.[i]
if (!team) return false
@@ -379,19 +377,15 @@ watch(
})
const ownedProjects = projects.filter((project) => ownedTeamIds.includes(project.team))
usersOwnedProjects.value = ownedProjects
}, // watch options
},
{ immediate: true, deep: true },
)
const onProjectTransferSubmit = async (projects) => {
try {
for (const project of projects) {
await useBaseFetch(`organization/${organization.value.id}/projects`, {
method: 'POST',
body: JSON.stringify({
project_id: project.id,
}),
apiVersion: 3,
await client.labrinth.organizations_v3.addProject(organization.value.id, {
project_id: project.id,
})
}
@@ -488,17 +482,17 @@ const updateDescending = () => {
const onBulkEditLinks = async () => {
try {
const baseData = {
issues_url: editLinks.value.issues.clear ? null : editLinks.value.issues.val.trim(),
source_url: editLinks.value.source.clear ? null : editLinks.value.source.val.trim(),
wiki_url: editLinks.value.wiki.clear ? null : editLinks.value.wiki.val.trim(),
discord_url: editLinks.value.discord.clear ? null : editLinks.value.discord.val.trim(),
issues_url: editLinks.issues.clear ? null : editLinks.issues.val.trim(),
source_url: editLinks.source.clear ? null : editLinks.source.val.trim(),
wiki_url: editLinks.wiki.clear ? null : editLinks.wiki.val.trim(),
discord_url: editLinks.discord.clear ? null : editLinks.discord.val.trim(),
}
const filteredData = Object.fromEntries(Object.entries(baseData).filter(([, v]) => v !== ''))
await useBaseFetch(`projects?ids=${JSON.stringify(selectedProjects.value.map((x) => x.id))}`, {
method: 'PATCH',
body: JSON.stringify(filteredData),
})
await client.labrinth.projects_v2.bulkEdit(
selectedProjects.value.map((x) => x.id),
filteredData,
)
editLinksModal.value?.hide()
addNotification({
@@ -508,14 +502,14 @@ const onBulkEditLinks = async () => {
})
selectedProjects.value = []
editLinks.value.issues.val = ''
editLinks.value.source.val = ''
editLinks.value.wiki.val = ''
editLinks.value.discord.val = ''
editLinks.value.issues.clear = false
editLinks.value.source.clear = false
editLinks.value.wiki.clear = false
editLinks.value.discord.clear = false
editLinks.issues.val = ''
editLinks.source.val = ''
editLinks.wiki.val = ''
editLinks.discord.val = ''
editLinks.issues.clear = false
editLinks.source.clear = false
editLinks.wiki.clear = false
editLinks.discord.clear = false
} catch (e) {
addNotification({
title: 'An error occurred',

View File

@@ -253,6 +253,7 @@ import {
CopyCode,
defineMessages,
FileInput,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
normalizeChildren,
@@ -272,6 +273,7 @@ import {
useScopes,
} from '~/composables/auth/scopes.ts'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
const formatDate = useFormatDateTime()
@@ -494,10 +496,7 @@ const auth = await useAuth()
const { data: usersApps, refetch: refresh } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'oauth_apps']),
queryFn: () =>
useBaseFetch(`user/${auth.value.user.id}/oauth_apps`, {
apiVersion: 3,
}),
queryFn: () => client.labrinth.oauth_internal.getUserApps(auth.value.user.id),
enabled: computed(() => !!auth.value?.user?.id),
})
@@ -551,14 +550,7 @@ async function onImageSelection(files) {
const file = files[0]
const extFromType = file.type.split('/')[1]
await useBaseFetch('oauth/app/' + editingId.value + '/icon', {
method: 'PATCH',
internal: true,
body: file,
query: {
ext: extFromType,
},
})
await client.labrinth.oauth_internal.uploadAppIcon(editingId.value, file, extFromType).promise
await refresh()
@@ -579,15 +571,10 @@ async function createApp() {
startLoading()
loading.value = true
try {
const createdAppInfo = await useBaseFetch('oauth/app', {
method: 'POST',
internal: true,
body: {
name: name.value,
icon_url: icon.value,
max_scopes: Number(scopesVal.value), // JS is 52 bit for ints so we're good for now
redirect_uris: redirectUris.value,
},
const createdAppInfo = await client.labrinth.oauth_internal.createApp({
name: name.value,
max_scopes: Number(scopesVal.value),
redirect_uris: redirectUris.value,
})
createdApps.value.push(createdAppInfo)
@@ -637,7 +624,7 @@ async function editApp() {
const body = {
name: name.value,
max_scopes: Number(scopesVal.value), // JS is 52 bit for ints so we're good for now
max_scopes: Number(scopesVal.value),
redirect_uris: redirectUris.value,
}
@@ -653,11 +640,7 @@ async function editApp() {
body.icon_url = icon.value
}
await useBaseFetch('oauth/app/' + editingId.value, {
method: 'PATCH',
internal: true,
body,
})
await client.labrinth.oauth_internal.editApp(editingId.value, body)
await refresh()
setForm(null)
@@ -681,10 +664,7 @@ async function removeApp() {
if (!editingId.value) {
throw new Error('No editing id')
}
await useBaseFetch(`oauth/app/${editingId.value}`, {
internal: true,
method: 'DELETE',
})
await client.labrinth.oauth_internal.deleteApp(editingId.value)
await refresh()
editingId.value = null
} catch (err) {

View File

@@ -95,6 +95,7 @@ import {
Button,
commonSettingsMessages,
ConfirmModal,
injectModrinthClient,
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
@@ -102,6 +103,7 @@ import { useQuery } from '@tanstack/vue-query'
import { useScopes } from '~/composables/auth/scopes.ts'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -119,32 +121,19 @@ useHead({
const { data: usersApps, refetch: refresh } = useQuery({
queryKey: ['oauth', 'authorizations'],
queryFn: () =>
useBaseFetch(`oauth/authorizations`, {
internal: true,
}),
queryFn: () => client.labrinth.oauth_internal.getAuthorizations(),
})
const { data: appInformation } = useQuery({
queryKey: computed(() => ['oauth', 'apps', usersApps.value?.map((c) => c.app_id)]),
queryFn: () =>
useBaseFetch('oauth/apps', {
internal: true,
query: {
ids: JSON.stringify(usersApps.value.map((c) => c.app_id)),
},
}),
queryFn: () => client.labrinth.oauth_internal.getApps(usersApps.value.map((c) => c.app_id)),
enabled: computed(() => !!usersApps.value?.length),
})
const { data: appCreatorsInformation } = useQuery({
queryKey: computed(() => ['users', appInformation.value?.map((c) => c.created_by)]),
queryFn: () =>
useBaseFetch('users', {
query: {
ids: JSON.stringify(appInformation.value.map((c) => c.created_by)),
},
}),
client.labrinth.users_v2.getMultiple(appInformation.value.map((c) => c.created_by)),
enabled: computed(() => !!appInformation.value?.length),
})
@@ -165,13 +154,7 @@ const appInfoLookup = computed(() => {
async function revokeApp(id) {
try {
await useBaseFetch(`oauth/authorizations`, {
internal: true,
method: 'DELETE',
query: {
client_id: id,
},
})
await client.labrinth.oauth_internal.revokeAuthorization(id)
revokingId.value = null
await refresh()
} catch (err) {

View File

@@ -38,7 +38,13 @@
</div>
</template>
<script setup>
import { Badge, Breadcrumbs, useFormatDateTime, useFormatPrice } from '@modrinth/ui'
import {
Badge,
Breadcrumbs,
injectModrinthClient,
useFormatDateTime,
useFormatPrice,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { products } from '~/generated/state.json'
@@ -47,6 +53,8 @@ definePageMeta({
middleware: 'auth',
})
const client = injectModrinthClient()
const formatPrice = useFormatPrice()
const formatDate = useFormatDateTime({
year: 'numeric',
@@ -57,7 +65,7 @@ const formatDate = useFormatDateTime({
const { data: charges } = useQuery({
queryKey: ['billing', 'payments'],
queryFn: async () => {
const charges = await useBaseFetch('billing/payments', { internal: true })
const charges = await client.labrinth.billing_internal.getPayments()
return charges
.filter((charge) => charge.status !== 'open' && charge.status !== 'cancelled')
.map((charge) => {

View File

@@ -458,8 +458,7 @@
:country="country"
:publishable-key="config.public.stripePublishableKey"
:send-billing-request="
async (body) =>
await useBaseFetch('billing/payment', { internal: true, method: 'POST', body })
async (body) => await client.labrinth.billing_internal.initiatePayment(body)
"
:on-error="
(err) =>
@@ -613,6 +612,7 @@ import {
CopyCode,
defineMessages,
getPaymentMethodIcon,
injectModrinthClient,
injectNotificationManager,
OverflowMenu,
paymentMethodMessages,
@@ -626,13 +626,12 @@ import { calculateSavings, getCurrency } from '@modrinth/utils'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import { useBaseFetch } from '@/composables/fetch.js'
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
import ServersUpgradeModalWrapper from '~/components/ui/servers/ServersUpgradeModalWrapper.vue'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
import { products } from '~/generated/state.json'
const { addNotification, handleError } = injectNotificationManager()
const client = injectModrinthClient()
definePageMeta({
middleware: 'auth',
})
@@ -742,27 +741,27 @@ const queryClient = useQueryClient()
const { data: paymentMethods } = useQuery({
queryKey: ['billing', 'payment_methods'],
queryFn: () => useBaseFetch('billing/payment_methods', { internal: true }),
queryFn: () => client.labrinth.billing_internal.getPaymentMethods(),
})
const { data: charges } = useQuery({
queryKey: ['billing', 'payments'],
queryFn: () => useBaseFetch('billing/payments', { internal: true }),
queryFn: () => client.labrinth.billing_internal.getPayments(),
})
const { data: customer } = useQuery({
queryKey: ['billing', 'customer'],
queryFn: () => useBaseFetch('billing/customer', { internal: true }),
queryFn: () => client.labrinth.billing_internal.getCustomer(),
})
const { data: subscriptions } = useQuery({
queryKey: ['billing', 'subscriptions'],
queryFn: () => useBaseFetch('billing/subscriptions', { internal: true }),
queryFn: () => client.labrinth.billing_internal.getSubscriptions(),
})
const { data: productsData } = useQuery({
queryKey: ['billing', 'products'],
queryFn: () => useBaseFetch('billing/products', { internal: true }),
queryFn: () => client.labrinth.billing_internal.getProducts(),
})
const { data: serversData } = useQuery({
queryKey: ['servers'],
queryFn: () => useServersFetch('servers'),
queryFn: () => client.archon.servers_v0.list(),
})
const midasProduct = ref(products.find((x) => x.metadata?.type === 'midas'))
@@ -826,10 +825,7 @@ function addPaymentMethod() {
}
async function createSetupIntent() {
return await useBaseFetch('billing/payment_method', {
internal: true,
method: 'POST',
})
return await client.labrinth.billing_internal.addPaymentMethodFlow()
}
const removePaymentMethodIndex = ref()
@@ -844,12 +840,8 @@ async function switchMidasInterval(interval) {
changingInterval.value = true
startLoading()
try {
await useBaseFetch(`billing/subscription/${midasSubscription.value.id}`, {
internal: true,
method: 'PATCH',
body: {
interval,
},
await client.labrinth.billing_internal.editSubscription(midasSubscription.value.id, {
interval,
})
await refresh()
} catch (error) {
@@ -862,12 +854,8 @@ async function switchMidasInterval(interval) {
async function editPaymentMethod(index, primary) {
startLoading()
try {
await useBaseFetch(`billing/payment_method/${paymentMethods.value[index].id}`, {
internal: true,
method: 'PATCH',
data: {
primary,
},
await client.labrinth.billing_internal.editPaymentMethod(paymentMethods.value[index].id, {
primary,
})
await refresh()
} catch (err) {
@@ -883,10 +871,7 @@ async function editPaymentMethod(index, primary) {
async function removePaymentMethod(index) {
startLoading()
try {
await useBaseFetch(`billing/payment_method/${paymentMethods.value[index].id}`, {
internal: true,
method: 'DELETE',
})
await client.labrinth.billing_internal.removePaymentMethod(paymentMethods.value[index].id)
await refresh()
} catch (err) {
addNotification({
@@ -902,12 +887,8 @@ const cancelSubscriptionId = ref(null)
async function cancelSubscription(id, cancelled) {
startLoading()
try {
await useBaseFetch(`billing/subscription/${id}`, {
internal: true,
method: 'PATCH',
body: {
cancelled,
},
await client.labrinth.billing_internal.editSubscription(id, {
cancelled,
})
await refresh()
} catch (err) {
@@ -975,12 +956,8 @@ const showPyroUpgradeModal = (subscription) => {
const resubscribePyro = async (subscriptionId, wasSuspended) => {
try {
await useBaseFetch(`billing/subscription/${subscriptionId}`, {
internal: true,
method: 'PATCH',
body: {
cancelled: false,
},
await client.labrinth.billing_internal.editSubscription(subscriptionId, {
cancelled: false,
})
await refresh()
if (wasSuspended) {

View File

@@ -196,6 +196,7 @@ import {
ConfirmModal,
CopyCode,
defineMessages,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
StyledInput,
@@ -203,7 +204,7 @@ import {
useRelativeTime,
useVIntl,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import Modal from '~/components/ui/Modal.vue'
import {
@@ -215,6 +216,8 @@ import {
useScopes,
} from '~/composables/auth/scopes.ts'
const client = injectModrinthClient()
const queryClient = useQueryClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -327,9 +330,9 @@ const deletePatIndex = ref(null)
const loading = ref(false)
const { data: pats, refetch: refresh } = useQuery({
const { data: pats } = useQuery({
queryKey: ['pat'],
queryFn: () => useBaseFetch('pat'),
queryFn: () => client.labrinth.pats_v2.list(),
placeholderData: [],
})
const displayPats = computed(() => {
@@ -395,13 +398,10 @@ async function createPat() {
startLoading()
loading.value = true
try {
const res = await useBaseFetch('pat', {
method: 'POST',
body: {
name: name.value,
scopes: Number(scopesVal.value),
expires: data.$dayjs(expires.value).toISOString(),
},
const res = await client.labrinth.pats_v2.create({
name: name.value,
scopes: Number(scopesVal.value),
expires: data.$dayjs(expires.value).toISOString(),
})
pats.value.push(res)
patModal.value.hide()
@@ -420,15 +420,12 @@ async function editPat() {
startLoading()
loading.value = true
try {
await useBaseFetch(`pat/${editPatId.value}`, {
method: 'PATCH',
body: {
name: name.value,
scopes: Number(scopesVal.value),
expires: data.$dayjs(expires.value).toISOString(),
},
await client.labrinth.pats_v2.modify(editPatId.value, {
name: name.value,
scopes: Number(scopesVal.value),
expires: data.$dayjs(expires.value).toISOString(),
})
await refresh()
await queryClient.invalidateQueries({ queryKey: ['pat'] })
patModal.value.hide()
} catch (err) {
addNotification({
@@ -445,10 +442,8 @@ async function removePat(id) {
startLoading()
try {
pats.value = pats.value.filter((x) => x.id !== id)
await useBaseFetch(`pat/${id}`, {
method: 'DELETE',
})
await refresh()
await client.labrinth.pats_v2.delete(id)
await queryClient.invalidateQueries({ queryKey: ['pat'] })
} catch (err) {
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),

View File

@@ -47,17 +47,20 @@ import {
commonMessages,
commonSettingsMessages,
defineMessages,
injectModrinthClient,
injectNotificationManager,
useFormatDateTime,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
definePageMeta({
middleware: 'auth',
})
const client = injectModrinthClient()
const queryClient = useQueryClient()
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime()
@@ -102,19 +105,17 @@ useHead({
title: () => `${formatMessage(commonSettingsMessages.sessions)} - Modrinth`,
})
const { data: sessions, refetch: refresh } = useQuery({
const { data: sessions } = useQuery({
queryKey: ['session', 'list'],
queryFn: () => useBaseFetch('session/list'),
queryFn: () => client.labrinth.sessions_v2.list(),
})
async function revokeSession(id) {
startLoading()
try {
sessions.value = sessions.value.filter((x) => x.id !== id)
await useBaseFetch(`session/${id}`, {
method: 'DELETE',
})
await refresh()
await client.labrinth.sessions_v2.delete(id)
await queryClient.invalidateQueries({ queryKey: ['session', 'list'] })
} catch (err) {
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),

View File

@@ -1,3 +1,4 @@
import type { Labrinth } from '@modrinth/api-client'
import {
BadgeDollarSignIcon,
GiftIcon,
@@ -43,29 +44,7 @@ export type PaymentProvider = 'tremendous' | 'muralpay' | 'paypal' | 'venmo'
**/
export type PaymentMethod = 'gift_card' | 'paypal' | 'venmo' | 'bank' | 'crypto'
export interface PayoutMethod {
id: string
type: string
name: string
category?: string
image_url: string | null
image_logo_url: string | null
interval: {
standard: {
min: number
max: number
}
fixed?: {
values: number[]
}
}
config?: {
fiat?: string | null
blockchain?: string[]
}
currency_code?: string | null
exchange_rate?: number | null
}
export type PayoutMethod = Labrinth.Payout.v3.PayoutMethod
export interface PaymentOption {
value: string

View File

@@ -21,6 +21,16 @@ export function fetchSegmented<T>(
).then((results) => results.flat())
}
export function fetchSegmentedWith<TId, TResult>(
data: TId[],
fetchFn: (ids: TId[]) => Promise<TResult[]>,
segmentSize = 800,
): Promise<TResult[]> {
return Promise.all(segmentData(data, segmentSize).map((ids) => fetchFn(ids))).then((results) =>
results.flat(),
)
}
export function asEncodedJsonArray<T>(data: T[]): string {
return encodeURIComponent(JSON.stringify(data))
}

View File

@@ -41,7 +41,7 @@ export class ArchonServersV0Module extends AbstractModule {
/**
* Check stock availability for a region
* POST /modrinth/v0/stock
* POST /modrinth/v0/stock?region=:region
*/
public async checkStock(
region: string,
@@ -52,6 +52,23 @@ export class ArchonServersV0Module extends AbstractModule {
version: 'modrinth/v0',
method: 'POST',
body: request,
skipAuth: true,
})
}
/**
* Check stock availability (without region filter)
* POST /modrinth/v0/stock
*/
public async checkStockGlobal(
request: Archon.Servers.v0.StockRequest,
): Promise<Archon.Servers.v0.StockResponse> {
return this.client.request<Archon.Servers.v0.StockResponse>('/stock', {
api: 'archon',
version: 'modrinth/v0',
method: 'POST',
body: request,
skipAuth: true,
})
}

View File

@@ -39,6 +39,7 @@ export class ArchonServersV1Module extends AbstractModule {
api: 'archon',
version: 1,
method: 'GET',
skipAuth: true,
})
}

View File

@@ -10,15 +10,28 @@ import { ISO3166Module } from './iso3166'
import { KyrosContentV1Module } from './kyros/content/v1'
import { KyrosFilesV0Module } from './kyros/files/v0'
import { LabrinthVersionsV2Module, LabrinthVersionsV3Module } from './labrinth'
import { LabrinthAffiliateInternalModule } from './labrinth/affiliate/internal'
import { LabrinthAuthInternalModule } from './labrinth/auth/internal'
import { LabrinthAuthV2Module } from './labrinth/auth/v2'
import { LabrinthBillingInternalModule } from './labrinth/billing/internal'
import { LabrinthGlobalsInternalModule } from './labrinth/globals/internal'
import { LabrinthNotificationsV2Module } from './labrinth/notifications/v2'
import { LabrinthOAuthInternalModule } from './labrinth/oauth/internal'
import { LabrinthCollectionsModule } from './labrinth/collections'
import { LabrinthOrganizationsV3Module } from './labrinth/organizations/v3'
import { LabrinthPatsV2Module } from './labrinth/pats/v2'
import { LabrinthLimitsV3Module } from './labrinth/limits/v3'
import { LabrinthPayoutV3Module } from './labrinth/payout/v3'
import { LabrinthPayoutsV3Module } from './labrinth/payouts/v3'
import { LabrinthReportsV3Module } from './labrinth/reports/v3'
import { LabrinthProjectsV2Module } from './labrinth/projects/v2'
import { LabrinthProjectsV3Module } from './labrinth/projects/v3'
import { LabrinthServerPingInternalModule } from './labrinth/server-ping/internal'
import { LabrinthSessionsV2Module } from './labrinth/sessions/v2'
import { LabrinthStateModule } from './labrinth/state'
import { LabrinthTagsV2Module } from './labrinth/tags/v2'
import { LabrinthTeamsV2Module } from './labrinth/teams/v2'
import { LabrinthTeamsV3Module } from './labrinth/teams/v3'
import { LabrinthTechReviewInternalModule } from './labrinth/tech-review/internal'
import { LabrinthThreadsV3Module } from './labrinth/threads/v3'
import { LabrinthUsersV2Module } from './labrinth/users/v2'
@@ -48,15 +61,28 @@ export const MODULE_REGISTRY = {
launchermeta_manifest_v0: LauncherMetaManifestV0Module,
kyros_content_v1: KyrosContentV1Module,
kyros_files_v0: KyrosFilesV0Module,
labrinth_affiliate_internal: LabrinthAffiliateInternalModule,
labrinth_auth_internal: LabrinthAuthInternalModule,
labrinth_auth_v2: LabrinthAuthV2Module,
labrinth_billing_internal: LabrinthBillingInternalModule,
labrinth_collections: LabrinthCollectionsModule,
labrinth_globals_internal: LabrinthGlobalsInternalModule,
labrinth_notifications_v2: LabrinthNotificationsV2Module,
labrinth_oauth_internal: LabrinthOAuthInternalModule,
labrinth_organizations_v3: LabrinthOrganizationsV3Module,
labrinth_pats_v2: LabrinthPatsV2Module,
labrinth_limits_v3: LabrinthLimitsV3Module,
labrinth_payout_v3: LabrinthPayoutV3Module,
labrinth_payouts_v3: LabrinthPayoutsV3Module,
labrinth_projects_v2: LabrinthProjectsV2Module,
labrinth_projects_v3: LabrinthProjectsV3Module,
labrinth_reports_v3: LabrinthReportsV3Module,
labrinth_server_ping_internal: LabrinthServerPingInternalModule,
labrinth_sessions_v2: LabrinthSessionsV2Module,
labrinth_state: LabrinthStateModule,
labrinth_tags_v2: LabrinthTagsV2Module,
labrinth_teams_v2: LabrinthTeamsV2Module,
labrinth_teams_v3: LabrinthTeamsV3Module,
labrinth_tech_review_internal: LabrinthTechReviewInternalModule,
labrinth_threads_v3: LabrinthThreadsV3Module,
labrinth_users_v2: LabrinthUsersV2Module,

View File

@@ -0,0 +1,72 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthAffiliateInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_affiliate_internal'
}
/**
* Get all affiliate codes for the authenticated user (or all if admin)
* GET /_internal/affiliate
*/
public async getAll(): Promise<Labrinth.Affiliate.Internal.AffiliateCode[]> {
return this.client.request<Labrinth.Affiliate.Internal.AffiliateCode[]>('/affiliate', {
api: 'labrinth',
version: 'internal',
method: 'GET',
})
}
/**
* Create a new affiliate code
* PUT /_internal/affiliate
*/
public async create(
data: Labrinth.Affiliate.Internal.CreateRequest,
): Promise<Labrinth.Affiliate.Internal.AffiliateCode> {
return this.client.request<Labrinth.Affiliate.Internal.AffiliateCode>('/affiliate', {
api: 'labrinth',
version: 'internal',
method: 'PUT',
body: data,
})
}
/**
* Get a specific affiliate code by ID
* GET /_internal/affiliate/{id}
*/
public async get(id: string): Promise<Labrinth.Affiliate.Internal.AffiliateCode> {
return this.client.request<Labrinth.Affiliate.Internal.AffiliateCode>(`/affiliate/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'GET',
})
}
/**
* Delete an affiliate code
* DELETE /_internal/affiliate/{id}
*/
public async delete(id: string): Promise<void> {
return this.client.request<void>(`/affiliate/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'DELETE',
})
}
/**
* Update an affiliate code's source name
* PATCH /_internal/affiliate/{id}
*/
public async patch(id: string, data: Labrinth.Affiliate.Internal.PatchRequest): Promise<void> {
return this.client.request<void>(`/affiliate/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'PATCH',
body: data,
})
}
}

View File

@@ -0,0 +1,87 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthAuthV2Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_auth_v2'
}
/**
* Log in with a password
*
* Returns a session token on success, or a flow ID if 2FA is required.
*
* @param data - Login credentials and captcha challenge
* @returns Promise resolving to a login response with session or flow
*/
public async login(data: Labrinth.Auth.v2.LoginRequest): Promise<Labrinth.Auth.v2.LoginResponse> {
return this.client.request<Labrinth.Auth.v2.LoginResponse>(`/auth/login`, {
api: 'labrinth',
version: 2,
method: 'POST',
body: data,
})
}
/**
* Complete a 2FA login flow
*
* @param data - The 2FA code and flow ID
* @returns Promise resolving to a session response
*/
public async login2FA(
data: Labrinth.Auth.v2.Login2FARequest,
): Promise<Labrinth.Auth.v2.Login2FAResponse> {
return this.client.request<Labrinth.Auth.v2.Login2FAResponse>(`/auth/login/2fa`, {
api: 'labrinth',
version: 2,
method: 'POST',
body: data,
})
}
/**
* Create a new account with a password
*
* @param data - Account creation data
* @returns Promise resolving to a session response
*/
public async createAccount(
data: Labrinth.Auth.v2.CreateAccountRequest,
): Promise<Labrinth.Auth.v2.CreateAccountResponse> {
return this.client.request<Labrinth.Auth.v2.CreateAccountResponse>(`/auth/create`, {
api: 'labrinth',
version: 2,
method: 'POST',
body: data,
})
}
/**
* Begin a password reset flow by sending a recovery email
*
* @param data - The username/email and captcha challenge
*/
public async resetPasswordBegin(data: Labrinth.Auth.v2.ResetPasswordRequest): Promise<void> {
return this.client.request(`/auth/password/reset`, {
api: 'labrinth',
version: 2,
method: 'POST',
body: data,
})
}
/**
* Change a user's password (via reset flow or with old password)
*
* @param data - The password change data
*/
public async changePassword(data: Labrinth.Auth.v2.ChangePasswordRequest): Promise<void> {
return this.client.request(`/auth/password`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
body: data,
})
}
}

View File

@@ -0,0 +1,22 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthGlobalsInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_globals_internal'
}
/**
* Get configured global non-secret variables for this backend instance
*
* @returns Promise resolving to the global configuration
*/
public async get(): Promise<Labrinth.Globals.Internal.Globals> {
return this.client.request<Labrinth.Globals.Internal.Globals>(`/globals`, {
api: 'labrinth',
version: 'internal',
method: 'GET',
skipAuth: true,
})
}
}

View File

@@ -1,11 +1,20 @@
export * from './auth/internal'
export * from './auth/v2'
export * from './billing/internal'
export * from './collections'
export * from './globals/internal'
export * from './notifications/v2'
export * from './oauth/internal'
export * from './organizations/v3'
export * from './pats/v2'
export * from './limits/v3'
export * from './payout/v3'
export * from './payouts/v3'
export * from './projects/v2'
export * from './projects/v3'
export * from './reports/v3'
export * from './server-ping/internal'
export * from './sessions/v2'
export * from './state'
export * from './tech-review/internal'
export * from './threads/v3'

View File

@@ -0,0 +1,41 @@
import { AbstractModule } from '../../../core/abstract-module.js'
import type { Labrinth } from '../types'
export class LabrinthLimitsV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_limits_v3'
}
/**
* Get project creation limits for the authenticated user.
*/
public async getProjectLimits(): Promise<Labrinth.Limits.v3.UserLimits> {
return this.client.request<Labrinth.Limits.v3.UserLimits>('/limits/projects', {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
/**
* Get organization creation limits for the authenticated user.
*/
public async getOrganizationLimits(): Promise<Labrinth.Limits.v3.UserLimits> {
return this.client.request<Labrinth.Limits.v3.UserLimits>('/limits/organizations', {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
/**
* Get collection creation limits for the authenticated user.
*/
public async getCollectionLimits(): Promise<Labrinth.Limits.v3.UserLimits> {
return this.client.request<Labrinth.Limits.v3.UserLimits>('/limits/collections', {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
}

View File

@@ -0,0 +1,128 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthNotificationsV2Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_notifications_v2'
}
/**
* Get all notifications for a user
*
* @param userId - The user's ID
* @returns Promise resolving to the user's notifications
*
* @example
* ```typescript
* const notifications = await client.labrinth.notifications_v2.getUserNotifications('user123')
* ```
*/
public async getUserNotifications(
userId: string,
): Promise<Labrinth.Notifications.v2.Notification[]> {
return this.client.request<Labrinth.Notifications.v2.Notification[]>(
`/user/${userId}/notifications`,
{
api: 'labrinth',
version: 2,
method: 'GET',
},
)
}
/**
* Get multiple notifications by their IDs
*
* @param ids - Array of notification IDs
* @returns Promise resolving to an array of notifications
*
* @example
* ```typescript
* const notifications = await client.labrinth.notifications_v2.getMultiple(['id1', 'id2'])
* ```
*/
public async getMultiple(ids: string[]): Promise<Labrinth.Notifications.v2.Notification[]> {
return this.client.request<Labrinth.Notifications.v2.Notification[]>(
`/notifications?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 2,
method: 'GET',
},
)
}
/**
* Mark a single notification as read
*
* @param id - Notification ID
*
* @example
* ```typescript
* await client.labrinth.notifications_v2.markAsRead('notif123')
* ```
*/
public async markAsRead(id: string): Promise<void> {
return this.client.request(`/notification/${id}`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
})
}
/**
* Mark multiple notifications as read
*
* @param ids - Array of notification IDs to mark as read
*
* @example
* ```typescript
* await client.labrinth.notifications_v2.markMultipleAsRead(['id1', 'id2'])
* ```
*/
public async markMultipleAsRead(ids: string[]): Promise<void> {
return this.client.request(`/notifications`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
params: { ids: JSON.stringify([...new Set(ids)]) },
})
}
/**
* Delete a single notification
*
* @param id - Notification ID
*
* @example
* ```typescript
* await client.labrinth.notifications_v2.delete('notif123')
* ```
*/
public async delete(id: string): Promise<void> {
return this.client.request(`/notification/${id}`, {
api: 'labrinth',
version: 2,
method: 'DELETE',
})
}
/**
* Delete multiple notifications
*
* @param ids - Array of notification IDs to delete
*
* @example
* ```typescript
* await client.labrinth.notifications_v2.deleteMultiple(['id1', 'id2'])
* ```
*/
public async deleteMultiple(ids: string[]): Promise<void> {
return this.client.request(`/notifications`, {
api: 'labrinth',
version: 2,
method: 'DELETE',
params: { ids: JSON.stringify([...new Set(ids)]) },
})
}
}

View File

@@ -0,0 +1,208 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { UploadHandle } from '../../../types/upload'
import type { Labrinth } from '../types'
export class LabrinthOAuthInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_oauth_internal'
}
/**
* Get a user's OAuth applications
*
* @param userId - The user's ID
* @returns Promise resolving to an array of the user's OAuth clients
*/
public async getUserApps(userId: string): Promise<Labrinth.OAuth.Internal.OAuthClient[]> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClient[]>(
`/user/${userId}/oauth_apps`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
/**
* Get a single OAuth application by ID
*
* @param id - The OAuth client ID
* @returns Promise resolving to the OAuth client
*/
public async getApp(id: string): Promise<Labrinth.OAuth.Internal.OAuthClient> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClient>(`/oauth/app/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'GET',
})
}
/**
* Get multiple OAuth applications by their IDs
*
* @param ids - Array of OAuth client IDs
* @returns Promise resolving to an array of OAuth clients
*/
public async getApps(ids: string[]): Promise<Labrinth.OAuth.Internal.OAuthClient[]> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClient[]>(
`/oauth/apps?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
}
/**
* Create a new OAuth application
*
* @param data - The OAuth app creation data
* @returns Promise resolving to the created OAuth client with its client secret
*/
public async createApp(
data: Labrinth.OAuth.Internal.CreateOAuthAppRequest,
): Promise<Labrinth.OAuth.Internal.OAuthClientCreationResult> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClientCreationResult>(`/oauth/app`, {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
})
}
/**
* Edit an existing OAuth application
*
* @param id - The OAuth client ID
* @param data - The fields to update
*/
public async editApp(
id: string,
data: Labrinth.OAuth.Internal.EditOAuthAppRequest,
): Promise<void> {
return this.client.request(`/oauth/app/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'PATCH',
body: data,
})
}
/**
* Delete an OAuth application
*
* @param id - The OAuth client ID
*/
public async deleteApp(id: string): Promise<void> {
return this.client.request(`/oauth/app/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'DELETE',
})
}
/**
* Update the icon for an OAuth application
*
* @param id - The OAuth client ID
* @param file - The icon file
* @param ext - The file extension (e.g. 'png', 'jpeg')
* @returns UploadHandle for progress tracking and cancellation
*/
public uploadAppIcon(id: string, file: File | Blob, ext: string): UploadHandle<void> {
return this.client.upload<void>(`/oauth/app/${id}/icon`, {
api: 'labrinth',
version: 'internal',
file,
params: { ext },
})
}
/**
* Get the current user's OAuth authorizations
*
* @returns Promise resolving to an array of OAuth client authorizations
*/
public async getAuthorizations(): Promise<Labrinth.OAuth.Internal.OAuthClientAuthorization[]> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClientAuthorization[]>(
`/oauth/authorizations`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
}
/**
* Revoke an OAuth authorization for a client
*
* @param clientId - The OAuth client ID to revoke
*/
public async revokeAuthorization(clientId: string): Promise<void> {
return this.client.request(`/oauth/authorizations`, {
api: 'labrinth',
version: 'internal',
method: 'DELETE',
params: { client_id: clientId },
})
}
/**
* Initialize an OAuth authorization flow
*
* Returns either an OAuthClientAccessRequest (if user needs to approve)
* or a redirect URL string (if already authorized).
*
* @param params - The OAuth query parameters
* @returns Promise resolving to an access request object or redirect URL string
*/
public async authorize(params: {
client_id: string
redirect_uri: string
scope: string
state?: string
}): Promise<Labrinth.OAuth.Internal.OAuthClientAccessRequest | string> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClientAccessRequest | string>(
`/oauth/authorize`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
params: params as Record<string, string>,
},
)
}
/**
* Accept an OAuth authorization request
*
* @param data - The flow ID to accept
* @returns Promise resolving to a redirect URL string
*/
public async accept(data: Labrinth.OAuth.Internal.AcceptRejectRequest): Promise<string> {
return this.client.request<string>(`/oauth/accept`, {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
})
}
/**
* Reject an OAuth authorization request
*
* @param data - The flow ID to reject
* @returns Promise resolving to a redirect URL string
*/
public async reject(data: Labrinth.OAuth.Internal.AcceptRejectRequest): Promise<string> {
return this.client.request<string>(`/oauth/reject`, {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
})
}
}

View File

@@ -49,4 +49,74 @@ export class LabrinthOrganizationsV3Module extends AbstractModule {
},
)
}
/**
* Get multiple organizations by their IDs
*
* @param ids - Array of organization IDs
* @returns Promise resolving to an array of organizations
*
* @example
* ```typescript
* const orgs = await client.labrinth.organizations_v3.getMultiple(['id1', 'id2'])
* ```
*/
public async getMultiple(ids: string[]): Promise<Labrinth.Organizations.v3.Organization[]> {
return this.client.request<Labrinth.Organizations.v3.Organization[]>(
`/organizations?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
/**
* Add a project to an organization
*
* @param idOrSlug - Organization ID or slug
* @param request - The project to add
*
* @example
* ```typescript
* await client.labrinth.organizations_v3.addProject('my-org', { project_id: 'AABBCCDD' })
* ```
*/
public async addProject(
idOrSlug: string,
request: Labrinth.Organizations.v3.AddProjectRequest,
): Promise<void> {
return this.client.request(`/organization/${idOrSlug}/projects`, {
api: 'labrinth',
version: 3,
method: 'POST',
body: request,
})
}
/**
* Remove a project from an organization
*
* @param idOrSlug - Organization ID or slug
* @param projectId - Project ID to remove
* @param data - Request body containing the new_owner user ID
*
* @example
* ```typescript
* await client.labrinth.organizations_v3.removeProject('my-org', 'proj123', { new_owner: 'user456' })
* ```
*/
public async removeProject(
idOrSlug: string,
projectId: string,
data: Labrinth.Organizations.v3.RemoveProjectRequest,
): Promise<void> {
return this.client.request(`/organization/${idOrSlug}/projects/${projectId}`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
body: data,
})
}
}

View File

@@ -0,0 +1,66 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthPatsV2Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_pats_v2'
}
/**
* Get all personal access tokens for the authenticated user
*
* @returns Promise resolving to an array of PATs
*/
public async list(): Promise<Labrinth.Pats.v2.PersonalAccessToken[]> {
return this.client.request<Labrinth.Pats.v2.PersonalAccessToken[]>('/pat', {
api: 'labrinth',
version: 2,
method: 'GET',
})
}
/**
* Create a new personal access token
*
* @param data - The PAT creation request data
* @returns Promise resolving to the newly created PAT (includes access_token)
*/
public async create(
data: Labrinth.Pats.v2.CreatePatRequest,
): Promise<Labrinth.Pats.v2.PersonalAccessToken> {
return this.client.request<Labrinth.Pats.v2.PersonalAccessToken>('/pat', {
api: 'labrinth',
version: 2,
method: 'POST',
body: data,
})
}
/**
* Modify an existing personal access token
*
* @param id - The PAT ID
* @param data - The fields to update
*/
public async modify(id: string, data: Labrinth.Pats.v2.ModifyPatRequest): Promise<void> {
return this.client.request(`/pat/${id}`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
body: data,
})
}
/**
* Delete a personal access token
*
* @param id - The PAT ID
*/
public async delete(id: string): Promise<void> {
return this.client.request(`/pat/${id}`, {
api: 'labrinth',
version: 2,
method: 'DELETE',
})
}
}

View File

@@ -18,4 +18,45 @@ export class LabrinthPayoutV3Module extends AbstractModule {
method: 'GET',
})
}
/**
* Get the authenticated user's transaction history (withdrawals and payouts)
*
* @returns Promise resolving to an array of transaction items
*/
public async getHistory(): Promise<Labrinth.Payout.v3.TransactionItem[]> {
return this.client.request<Labrinth.Payout.v3.TransactionItem[]>('/payout/history', {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
/**
* Get available payout methods, optionally filtered by country
*
* @param country - Optional ISO country code to filter methods by supported countries
* @returns Promise resolving to an array of payout methods
*/
public async getMethods(country?: string): Promise<Labrinth.Payout.v3.PayoutMethod[]> {
return this.client.request<Labrinth.Payout.v3.PayoutMethod[]>('/payout/methods', {
api: 'labrinth',
version: 3,
method: 'GET',
params: country ? { country } : undefined,
})
}
/**
* Cancel a pending payout
*
* @param id - The payout ID to cancel
*/
public async cancel(id: string): Promise<void> {
return this.client.request<void>(`/payout/${id}`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
})
}
}

View File

@@ -0,0 +1,26 @@
import { AbstractModule } from '../../../core/abstract-module.js'
import type { Labrinth } from '../types'
export class LabrinthPayoutsV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_payouts_v3'
}
/**
* Get platform revenue data.
*
* @param params - Optional start/end date filters
* @returns Promise resolving to platform revenue data
*/
public async getPlatformRevenue(params?: {
start?: string
end?: string
}): Promise<Labrinth.Payouts.v3.RevenueResponse> {
return this.client.request<Labrinth.Payouts.v3.RevenueResponse>('/payout/platform_revenue', {
api: 'labrinth',
version: 3,
method: 'GET',
params: params as Record<string, string>,
})
}
}

View File

@@ -256,4 +256,31 @@ export class LabrinthProjectsV2Module extends AbstractModule {
params: { count: String(count) },
})
}
/**
* Bulk edit multiple projects at once
*
* @param ids - Array of project IDs to edit
* @param data - Fields to update across all specified projects
*
* @example
* ```typescript
* await client.labrinth.projects_v2.bulkEdit(['id1', 'id2'], {
* issues_url: 'https://github.com/issues',
* source_url: null,
* })
* ```
*/
public async bulkEdit(
ids: string[],
data: Labrinth.Projects.v2.BulkEditProjectRequest,
): Promise<void> {
return this.client.request(`/projects`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
params: { ids: JSON.stringify(ids) },
body: data,
})
}
}

View File

@@ -0,0 +1,141 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthReportsV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_reports_v3'
}
/**
* Get a report by ID
*
* @param id - Report ID
* @returns Promise resolving to the report data
*
* @example
* ```typescript
* const report = await client.labrinth.reports_v3.get('abc123')
* ```
*/
public async get(id: string): Promise<Labrinth.Reports.v3.Report> {
return this.client.request<Labrinth.Reports.v3.Report>(`/report/${id}`, {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
/**
* List reports for the current user (or all reports if moderator)
*
* @param params - Optional query parameters for count, offset, and whether to show all reports
* @returns Promise resolving to an array of reports
*
* @example
* ```typescript
* const reports = await client.labrinth.reports_v3.list({ count: 100 })
* ```
*/
public async list(
params?: Labrinth.Reports.v3.ListReportsParams,
): Promise<Labrinth.Reports.v3.Report[]> {
const queryParams: Record<string, string> = {}
if (params?.count != null) queryParams.count = String(params.count)
if (params?.offset != null) queryParams.offset = String(params.offset)
if (params?.all != null) queryParams.all = String(params.all)
return this.client.request<Labrinth.Reports.v3.Report[]>(`/report`, {
api: 'labrinth',
version: 3,
method: 'GET',
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
})
}
/**
* Get multiple reports by IDs
*
* @param ids - Array of report IDs
* @returns Promise resolving to an array of reports
*
* @example
* ```typescript
* const reports = await client.labrinth.reports_v3.getMultiple(['id1', 'id2'])
* ```
*/
public async getMultiple(ids: string[]): Promise<Labrinth.Reports.v3.Report[]> {
return this.client.request<Labrinth.Reports.v3.Report[]>(
`/reports?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
/**
* Create a new report
*
* @param data - Report creation data
* @returns Promise resolving to the created report
*
* @example
* ```typescript
* const report = await client.labrinth.reports_v3.create({
* report_type: 'spam',
* item_id: 'project123',
* item_type: 'project',
* body: 'This project is spam',
* })
* ```
*/
public async create(
data: Labrinth.Reports.v3.CreateReportRequest,
): Promise<Labrinth.Reports.v3.Report> {
return this.client.request<Labrinth.Reports.v3.Report>(`/report`, {
api: 'labrinth',
version: 3,
method: 'POST',
body: data,
})
}
/**
* Edit a report
*
* @param id - Report ID
* @param data - Report edit data
*
* @example
* ```typescript
* await client.labrinth.reports_v3.edit('abc123', { closed: true })
* ```
*/
public async edit(id: string, data: Labrinth.Reports.v3.EditReportRequest): Promise<void> {
return this.client.request(`/report/${id}`, {
api: 'labrinth',
version: 3,
method: 'PATCH',
body: data,
})
}
/**
* Delete a report (moderator only)
*
* @param id - Report ID
*
* @example
* ```typescript
* await client.labrinth.reports_v3.delete('abc123')
* ```
*/
public async delete(id: string): Promise<void> {
return this.client.request(`/report/${id}`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
})
}
}

View File

@@ -0,0 +1,34 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthSessionsV2Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_sessions_v2'
}
/**
* List all sessions for the authenticated user
*
* @returns Promise resolving to an array of sessions
*/
public async list(): Promise<Labrinth.Sessions.v2.Session[]> {
return this.client.request<Labrinth.Sessions.v2.Session[]>('/session/list', {
api: 'labrinth',
version: 2,
method: 'GET',
})
}
/**
* Delete (revoke) a session
*
* @param id - The session ID
*/
public async delete(id: string): Promise<void> {
return this.client.request(`/session/${id}`, {
api: 'labrinth',
version: 2,
method: 'DELETE',
})
}
}

View File

@@ -0,0 +1,29 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthTagsV2Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_tags_v2'
}
/**
* Get license text by SPDX identifier
*
* @param licenseId - SPDX license identifier (e.g., 'MIT', 'Apache-2.0')
* @returns Promise resolving to the license title and body text
*
* @example
* ```typescript
* const license = await client.labrinth.tags_v2.getLicenseText('MIT')
* console.log(license.title) // "MIT License"
* console.log(license.body) // full license text
* ```
*/
public async getLicenseText(licenseId: string): Promise<Labrinth.Tags.v2.LicenseText> {
return this.client.request<Labrinth.Tags.v2.LicenseText>(`/tag/license/${licenseId}`, {
api: 'labrinth',
version: 2,
method: 'GET',
})
}
}

View File

@@ -0,0 +1,101 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthTeamsV2Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_teams_v2'
}
/**
* Add a member to a team
*
* @param teamId - Team ID
* @param data - New member data including user_id
*
* @example
* ```typescript
* await client.labrinth.teams_v2.addMember('team123', { user_id: 'user456' })
* ```
*/
public async addMember(
teamId: string,
data: Labrinth.Teams.v2.AddTeamMemberRequest,
): Promise<void> {
return this.client.request(`/team/${teamId}/members`, {
api: 'labrinth',
version: 2,
method: 'POST',
body: data,
})
}
/**
* Edit a team member
*
* @param teamId - Team ID
* @param userId - User ID of the member to edit
* @param data - Member update data
*
* @example
* ```typescript
* await client.labrinth.teams_v2.editMember('team123', 'user456', {
* role: 'Developer',
* permissions: 0b111,
* })
* ```
*/
public async editMember(
teamId: string,
userId: string,
data: Labrinth.Teams.v2.EditTeamMemberRequest,
): Promise<void> {
return this.client.request(`/team/${teamId}/members/${userId}`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
body: data,
})
}
/**
* Remove a member from a team
*
* @param teamId - Team ID
* @param userId - User ID of the member to remove
*
* @example
* ```typescript
* await client.labrinth.teams_v2.removeMember('team123', 'user456')
* ```
*/
public async removeMember(teamId: string, userId: string): Promise<void> {
return this.client.request(`/team/${teamId}/members/${userId}`, {
api: 'labrinth',
version: 2,
method: 'DELETE',
})
}
/**
* Transfer team ownership to another member
*
* @param teamId - Team ID
* @param data - Transfer data including the new owner's user_id
*
* @example
* ```typescript
* await client.labrinth.teams_v2.transferOwnership('team123', { user_id: 'user456' })
* ```
*/
public async transferOwnership(
teamId: string,
data: Labrinth.Teams.v2.TransferOwnershipRequest,
): Promise<void> {
return this.client.request(`/team/${teamId}/owner`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
body: data,
})
}
}

View File

@@ -0,0 +1,31 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthTeamsV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_teams_v3'
}
/**
* Get multiple teams by their IDs
*
* @param ids - Array of team IDs
* @returns Promise resolving to an array of team member arrays (one per team)
*
* @example
* ```typescript
* const teams = await client.labrinth.teams_v3.getMultiple(['team1', 'team2'])
* // teams[0] = members of team1, teams[1] = members of team2
* ```
*/
public async getMultiple(ids: string[]): Promise<Labrinth.Projects.v3.TeamMember[][]> {
return this.client.request<Labrinth.Projects.v3.TeamMember[][]>(
`/teams?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
}

View File

@@ -26,6 +26,28 @@ export class LabrinthThreadsV3Module extends AbstractModule {
})
}
/**
* Get multiple threads by IDs (v3)
*
* @param ids - Array of thread IDs
* @returns Promise resolving to an array of threads
*
* @example
* ```typescript
* const threads = await client.labrinth.threads_v3.getMultiple(['id1', 'id2'])
* ```
*/
public async getMultiple(ids: string[]): Promise<Labrinth.Threads.v3.Thread[]> {
return this.client.request<Labrinth.Threads.v3.Thread[]>(
`/threads?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
/**
* Send a message to a thread (v3)
*

View File

@@ -158,6 +158,83 @@ export namespace Labrinth {
requested_form_type: string | null
form_completion_status: string | null
}
export type PayoutStatus =
| 'success'
| 'in-transit'
| 'cancelled'
| 'cancelling'
| 'failed'
| 'unknown'
export type PayoutMethodType = 'venmo' | 'paypal' | 'tremendous' | 'muralpay'
export type PayoutSource = 'creator_rewards' | 'affilites'
export type TransactionItem =
| {
type: 'withdrawal'
id: string
status: PayoutStatus
created: string
amount: number
fee: number | null
method_type: PayoutMethodType | null
method_id: string | null
method_address: string | null
}
| {
type: 'payout_available'
created: string
payout_source: PayoutSource
amount: number
}
export type WithdrawalFees = {
net_usd: number
fee: number
exchange_rate: number | null
}
export type PayoutDecimal = number
export type PayoutInterval = {
standard?: { min: number; max: number }
fixed?: { values: PayoutDecimal[] }
}
export type PayoutMethod = {
id: string
type: PayoutMethodType
name: string
category: string | null
image_url: string | null
image_logo_url: string | null
interval: PayoutInterval
currency_code: string | null
exchange_rate: number | null
}
}
}
export namespace Affiliate {
export namespace Internal {
export type AffiliateCode = {
id: string
created_at: string | null
created_by: string | null
affiliate: string
source_name: string
}
export type CreateRequest = {
affiliate?: string
source_name: string
}
export type PatchRequest = {
source_name: string
}
}
}
@@ -167,6 +244,123 @@ export namespace Labrinth {
subscribed: boolean
}
}
export namespace v2 {
export type LoginRequest = {
username: string
password: string
challenge: string
}
export type LoginResponse = {
session?: string
flow?: string
}
export type Login2FARequest = {
code: string
flow: string
}
export type Login2FAResponse = {
session: string
}
export type CreateAccountRequest = {
username: string
password: string
email: string
challenge: string
sign_up_newsletter?: boolean
}
export type CreateAccountResponse = {
session: string
}
export type ResetPasswordRequest = {
username: string
challenge: string
}
export type ChangePasswordRequest = {
flow?: string
old_password?: string
new_password?: string
}
}
}
export namespace Globals {
export namespace Internal {
export type Globals = {
tax_compliance_thresholds: Record<string, number>
captcha_enabled: boolean
}
}
}
export namespace OAuth {
export namespace Internal {
export type OAuthClientAccessRequest = {
flow_id: string
client_id: string
client_name: string
client_icon: string | null
requested_scopes: number
}
export type AcceptRejectRequest = {
flow: string
}
export type OAuthRedirectUri = {
id: string
client_id: string
uri: string
}
export type OAuthClient = {
id: string
name: string
icon_url: string | null
max_scopes: number
redirect_uris: OAuthRedirectUri[]
created_by: string
created: string
url: string | null
description: string | null
}
export type OAuthClientCreationResult = OAuthClient & {
client_secret: string
}
export type OAuthClientAuthorization = {
id: string
app_id: string
user_id: string
scopes: number
created: string
}
export type CreateOAuthAppRequest = {
name: string
max_scopes: number
redirect_uris: string[]
url?: string
description?: string
}
export type EditOAuthAppRequest = {
name?: string
max_scopes?: number
redirect_uris?: string[]
url?: string | null
description?: string | null
icon_url?: string
}
}
}
export namespace Projects {
@@ -318,6 +512,22 @@ export namespace Labrinth {
projects: Project[]
versions: Labrinth.Versions.v2.Version[]
}
export type BulkEditProjectRequest = {
categories?: string[]
add_categories?: string[]
remove_categories?: string[]
additional_categories?: string[]
add_additional_categories?: string[]
remove_additional_categories?: string[]
donation_urls?: DonationLink[]
add_donation_urls?: DonationLink[]
remove_donation_urls?: DonationLink[]
issues_url?: string | null
source_url?: string | null
wiki_url?: string | null
discord_url?: string | null
}
}
export namespace v3 {
@@ -882,6 +1092,36 @@ export namespace Labrinth {
short: string
name: string
}
export type LicenseText = {
title: string
body: string
}
}
}
export namespace Teams {
export namespace v2 {
export type AddTeamMemberRequest = {
user_id: string
role?: string
permissions?: number
organization_permissions?: number | null
payouts_split?: number
ordering?: number
}
export type EditTeamMemberRequest = {
permissions?: number
organization_permissions?: number | null
role?: string
payouts_split?: number
ordering?: number
}
export type TransferOwnershipRequest = {
user_id: string
}
}
}
@@ -1017,6 +1257,106 @@ export namespace Labrinth {
}
}
export namespace Reports {
export namespace v3 {
export type ItemType = 'project' | 'version' | 'user' | 'unknown'
export type Report = {
id: string
report_type: string
item_id: string
item_type: ItemType
reporter: string
body: string
created: string
closed: boolean
thread_id: string
}
export type CreateReportRequest = {
report_type: string
item_id: string
item_type: ItemType
body: string
uploaded_images?: string[]
}
export type EditReportRequest = {
body?: string
closed?: boolean
}
export type ListReportsParams = {
count?: number
offset?: number
all?: boolean
}
}
}
export namespace Notifications {
export namespace v2 {
export type NotificationAction = {
title: string
action_route: [string, string]
}
export type NotificationBody = {
type: string
project_id?: string
version_id?: string
report_id?: string
thread_id?: string
message_id?: string
invited_by?: string
organization_id?: string
team_id?: string
role?: string
old_status?: string
new_status?: string
[key: string]: unknown
}
export type Notification = {
id: string
user_id: string
type: string | null
title: string
text: string
link: string
read: boolean
created: string
actions: NotificationAction[]
body: NotificationBody
}
}
}
export namespace Payouts {
export namespace v3 {
export type RevenueData = {
time: number
revenue: string
creator_revenue: string
}
export type RevenueResponse = {
all_time: string
all_time_available: string
data: RevenueData[]
}
}
}
export namespace Limits {
export namespace v3 {
export type UserLimits = {
current: number
max: number
}
}
}
export namespace Collections {
export type CollectionStatus = 'listed' | 'unlisted' | 'private' | 'rejected' | 'unknown'
@@ -1267,4 +1607,52 @@ export namespace Labrinth {
}
}
}
export namespace Pats {
export namespace v2 {
export type PersonalAccessToken = {
id: string
name: string
access_token: string | null
scopes: number
user_id: string
created: string
expires: string
last_used: string | null
}
export type CreatePatRequest = {
scopes: number
name: string
expires: string
}
export type ModifyPatRequest = {
scopes?: number
name?: string
expires?: string
}
}
}
export namespace Sessions {
export namespace v2 {
export type Session = {
id: string
session: string | null
user_id: string
created: string
last_login: string
expires: string
refresh_expires: string
os: string | null
platform: string | null
user_agent: string
city: string | null
country: string | null
ip: string
current: boolean
}
}
}
}

View File

@@ -112,6 +112,49 @@ export class LabrinthUsersV2Module extends AbstractModule {
)
}
/**
* Get a user's notifications
*
* @param idOrUsername - The user's ID or username
* @returns Promise resolving to an array of the user's notifications
*
* @example
* ```typescript
* const notifications = await client.labrinth.users_v2.getNotifications('my_user')
* ```
*/
public async getNotifications(
idOrUsername: string,
): Promise<Labrinth.Notifications.v2.Notification[]> {
return this.client.request<Labrinth.Notifications.v2.Notification[]>(
`/user/${idOrUsername}/notifications`,
{
api: 'labrinth',
version: 2,
method: 'GET',
},
)
}
/**
* Get projects a user follows
*
* @param idOrUsername - The user's ID or username
* @returns Promise resolving to an array of followed projects
*
* @example
* ```typescript
* const projects = await client.labrinth.users_v2.getFollowedProjects('my_user')
* ```
*/
public async getFollowedProjects(idOrUsername: string): Promise<Labrinth.Projects.v2.Project[]> {
return this.client.request<Labrinth.Projects.v2.Project[]>(`/user/${idOrUsername}/follows`, {
api: 'labrinth',
version: 2,
method: 'GET',
})
}
/**
* Update a user
*

View File

@@ -31,15 +31,17 @@
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { AffiliateIcon, XCircleIcon } from '@modrinth/assets'
import type { AffiliateLink } from '@modrinth/utils'
import { defineMessages, useVIntl } from '../../composables/i18n'
import { AutoBrandIcon, ButtonStyled, CopyCode } from '../index'
type AffiliateCode = Labrinth.Affiliate.Internal.AffiliateCode
withDefaults(
defineProps<{
affiliate: AffiliateLink
affiliate: AffiliateCode
showRevoke?: boolean
createdBy?: string
}>(),
@@ -50,7 +52,7 @@ withDefaults(
)
const emit = defineEmits<{
(e: 'revoke', affiliate: AffiliateLink): void
(e: 'revoke', affiliate: AffiliateCode): void
}>()
const { formatMessage } = useVIntl()