diff --git a/package.json b/package.json index 8b0883b..eac0f65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cachet", - "version": "0.3.1", + "version": "0.3.2", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "bun run --watch src/index.ts", diff --git a/src/cache.ts b/src/cache.ts index b100081..b24fe24 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -2,6 +2,7 @@ import { Database } from "bun:sqlite"; import { schedule } from "node-cron"; import { MigrationManager } from "./migrations/migrationManager"; import { endpointGroupingMigration } from "./migrations/endpointGroupingMigration"; +import { logGroupingMigration } from "./migrations/logGroupingMigration"; /** * @fileoverview This file contains the Cache class for storing user and emoji data with automatic expiration. To use the module in your project, import the default export and create a new instance of the Cache class. The class provides methods for inserting and retrieving user and emoji data from the cache. The cache automatically purges expired items every hour. @@ -149,7 +150,10 @@ class Cache { */ private async runMigrations() { try { - const migrations = [endpointGroupingMigration]; + const migrations = [ + endpointGroupingMigration, + logGroupingMigration + ]; const migrationManager = new MigrationManager(this.db, migrations); const result = await migrationManager.runMigrations(); diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 35cc85f..81d4a38 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -1,10 +1,12 @@ import { endpointGroupingMigration } from "./endpointGroupingMigration"; +import { logGroupingMigration } from "./logGroupingMigration"; import { Migration } from "./types"; import { MigrationManager } from "./migrationManager"; // Export all migrations export const migrations: Migration[] = [ endpointGroupingMigration, + logGroupingMigration, // Add new migrations here ]; diff --git a/src/migrations/logGroupingMigration.ts b/src/migrations/logGroupingMigration.ts new file mode 100644 index 0000000..d11110e --- /dev/null +++ b/src/migrations/logGroupingMigration.ts @@ -0,0 +1,97 @@ +import { Database } from "bun:sqlite"; +import { Migration } from "./types"; + +/** + * Migration to group request logs that aren't already grouped + * This migration normalizes request_analytics data to use consistent endpoint grouping + */ +export const logGroupingMigration: Migration = { + version: "0.3.2", + description: "Group request logs that aren't already grouped", + + async up(db: Database): Promise { + console.log("Running log grouping migration..."); + + // Get all request_analytics entries with specific URLs that need grouping + const results = db.query(` + SELECT id, endpoint FROM request_analytics + WHERE + endpoint NOT LIKE '/users/%/r' AND + endpoint NOT LIKE '/users/%' AND + endpoint NOT LIKE '/emojis/%/r' AND + endpoint NOT LIKE '/emojis/%' AND + endpoint NOT LIKE '/health' AND + endpoint NOT LIKE '/dashboard' AND + endpoint NOT LIKE '/swagger%' AND + endpoint NOT LIKE '/reset' AND + endpoint NOT LIKE '/stats' AND + endpoint NOT LIKE '/' + `).all() as Array<{ id: string; endpoint: string }>; + + console.log(`Found ${results.length} entries to update`); + + // Process each entry and update with the correct grouping + for (const entry of results) { + let newEndpoint = entry.endpoint; + + // Apply grouping logic + if (entry.endpoint.includes("localhost") || entry.endpoint.includes("http")) { + // Extract the path from URLs + try { + const url = new URL(entry.endpoint); + newEndpoint = url.pathname; + } catch (e) { + // If URL parsing fails, try to extract the path manually + const pathMatch = entry.endpoint.match(/https?:\/\/[^\/]+(\/.*)/); + if (pathMatch && pathMatch[1]) { + newEndpoint = pathMatch[1]; + } + } + } + + // Now apply the same grouping logic to the extracted path + if (newEndpoint.match(/^\/users\/[^\/]+$/)) { + newEndpoint = "/users/USER_ID"; + } else if (newEndpoint.match(/^\/users\/[^\/]+\/r$/)) { + newEndpoint = "/users/USER_ID/r"; + } else if (newEndpoint.match(/^\/emojis\/[^\/]+$/)) { + newEndpoint = "/emojis/EMOJI_NAME"; + } else if (newEndpoint.match(/^\/emojis\/[^\/]+\/r$/)) { + newEndpoint = "/emojis/EMOJI_NAME/r"; + } else if (newEndpoint.includes("/users/") && newEndpoint.includes("/r")) { + newEndpoint = "/users/USER_ID/r"; + } else if (newEndpoint.includes("/users/")) { + newEndpoint = "/users/USER_ID"; + } else if (newEndpoint.includes("/emojis/") && newEndpoint.includes("/r")) { + newEndpoint = "/emojis/EMOJI_NAME/r"; + } else if (newEndpoint.includes("/emojis/")) { + newEndpoint = "/emojis/EMOJI_NAME"; + } else if (newEndpoint === "/") { + newEndpoint = "/"; + } else if (newEndpoint === "/health") { + newEndpoint = "/health"; + } else if (newEndpoint === "/dashboard") { + newEndpoint = "/dashboard"; + } else if (newEndpoint.startsWith("/swagger")) { + newEndpoint = "/swagger"; + } else if (newEndpoint === "/reset") { + newEndpoint = "/reset"; + } else if (newEndpoint === "/stats") { + newEndpoint = "/stats"; + } else { + newEndpoint = "/other"; + } + + // Only update if the endpoint has changed + if (newEndpoint !== entry.endpoint) { + db.run(` + UPDATE request_analytics + SET endpoint = ? + WHERE id = ? + `, [newEndpoint, entry.id]); + } + } + + console.log("Log grouping migration completed"); + } +}; \ No newline at end of file