diff --git a/README.md b/README.md index b265956..ad113ca 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ a Page to view some global statistics about [Modrinth](https://modrinth.com/) # example .env MONGODB_DB=modrinth-statistics MONGODB_CONNECTION=mongodb://:@:27017/ +MONGODB_DEV_DB=modrinth-statistics-dev +MONGODB_DEV_CONNECTION=mongodb://:@:27017/ POSTHOG_API_KEY= POSTHOG_API_HOST=https://eu.posthog.com ``` \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index fd8801ab0fe83ccf3ce8126351905da42e6a20aa..3987696a5032b089d5368960a7535466a4fb32ad 100755 GIT binary patch delta 59586 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 diff --git a/nuxt.config.ts b/nuxt.config.ts index ad214d2..bdc4798 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -14,24 +14,44 @@ export default defineNuxtConfig({ nitro: { // preset: "bun", storage: { - statistics: { + globalStatistics: { driver: "mongodb", connectionString: `${process.env.MONGODB_CONNECTION}`, databaseName: `${process.env.MONGODB_DB}`, - collectionName: "stats", + collectionName: "globalStatistics", + }, + projectStatistics: { + driver: "mongodb", + connectionString: `${process.env.MONGODB_CONNECTION}`, + databaseName: `${process.env.MONGODB_DB}`, + collectionName: "projectStatistics", + }, + metadata: { + driver: "mongodb", + connectionString: `${process.env.MONGODB_CONNECTION}`, + databaseName: `${process.env.MONGODB_DB}`, + collectionName: "metadata", }, }, devStorage: { - statistics: { - driver: "fs", - base: "./.data/statistics", + globalStatistics: { + driver: "mongodb", + connectionString: `${process.env.MONGODB_DEV_CONNECTION}`, + databaseName: `${process.env.MONGODB_DEV_DB}`, + collectionName: "globalStatistics", + }, + projectStatistics: { + driver: "mongodb", + connectionString: `${process.env.MONGODB_DEV_CONNECTION}`, + databaseName: `${process.env.MONGODB_DEV_DB}`, + collectionName: "projectStatistics", + }, + metadata: { + driver: "mongodb", + connectionString: `${process.env.MONGODB_DEV_CONNECTION}`, + databaseName: `${process.env.MONGODB_DEV_DB}`, + collectionName: "metadata", }, - // statistics: { - // driver: "mongodb", - // connectionString: `${process.env.MONGODB_CONNECTION}`, - // databaseName: `${process.env.MONGODB_DB}`, - // collectionName: "stats", - // }, }, experimental: { tasks: true, diff --git a/package.json b/package.json index 37721cf..2a18d78 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "devDependencies": { "@biomejs/biome": "1.9.4", "@iconify-json/akar-icons": "^1.2.1", + "@iconify-json/ic": "^1.2.1", "@iconify-json/mdi": "^1.2.1", "@iconify-json/ph": "^1.2.1", "@nuxt/icon": "^1.5.6", diff --git a/server/api/gameVersions.ts b/server/api/gameVersions.ts index 6753488..b678c95 100644 --- a/server/api/gameVersions.ts +++ b/server/api/gameVersions.ts @@ -1,15 +1,12 @@ -import type { - GameVersionData, - GameVersions, -} from "~~/server/utils/processing/gameVersions/types"; +import type { GameVersionData } from "~~/server/utils/processing/gameVersions/types"; +import { getGameVersions } from "~~/server/utils/storage"; export default defineEventHandler(async (event): Promise => { const query = getQuery(event); - const storage = useStorage("statistics"); - const gameVersions = await storage.getItem("gameVersions"); + const gameVersions = await getGameVersions(); - if (!gameVersions) { + if (gameVersions instanceof Error) { setResponseStatus(event, 500); return []; } diff --git a/server/api/latestDate.ts b/server/api/latestDate.ts index c7b013c..6eb169b 100644 --- a/server/api/latestDate.ts +++ b/server/api/latestDate.ts @@ -1,8 +1,9 @@ +import { getLatestDate } from "~~/server/utils/storage"; + export default defineEventHandler(async (event): Promise => { - const storage = useStorage("statistics"); - const gameVersions = await storage.getItem("latestDate"); + const gameVersions = await getLatestDate(); - if (!gameVersions) { + if (gameVersions instanceof Error) { setResponseStatus(event, 500); return null; } diff --git a/server/api/stats/global.ts b/server/api/stats/global.ts index 67a7050..5a6b852 100644 --- a/server/api/stats/global.ts +++ b/server/api/stats/global.ts @@ -1,24 +1,23 @@ import type { GlobalStats } from "~~/server/utils/processing/global/types"; +import { getLatestGlobalStats } from "~~/server/utils/storage"; +import consola from "consola"; -export default defineEventHandler(async (event): Promise => { - const query = getQuery(event); +export default defineCachedEventHandler( + async (event): Promise => { + const globalStats = await getLatestGlobalStats(); - const storage = useStorage("statistics"); + if (globalStats instanceof Error) { + consola.error(globalStats.message); + setResponseStatus(event, 500); + return { + projects: 0, + versions: 0, + files: 0, + authors: 0, + }; + } - const dateKey = await storage.getItem("latestDate"); - const gameVersions = await storage.getItem( - `globalStats${dateKey}`, - ); - - if (!gameVersions) { - setResponseStatus(event, 500); - return { - projects: 0, - versions: 0, - files: 0, - authors: 0, - }; - } - - return gameVersions; -}); + return globalStats; + }, + { maxAge: 60 * 60 /* 1 hour */ }, +); diff --git a/server/api/stats/projects.ts b/server/api/stats/projects.ts index a14dae4..07b9188 100644 --- a/server/api/stats/projects.ts +++ b/server/api/stats/projects.ts @@ -1,4 +1,3 @@ -import consola from "consola"; import type { ProjectStatCategory, ProjectStatsValue, @@ -17,18 +16,6 @@ export default defineCachedEventHandler( async (event): Promise => { const query = getQuery(event); - const storage = useStorage("statistics"); - const dateKey = await storage.getItem("latestDate"); - - if (!dateKey) { - consola.error("no latestDate set"); - - return { - labels: [], - data: [], - }; - } - let typeFn: (value: ProjectStatsValue) => number; switch (query.stat) { @@ -47,10 +34,9 @@ export default defineCachedEventHandler( } return exportStats( - query.mode, + query.mode as "all" | "major" | "minor", query.type, query.exclusive === "true", - dateKey, typeFn, query.versionTo, query.versionFrom, diff --git a/server/api/stats/time/global.ts b/server/api/stats/time/global.ts index 55c09e8..f96928c 100644 --- a/server/api/stats/time/global.ts +++ b/server/api/stats/time/global.ts @@ -1,5 +1,6 @@ import consola from "consola"; import type { GlobalStatCategory } from "~~/server/utils/processing/global/types"; +import { getLatestDate } from "~~/server/utils/storage"; type QueryData = { type: GlobalStatCategory; @@ -9,10 +10,9 @@ export default defineCachedEventHandler( async (event): Promise => { const query = getQuery(event); - const storage = useStorage("statistics"); - const dateKey = await storage.getItem("latestDate"); + const dateKey = await getLatestDate(); - if (!dateKey) { + if (dateKey instanceof Error) { consola.error("no latestDate set"); return { diff --git a/server/api/stats/time/projects.ts b/server/api/stats/time/projects.ts index 2fcb045..d15c6b7 100644 --- a/server/api/stats/time/projects.ts +++ b/server/api/stats/time/projects.ts @@ -1,4 +1,3 @@ -import consola from "consola"; import type { ProjectStatCategory, ProjectStatsValue, @@ -16,18 +15,6 @@ export default defineCachedEventHandler( async (event): Promise => { const query = getQuery(event); - const storage = useStorage("statistics"); - const dateKey = await storage.getItem("latestDate"); - - if (!dateKey) { - consola.error("no latestDate set"); - - return { - labels: [], - data: [], - }; - } - let typeFn: (value: ProjectStatsValue) => number; switch (query.stat) { @@ -46,10 +33,10 @@ export default defineCachedEventHandler( } return exportStatsOverTime( - query.mode, + query.mode as "all" | "major" | "minor", query.type, query.exclusive === "true", - dateKey, + new Date(), query.version, typeFn, ); diff --git a/server/utils/export/globalStats.ts b/server/utils/export/globalStats.ts index e91527d..b5d4e19 100644 --- a/server/utils/export/globalStats.ts +++ b/server/utils/export/globalStats.ts @@ -1,12 +1,11 @@ import type { GlobalStats } from "~~/server/utils/processing/global/types"; -import { dateToKey, keyToDate } from "~~/utils/date"; +import { dateToFormatted, keyToDate } from "~~/utils/date"; +import { getGlobalStats } from "~~/server/utils/storage"; export async function exportGlobalStatsOverTime( firstDateKey: string, fn: (value: GlobalStats) => number, ): Promise { - const storage = useStorage("statistics"); - const statsOverTime: StatExport = { labels: [], data: [ @@ -21,16 +20,13 @@ export async function exportGlobalStatsOverTime( const date = keyToDate(firstDateKey); while (true) { - const dateKey = dateToKey(date); - const key = `globalStats${dateKey}`; - const stats = await storage.getItem(key); + const stats = await getGlobalStats(date); - if (!stats) { + if (stats instanceof Error) { break; } - statsOverTime.labels.splice(0, 0, dateKey); - + statsOverTime.labels.splice(0, 0, dateToFormatted(date)); statsOverTime.data[0].data.splice(0, 0, fn(stats)); date.setUTCDate(date.getUTCDate() - 1); diff --git a/server/utils/export/projectStats.ts b/server/utils/export/projectStats.ts index 1929c47..1148a90 100644 --- a/server/utils/export/projectStats.ts +++ b/server/utils/export/projectStats.ts @@ -3,24 +3,11 @@ import type { ProjectStats, ProjectStatsValue, } from "~~/server/utils/processing/projects/types"; -import { dateToKey, keyToDate } from "~~/utils/date"; - -function getStorageKey( - type: ProjectTypes, - versionCategory: string, - exclusive: boolean, - dateKey: string, -) { - // key format: Stats - let key = `${type}Stats${firstLetterUpperCase(versionCategory)}`; - - if (exclusive) { - key += "Exclusive"; - } - - key += dateKey; - return key; -} +import { dateToFormatted } from "~~/utils/date"; +import { + getLatestProjectStats, + getProjectStats, +} from "~~/server/utils/storage"; // maps database data to a format usable by the frontend function mapStats( @@ -67,27 +54,24 @@ function filterVersions( } export async function exportStats( - versionCategory: string, + versionCategory: "all" | "major" | "minor", type: ProjectTypes, exclusive: boolean, - dateKey: string, fn: (value: ProjectStatsValue) => number, versionTo: string | null, versionFrom: string | null, ): Promise { - const key = getStorageKey(type, versionCategory, exclusive, dateKey); - - const storage = useStorage("statistics"); - const stats = await storage.getItem(key); + const stats = await getLatestProjectStats(type, versionCategory, exclusive); - if (!stats) { - consola.error(`could not find stats for key - "${key}"`); + if (stats instanceof Error) { + consola.error(stats.message); return { labels: [], data: [], }; } + const mappedStats = mapStats(stats, fn); filterVersions(mappedStats, versionTo, versionFrom); @@ -96,34 +80,30 @@ export async function exportStats( } export async function exportStatsOverTime( - versionCategory: string, + versionCategory: "all" | "major" | "minor", type: ProjectTypes, exclusive: boolean, - firstDateKey: string, + firstDate: Date, version: string, fn: (value: ProjectStatsValue) => number, ): Promise { - const storage = useStorage("statistics"); - const statsOverTime: ProjectStats = { versions: [], data: [], }; - const date = keyToDate(firstDateKey); + const date = firstDate; while (true) { - const dateKey = dateToKey(date); - const key = getStorageKey(type, versionCategory, exclusive, dateKey); - const stats = await storage.getItem(key); + const stats = await getProjectStats(date, type, versionCategory, exclusive); - if (!stats) { + if (stats instanceof Error) { break; } const index = stats.versions.indexOf(version); - statsOverTime.versions.splice(0, 0, dateKey); + statsOverTime.versions.splice(0, 0, dateToFormatted(date)); outer: for (const data of stats.data) { for (const dataOverTime of statsOverTime.data) { diff --git a/server/utils/processing/gameVersions/processing.ts b/server/utils/processing/gameVersions/processing.ts index 112de1a..e4b755d 100644 --- a/server/utils/processing/gameVersions/processing.ts +++ b/server/utils/processing/gameVersions/processing.ts @@ -8,14 +8,13 @@ import type { SupportedVersions, } from "~~/server/utils/processing/gameVersions/types"; import type { Version } from "~~/server/utils/processing/projects/types"; +import { setGameVersions } from "~~/server/utils/storage"; export async function updateGameVersions() { const versions = await getGameVersions(); const gameVersions = groupGameVersions(versions); - const storage = useStorage("statistics"); - - await storage.setItem("gameVersions", gameVersions); + await setGameVersions(gameVersions); } // returns what versionGroup (all, minor, major) a specif projectVersion supports diff --git a/server/utils/processing/global/processing.ts b/server/utils/processing/global/processing.ts index 57f7547..3a9203d 100644 --- a/server/utils/processing/global/processing.ts +++ b/server/utils/processing/global/processing.ts @@ -1,17 +1,11 @@ import consola from "consola"; +import type { GlobalStats } from "~~/server/utils/processing/global/types"; +import { setGlobalStats } from "~~/server/utils/storage"; export async function updateGlobalStats() { consola.log("fetching globalStats"); - const data = await $modrinthFetch<{ - projects: number; - versions: number; - files: number; - authors: number; - }>("/statistics"); + const data = await $modrinthFetch("/statistics"); - const storage = useStorage("statistics"); - const dateKey = dateToKey(new Date()); - - await storage.setItem(`globalStats${dateKey}`, data); + await setGlobalStats(data, new Date()); } diff --git a/server/utils/processing/projects/processing.ts b/server/utils/processing/projects/processing.ts index 0652135..bc81412 100644 --- a/server/utils/processing/projects/processing.ts +++ b/server/utils/processing/projects/processing.ts @@ -13,6 +13,11 @@ import type { ProjectStats, Version, } from "~~/server/utils/processing/projects/types"; +import { + getGameVersions, + setLatestDate, + setProjectStats, +} from "~~/server/utils/storage"; type StatsDataType = Map< string, @@ -63,54 +68,38 @@ export async function updateStatistics() { async function updateStatistic(type: ProjectTypes) { consola.log(`analyzing - ${type}`); const stats = await getStatistics(type); + + if (stats instanceof Error) { + consola.error(stats.message); + return; + } + await saveStats(stats, type); } async function saveStats(stats: AllStats, type: ProjectTypes) { - const storage = useStorage("statistics"); - const dateKey = dateToKey(new Date()); - - await storage.setItem( - `${type}StatsAll${dateKey}`, - stats.all.all, - ); - await storage.setItem( - `${type}StatsMinor${dateKey}`, - stats.all.minor, - ); - await storage.setItem( - `${type}StatsMajor${dateKey}`, - stats.all.major, - ); - - await storage.setItem( - `${type}StatsAllExclusive${dateKey}`, - stats.exclusive.all, - ); - await storage.setItem( - `${type}StatsMinorExclusive${dateKey}`, - stats.exclusive.minor, - ); - await storage.setItem( - `${type}StatsMajorExclusive${dateKey}`, - stats.exclusive.major, - ); - - await storage.setItem("latestDate", dateKey); + await setProjectStats(stats.all.all, type, "all", false); + await setProjectStats(stats.all.minor, type, "minor", false); + await setProjectStats(stats.all.major, type, "major", false); + + await setProjectStats(stats.exclusive.all, type, "all", true); + await setProjectStats(stats.exclusive.minor, type, "minor", true); + await setProjectStats(stats.exclusive.major, type, "major", true); + + await setLatestDate(new Date()); } -async function getStatistics(type: ProjectTypes): Promise { +async function getStatistics(type: ProjectTypes): Promise { const BATCH_COUNT = import.meta.dev ? 1 : 10; const data: StatsData = { all: { all: new Map(), major: new Map(), minor: new Map() }, exclusive: { all: new Map(), major: new Map(), minor: new Map() }, }; - const storage = useStorage("statistics"); - const gameVersions = await storage.getItem("gameVersions"); + const gameVersions = await getGameVersions(); - if (!gameVersions) { - throw "no gameVersions were initilized!"; + if (gameVersions instanceof Error) { + return gameVersions; } let projectIndex = 0; @@ -136,11 +125,10 @@ async function getStatistics(type: ProjectTypes): Promise { if (done || import.meta.dev) break; } - function toVersions(data: StatsDataEntry): AllStatsEntry { - if (!gameVersions) { - throw "no gameVersions were initilized!"; - } - + function toVersions( + gameVersions: GameVersions, + data: StatsDataEntry, + ): AllStatsEntry { return { all: groupStatsToStats(StatsFromType(gameVersions.all, data.all)), minor: groupStatsToStats(StatsFromType(gameVersions.minor, data.minor)), @@ -149,8 +137,8 @@ async function getStatistics(type: ProjectTypes): Promise { } return { - all: toVersions(data.all), - exclusive: toVersions(data.exclusive), + all: toVersions(gameVersions, data.all), + exclusive: toVersions(gameVersions, data.exclusive), }; } diff --git a/server/utils/storage.ts b/server/utils/storage.ts new file mode 100644 index 0000000..fe7b423 --- /dev/null +++ b/server/utils/storage.ts @@ -0,0 +1,116 @@ +import type { GameVersions } from "~~/server/utils/processing/gameVersions/types"; +import type { GlobalStats } from "~~/server/utils/processing/global/types"; +import type { ProjectStats } from "~~/server/utils/processing/projects/types"; + +export async function setGameVersions(gameVersions: GameVersions) { + const storage = useStorage("metadata"); + await storage.setItem("gameVersions", gameVersions); +} + +export async function getGameVersions(): Promise { + return getFromStorage("metadata", "gameVersions"); +} + +export async function setLatestDate(date: Date) { + const storage = useStorage("metadata"); + await storage.setItem("latestDate", dateToKey(date)); +} + +export async function getLatestDate(): Promise { + return getFromStorage("metadata", "latestDate"); +} + +export async function setGlobalStats(data: GlobalStats, date: Date) { + const storage = useStorage("globalStatistics"); + const dateKey = dateToKey(date); + + await storage.setItem(`globalStats${dateKey}`, data); +} + +export async function getLatestGlobalStats(): Promise { + const dateKey = await getLatestDate(); + const key = `globalStats${dateKey}`; + + return getFromStorage("globalStatistics", key); +} + +export async function getGlobalStats(date: Date): Promise { + const dateKey = dateToKey(date); + const key = `globalStats${dateKey}`; + + return getFromStorage("globalStatistics", key); +} + +function getProjectStorageKey( + type: ProjectTypes, + versionCategory: "all" | "major" | "minor", + exclusive: boolean, + dateKey: string, +) { + // key format: Stats + let key = `${type}Stats${firstLetterUpperCase(versionCategory)}`; + + if (exclusive) { + key += "Exclusive"; + } + + key += dateKey; + return key; +} + +export async function setProjectStats( + data: ProjectStats, + type: ProjectTypes, + versionCategory: "all" | "major" | "minor", + exclusive: boolean, +) { + const storage = useStorage("projectStatistics"); + + const dateKey = dateToKey(new Date()); + const key = getProjectStorageKey(type, versionCategory, exclusive, dateKey); + + await storage.setItem(key, data); +} + +export async function getProjectStats( + date: Date, + type: ProjectTypes, + versionCategory: "all" | "major" | "minor", + exclusive: boolean, +): Promise { + const dateKey = dateToKey(date); + const key = getProjectStorageKey(type, versionCategory, exclusive, dateKey); + + return getFromStorage("projectStatistics", key); +} + +export async function getLatestProjectStats( + type: ProjectTypes, + versionCategory: "all" | "major" | "minor", + exclusive: boolean, +): Promise { + const dateKey = await getLatestDate(); + + if (dateKey instanceof Error) return dateKey; + + const key = getProjectStorageKey(type, versionCategory, exclusive, dateKey); + + return getFromStorage("projectStatistics", key); +} + +type StorageValue = null | string | number | boolean | object; +async function getFromStorage( + storageName: string, + key: string, +) { + const storage = useStorage(storageName); + const data = await storage.getItem(key); + + if (!data) { + return new Error( + `could not find value in storage - storage: "${storageName}" key: "${key}"`, + ); + } + + return data; +} diff --git a/utils/date.ts b/utils/date.ts index 8552b06..a5f80ac 100644 --- a/utils/date.ts +++ b/utils/date.ts @@ -2,6 +2,10 @@ export function dateToKey(date: Date): string { return `${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}`; } +export function dateToFormatted(date: Date): string { + return `${date.getUTCFullYear()} ${date.getUTCMonth() + 1} ${date.getUTCDate()}`; +} + export function keyToDate(key: string): Date { const [year, month, day] = key.split("-").map(Number); return new Date(Date.UTC(year as number, (month as number) - 1, day));