a cache for slack profile pictures and emojis

Add log grouping migration

- Create migration to normalize request logs with full URLs
- Extract paths from URLs and apply consistent grouping
- Update version to 0.3.2

🦊 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>

dunkirk.sh d4a5fd0b 1d01a11e

verified
Changed files
+105 -2
src
+1 -1
package.json
···
{
"name": "cachet",
-
"version": "0.3.1",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "bun run --watch src/index.ts",
···
{
"name": "cachet",
+
"version": "0.3.2",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "bun run --watch src/index.ts",
+5 -1
src/cache.ts
···
import { schedule } from "node-cron";
import { MigrationManager } from "./migrations/migrationManager";
import { endpointGroupingMigration } from "./migrations/endpointGroupingMigration";
/**
* @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.
···
*/
private async runMigrations() {
try {
-
const migrations = [endpointGroupingMigration];
const migrationManager = new MigrationManager(this.db, migrations);
const result = await migrationManager.runMigrations();
···
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.
···
*/
private async runMigrations() {
try {
+
const migrations = [
+
endpointGroupingMigration,
+
logGroupingMigration
+
];
const migrationManager = new MigrationManager(this.db, migrations);
const result = await migrationManager.runMigrations();
+2
src/migrations/index.ts
···
import { endpointGroupingMigration } from "./endpointGroupingMigration";
import { Migration } from "./types";
import { MigrationManager } from "./migrationManager";
// Export all migrations
export const migrations: Migration[] = [
endpointGroupingMigration,
// Add new migrations here
];
···
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
];
+97
src/migrations/logGroupingMigration.ts
···
···
+
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<void> {
+
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");
+
}
+
};