diff --git a/README.md b/README.md index 7c3fd3c..d5d4fb0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,5 @@ a Page to view some global statistics about [Modrinth](https://modrinth.com/) ### Planned Features * data on specific projects - Same graphs as for mods but only showing a single project -* chart that shows how many projects exist that support a specific version/loader * settings * only show specif versions - * only show versions that support a single launcher diff --git a/components/navigation/Links.vue b/components/navigation/Links.vue index 3b17d97..be5b333 100644 --- a/components/navigation/Links.vue +++ b/components/navigation/Links.vue @@ -8,6 +8,7 @@ let urls = new Map(); for (let type of projectTypeList) { const urlList = [ {title: "Downloads", url: `/${type}/downloads`}, + {title: "Count", url: `/${type}/count`}, {title: "Versions", url: `/${type}/versions`}, ] diff --git a/nuxt.config.ts b/nuxt.config.ts index b6e2456..a3cf83d 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,6 +1,7 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ devtools: {enabled: true}, + app: { head: { link: [ @@ -8,6 +9,7 @@ export default defineNuxtConfig({ ] } }, + routeRules: { "/downloads": { redirect: "/downloads/major" @@ -23,6 +25,7 @@ export default defineNuxtConfig({ // prerender: true // } }, + nitro: { // preset: "bun", storage: { @@ -38,5 +41,7 @@ export default defineNuxtConfig({ '0 0 * * *': ['analyze'] } }, - modules: ["@nuxt/icon"] + + modules: ["@nuxt/icon"], + compatibilityDate: "2024-07-05" }) \ No newline at end of file diff --git a/pages/[type]/count.vue b/pages/[type]/count.vue new file mode 100644 index 0000000..b4d8f8f --- /dev/null +++ b/pages/[type]/count.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/server/api/stats.ts b/server/api/stats.ts index 536e073..2948d1d 100644 --- a/server/api/stats.ts +++ b/server/api/stats.ts @@ -2,15 +2,19 @@ import {exportStats, StatExport} from "~/server/utils/api/stats"; import {ProjectTypes} from "~/utils/project"; export default defineEventHandler(async (event): Promise => { - let query = getQuery(event); + let query = getQuery(event); - let mode = query.mode as string; - let type = query.type as ProjectTypes; - let exclusive = query.exclusive === "true"; + let mode = query.mode as string; + let type = query.type as ProjectTypes; + let exclusive = query.exclusive === "true"; - if (query.stat == "versions") { - return exportStats(mode, type, exclusive, value => value.versions) - } else { - return exportStats(mode, type, exclusive, value => value.downloads) + //todo: switch + if (query.stat == "versions") { + return exportStats(mode, type, exclusive, value => value.versions) + } else if (query.stat == "count") { + return exportStats(mode, type, exclusive, value => value.count) + } else { + return exportStats(mode, type, exclusive, value => value.downloads) + } } -}) \ No newline at end of file +) \ No newline at end of file diff --git a/server/utils/fetchData.ts b/server/utils/fetchData.ts index 46f11b7..c93e7ef 100644 --- a/server/utils/fetchData.ts +++ b/server/utils/fetchData.ts @@ -41,6 +41,7 @@ export async function getVersions(versionIds: string[]): Promise { //done to remove unused values from the ram return data.flatMap(value => { return { + project_id: value.project_id, loaders: value.loaders, game_versions: value.game_versions, downloads: value.downloads @@ -49,6 +50,7 @@ export async function getVersions(versionIds: string[]): Promise { } export type Version = { + project_id: string loaders: string[] game_versions: string[] downloads: number diff --git a/server/utils/statistics.ts b/server/utils/statistics.ts index 8bf96e4..b854ba3 100644 --- a/server/utils/statistics.ts +++ b/server/utils/statistics.ts @@ -1,14 +1,30 @@ -import {Stats} from "~/server/utils/types/stats"; +import {Stats, StatsValue} from "~/server/utils/types/stats"; import {GameVersion, Version} from "~/server/utils/fetchData"; import {projectTypeList, ProjectTypes} from "~/utils/project"; import consola from "consola"; -type StatsDataEntry = Map> +type StatsDataEntry = Map +}>> type StatsData = { all: StatsDataEntry, exclusive: StatsDataEntry } type AllStatsEntry = { all: Stats, minor: Stats, major: Stats }; type AllStats = { all: AllStatsEntry, exclusive: AllStatsEntry } +type GroupStats = { + versions: string[] + data: { + name: string + values: { + downloads: number, + versions: number, + unique_projects: Set + }[] + }[] +} + export async function updateStatistics() { for (let type of projectTypeList) { @@ -72,7 +88,11 @@ async function getStatistics(type: ProjectTypes): Promise { } = onlyMinorVersions(versions, allDownloads); let majorVersionDownloads = onlyMajorVersions(minorGameVersions, minorVersionDownloads); - return {all: allDownloads, minor: minorVersionDownloads, major: majorVersionDownloads}; + return { + all: groupStatsToStats(allDownloads), + minor: groupStatsToStats(minorVersionDownloads), + major: groupStatsToStats(majorVersionDownloads) + }; } return { @@ -81,6 +101,25 @@ async function getStatistics(type: ProjectTypes): Promise { } } +function groupStatsToStats(groupStats: GroupStats): Stats { + return { + versions: groupStats.versions, + data: groupStats.data.map(value => { + return { + name: value.name, + values: + value.values.map(value1 => { + return { + downloads: value1.downloads, + versions: value1.versions, + count: value1.unique_projects.size + } + }) + } + }) + } +} + async function analyzeVersionsFromIds(versionIds: string[], data: StatsData, type: ProjectTypes) { const BATCH_SIZE = 1000; @@ -124,20 +163,31 @@ function insertLauncherData(allowedLaunchers: Array, data: StatsDataEntr if (downloads.has(gameVersion)) { let data = downloads.get(gameVersion)!; - downloads.set(gameVersion, {downloads: data.downloads + versionDownloads, count: data.count + 1}) + data.unique_projects.add(version.project_id) + downloads.set(gameVersion, { + downloads: data.downloads + versionDownloads, + versions: data.versions + 1, + unique_projects: data.unique_projects + }) } else { - downloads.set(gameVersion, {downloads: versionDownloads, count: 1}) + let set = new Set(); + + downloads.set(gameVersion, { + downloads: versionDownloads, + versions: 1, + unique_projects: set + }) } } } } -function onlyMinorVersions(gameVersions: GameVersion[], all: Stats): { +function onlyMinorVersions(gameVersions: GameVersion[], all: GroupStats): { gameVersions: GameVersion[], - downloads: Stats + downloads: GroupStats } { const versions = Array.from(gameVersions) - const downloads: Stats = JSON.parse(JSON.stringify(all)) + const downloads: GroupStats = structuredClone(all) let index = 0 @@ -161,6 +211,9 @@ function onlyMinorVersions(gameVersions: GameVersion[], all: Stats): { loader.values[index].versions += stats[0].versions loader.values[index].downloads += stats[0].downloads + for (let uniqueProject of stats[0].unique_projects) { + loader.values[index].unique_projects.add(uniqueProject) + } } versions.splice(index, 1) @@ -170,9 +223,10 @@ function onlyMinorVersions(gameVersions: GameVersion[], all: Stats): { return {gameVersions: versions, downloads: downloads} } -function onlyMajorVersions(gameVersions: GameVersion[], all: Stats): Stats { +function onlyMajorVersions(gameVersions: GameVersion[], all: GroupStats): GroupStats { const versions = Array.from(gameVersions) - const downloads: Stats = JSON.parse(JSON.stringify(all)) + const downloads = structuredClone(all) + let index = 0 let currentVersion = versions[0].name.split(".").slice(0, 2).join(".") @@ -199,6 +253,9 @@ function onlyMajorVersions(gameVersions: GameVersion[], all: Stats): Stats { loader.values[index].versions += stats[0].versions loader.values[index].downloads += stats[0].downloads + for (let uniqueProject of stats[0].unique_projects) { + loader.values[index].unique_projects.add(uniqueProject) + } } versions.splice(index, 1) @@ -208,7 +265,7 @@ function onlyMajorVersions(gameVersions: GameVersion[], all: Stats): Stats { return downloads } -function StatsFromData(versions: GameVersion[], data: StatsDataEntry): Stats { +function StatsFromData(versions: GameVersion[], data: StatsDataEntry): GroupStats { const versionArray = versions.map(value => { return value.name }) @@ -223,14 +280,15 @@ function StatsFromData(versions: GameVersion[], data: StatsDataEntry): Stats { if (downloadsMap.has(version)) { downloads.push(downloadsMap.get(version)!); } else { - downloads.push({downloads: 0, count: 0}) + downloads.push({downloads: 0, versions: 0, unique_projects: new Set()}) } } downloads = downloads.map((value) => { return { downloads: Math.round(value.downloads), - versions: value.count + versions: value.versions, + unique_projects: value.unique_projects } }) diff --git a/server/utils/types/stats.ts b/server/utils/types/stats.ts index 075fc25..795a8c4 100644 --- a/server/utils/types/stats.ts +++ b/server/utils/types/stats.ts @@ -6,4 +6,4 @@ export type Stats = { }[] } -export type StatsValue = { downloads: number, versions: number } \ No newline at end of file +export type StatsValue = { downloads: number, versions: number, count: number } \ No newline at end of file