Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions apps/frontend/src/components/ui/create/CreateLimitAlert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@
import { MessageIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, defineMessages, 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 { formatMessage } = useVIntl()

const messages = defineMessages({
Expand Down Expand Up @@ -121,10 +124,10 @@ const apiEndpoint = computed(() => {
}
})

const { data: limits } = await useAsyncData<UserLimits | undefined>(
`limits-${props.type}`,
() => useBaseFetch(apiEndpoint.value, { apiVersion: 3 }) as Promise<UserLimits>,
)
const { data: limits } = useQuery({
queryKey: computed(() => ['limits', props.type]),
queryFn: () => useBaseFetch(apiEndpoint.value, { apiVersion: 3 }) as Promise<UserLimits>,
})

const typeName = computed<{ singular: string; plural: string }>(() => {
switch (props.type) {
Expand Down
130 changes: 75 additions & 55 deletions apps/frontend/src/components/ui/report/ReportView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
</div>
</template>
<script setup>
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({
Expand All @@ -41,74 +45,90 @@ const props = defineProps({
},
})

const report = ref(null)
const queryClient = useQueryClient()

await fetchReport().then((result) => {
report.value = result
// 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
},
})

const { data: rawThread } = await useAsyncData(`thread/${report.value.thread_id}`, () =>
useBaseFetch(`thread/${report.value.thread_id}`),
)
const thread = computed(() => addReportMessage(rawThread.value, report.value))
// Compute user IDs needed
const userIds = computed(() => {
if (!rawReport.value) return []
const ids = [rawReport.value.reporter]
if (rawReport.value.item_type === 'user') {
ids.push(rawReport.value.item_id)
}
return ids
})

async function updateThread(newThread) {
rawThread.value = newThread
report.value = await fetchReport()
}
// Fetch users
const { data: users } = useQuery({
queryKey: computed(() => ['users', userIds.value]),
queryFn: () => useBaseFetch(`users?ids=${encodeURIComponent(JSON.stringify(userIds.value))}`),
enabled: computed(() => userIds.value.length > 0),
})

async function fetchReport() {
const { data: rawReport } = await useAsyncData(`report/${props.reportId}`, () =>
useBaseFetch(`report/${props.reportId}`),
)
rawReport.value.item_id = rawReport.value.item_id.replace(/"/g, '')
// Version ID if applicable
const versionId = computed(() =>
rawReport.value?.item_type === 'version' ? rawReport.value.item_id : null,
)

const userIds = []
userIds.push(rawReport.value.reporter)
if (rawReport.value.item_type === 'user') {
userIds.push(rawReport.value.item_id)
}
// Fetch version
const { data: version } = useQuery({
queryKey: computed(() => ['version', versionId.value]),
queryFn: () => useBaseFetch(`version/${versionId.value}`),
enabled: computed(() => !!versionId.value),
})

const versionId = rawReport.value.item_type === 'version' ? rawReport.value.item_id : null
// Project ID
const projectId = computed(() => {
if (version.value) return version.value.project_id
if (rawReport.value?.item_type === 'project') return rawReport.value.item_id
return null
})

let users = []
if (userIds.length > 0) {
const { data: usersVal } = await useAsyncData(`users?ids=${JSON.stringify(userIds)}`, () =>
useBaseFetch(`users?ids=${encodeURIComponent(JSON.stringify(userIds))}`),
)
users = usersVal.value
}
// Fetch project
const { data: project } = useQuery({
queryKey: computed(() => ['project', projectId.value]),
queryFn: () => useBaseFetch(`project/${projectId.value}`),
enabled: computed(() => !!projectId.value),
})

let version = null
if (versionId) {
const { data: versionVal } = await useAsyncData(`version/${versionId}`, () =>
useBaseFetch(`version/${versionId}`),
)
version = versionVal.value
// Assemble the full report object
const report = computed(() => {
if (!rawReport.value) return null
return {
...rawReport.value,
project: project.value ?? null,
version: version.value ?? null,
reporterUser: (users.value || []).find((user) => user.id === rawReport.value.reporter),
user:
rawReport.value.item_type === 'user'
? (users.value || []).find((user) => user.id === rawReport.value.item_id)
: undefined,
}
})

const projectId = version
? version.project_id
: rawReport.value.item_type === 'project'
? rawReport.value.item_id
: null
// Fetch thread
const { data: rawThread } = useQuery({
queryKey: computed(() => ['thread', report.value?.thread_id]),
queryFn: () => useBaseFetch(`thread/${report.value.thread_id}`),
enabled: computed(() => !!report.value?.thread_id),
})

let project = null
if (projectId) {
const { data: projectVal } = await useAsyncData(`project/${projectId}`, () =>
useBaseFetch(`project/${projectId}`),
)
project = projectVal.value
}
const thread = computed(() =>
rawThread.value && report.value ? addReportMessage(rawThread.value, report.value) : null,
)

const reportData = rawReport.value
reportData.project = project
reportData.version = version
reportData.reporterUser = users.find((user) => user.id === rawReport.value.reporter)
if (rawReport.value.item_type === 'user') {
reportData.user = users.find((user) => user.id === rawReport.value.item_id)
}
return reportData
async function updateThread(newThread) {
queryClient.setQueryData(['thread', report.value?.thread_id], newThread)
await queryClient.invalidateQueries({ queryKey: ['report', props.reportId] })
}
</script>
<style lang="scss" scoped>
Expand Down
144 changes: 90 additions & 54 deletions apps/frontend/src/components/ui/report/ReportsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
</template>
<script setup>
import { Chips } 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'

Expand All @@ -43,77 +46,110 @@ const props = defineProps({

const viewMode = ref('open')
const reasonFilter = ref('All')
const reports = ref([])

const MAX_REPORTS = 1500

let { data: rawReports } = await useAsyncData('report', () =>
useBaseFetch(`report?count=${MAX_REPORTS}`),
)

rawReports = rawReports.value.map((report) => {
report.item_id = report.item_id.replace(/"/g, '')
return report
const { data: rawReportsData } = useQuery({
queryKey: ['reports', MAX_REPORTS],
queryFn: () => useBaseFetch(`report?count=${MAX_REPORTS}`),
placeholderData: [],
})

const reporterUsers = rawReports.map((report) => report.reporter)
const reportedUsers = rawReports
.filter((report) => report.item_type === 'user')
.map((report) => report.item_id)
const versionReports = rawReports.filter((report) => report.item_type === 'version')
const versionIds = [...new Set(versionReports.map((report) => report.item_id))]
const userIds = [...new Set(reporterUsers.concat(reportedUsers))]
const threadIds = [
...new Set(rawReports.filter((report) => report.thread_id).map((report) => report.thread_id)),
]
const reasons = ['All', ...new Set(rawReports.map((report) => report.report_type))]
const rawReports = computed(() =>
rawReportsData.value.map((report) => ({
...report,
item_id: report.item_id.replace(/"/g, ''),
})),
)

const [{ data: users }, { data: versions }, { data: threads }] = await Promise.all([
await useAsyncData(`users?ids=${JSON.stringify(userIds)}`, () =>
fetchSegmented(userIds, (ids) => `users?ids=${asEncodedJsonArray(ids)}`),
),
await useAsyncData(`versions?ids=${JSON.stringify(versionIds)}`, () =>
fetchSegmented(versionIds, (ids) => `versions?ids=${asEncodedJsonArray(ids)}`),
),
await useAsyncData(`threads?ids=${JSON.stringify(threadIds)}`, () =>
fetchSegmented(threadIds, (ids) => `threads?ids=${asEncodedJsonArray(ids)}`),
const reporterUsers = computed(() => rawReports.value.map((report) => report.reporter))
const reportedUsers = computed(() =>
rawReports.value.filter((report) => report.item_type === 'user').map((report) => report.item_id),
)
const versionReports = computed(() =>
rawReports.value.filter((report) => report.item_type === 'version'),
)
const versionIds = computed(() => [
...new Set(versionReports.value.map((report) => report.item_id)),
])
const userIds = computed(() => [...new Set(reporterUsers.value.concat(reportedUsers.value))])
const threadIds = computed(() => [
...new Set(
rawReports.value.filter((report) => report.thread_id).map((report) => report.thread_id),
),
])
const reasons = computed(() => [
'All',
...new Set(rawReports.value.map((report) => report.report_type)),
])

const reportedProjects = rawReports
.filter((report) => report.item_type === 'project')
.map((report) => report.item_id)
const versionProjects = versions.value.map((version) => version.project_id)
const projectIds = [...new Set(reportedProjects.concat(versionProjects))]
const { data: users } = useQuery({
queryKey: computed(() => ['users', userIds.value]),
queryFn: () => fetchSegmented(userIds.value, (ids) => `users?ids=${asEncodedJsonArray(ids)}`),
enabled: computed(() => userIds.value.length > 0),
placeholderData: [],
})

const { data: versions } = useQuery({
queryKey: computed(() => ['versions', versionIds.value]),
queryFn: () =>
fetchSegmented(versionIds.value, (ids) => `versions?ids=${asEncodedJsonArray(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)}`),
enabled: computed(() => threadIds.value.length > 0),
placeholderData: [],
})

const { data: projects } = await useAsyncData(`projects?ids=${JSON.stringify(projectIds)}`, () =>
fetchSegmented(projectIds, (ids) => `projects?ids=${asEncodedJsonArray(ids)}`),
const reportedProjects = computed(() =>
rawReports.value
.filter((report) => report.item_type === 'project')
.map((report) => report.item_id),
)
const versionProjects = computed(() => versions.value.map((version) => version.project_id))
const projectIds = computed(() => [
...new Set(reportedProjects.value.concat(versionProjects.value)),
])

reports.value = rawReports.map((report) => {
report.reporterUser = users.value.find((user) => user.id === report.reporter)
if (report.item_type === 'user') {
report.user = users.value.find((user) => user.id === report.item_id)
} else if (report.item_type === 'project') {
report.project = projects.value.find((project) => project.id === report.item_id)
} else if (report.item_type === 'version') {
report.version = versions.value.find((version) => version.id === report.item_id)
report.project = projects.value.find((project) => project.id === report.version.project_id)
}
if (report.thread_id) {
report.thread = addReportMessage(
threads.value.find((thread) => report.thread_id === thread.id),
report,
)
}
report.open = true
return report
const { data: projects } = useQuery({
queryKey: computed(() => ['projects', projectIds.value]),
queryFn: () =>
fetchSegmented(projectIds.value, (ids) => `projects?ids=${asEncodedJsonArray(ids)}`),
enabled: computed(() => projectIds.value.length > 0),
placeholderData: [],
})

const reports = computed(() =>
rawReports.value.map((report) => {
const enrichedReport = { ...report }
enrichedReport.reporterUser = users.value.find((user) => user.id === report.reporter)
if (report.item_type === 'user') {
enrichedReport.user = users.value.find((user) => user.id === report.item_id)
} else if (report.item_type === 'project') {
enrichedReport.project = projects.value.find((project) => project.id === report.item_id)
} else if (report.item_type === 'version') {
enrichedReport.version = versions.value.find((version) => version.id === report.item_id)
enrichedReport.project = projects.value.find(
(project) => project.id === enrichedReport.version?.project_id,
)
}
if (report.thread_id) {
const thread = threads.value.find((thread) => report.thread_id === thread.id)
enrichedReport.thread = thread ? addReportMessage(thread, report) : null
}
enrichedReport.open = true
return enrichedReport
}),
)

const filteredReports = computed(() =>
reports.value?.filter(
(x) =>
(props.moderation || x.reporterUser.id === props.auth.user.id) &&
(props.moderation || x.reporterUser?.id === props.auth.user.id) &&
(viewMode.value === 'open' ? x.open : !x.open) &&
(reasonFilter.value === 'All' || reasonFilter.value === x.report_type),
),
Expand Down
Loading
Loading