Use REDIS_URL for share shortlinks.
Stores /s/:code mappings in the connected Redis database and falls back to Vercel KV REST when available.
This commit is contained in:
Generated
+87
-1
@@ -13,7 +13,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"express": "^4.18.2",
|
||||
"midi-writer-js": "^3.1.1"
|
||||
"midi-writer-js": "^3.1.1",
|
||||
"redis": "^5.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
@@ -465,6 +466,66 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.10.0.tgz",
|
||||
"integrity": "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.10.0.tgz",
|
||||
"integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.10.0.tgz",
|
||||
"integrity": "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.10.0.tgz",
|
||||
"integrity": "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.10.0.tgz",
|
||||
"integrity": "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tonaljs/midi": {
|
||||
"version": "4.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tonaljs/midi/-/midi-4.10.2.tgz",
|
||||
@@ -779,6 +840,15 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@@ -1624,6 +1694,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-5.10.0.tgz",
|
||||
"integrity": "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "5.10.0",
|
||||
"@redis/client": "5.10.0",
|
||||
"@redis/json": "5.10.0",
|
||||
"@redis/search": "5.10.0",
|
||||
"@redis/time-series": "5.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"express": "^4.18.2",
|
||||
"midi-writer-js": "^3.1.1"
|
||||
"midi-writer-js": "^3.1.1",
|
||||
"redis": "^5.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
|
||||
+43
-13
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import cors from 'cors';
|
||||
import crypto from 'node:crypto';
|
||||
import { kv } from '@vercel/kv';
|
||||
import { createClient } from 'redis';
|
||||
import { MIDISearchService } from './services/MIDISearchService.js';
|
||||
import { MIDIFetchService } from './services/MIDIFetchService.js';
|
||||
import { MIDIParseService } from './services/MIDIParseService.js';
|
||||
@@ -35,6 +36,28 @@ type SharePayload =
|
||||
const localShareStore = new Map<string, SharePayload>();
|
||||
const isVercel = Boolean(process.env.VERCEL);
|
||||
const hasKvEnv = Boolean(process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN);
|
||||
const redisUrl = process.env.REDIS_URL;
|
||||
const hasRedisEnv = Boolean(redisUrl);
|
||||
|
||||
let redisClient: ReturnType<typeof createClient> | null = null;
|
||||
let redisConnectPromise: Promise<void> | null = null;
|
||||
|
||||
async function getRedis(): Promise<ReturnType<typeof createClient>> {
|
||||
if (!redisUrl) throw new Error('REDIS_URL is not configured for this project.');
|
||||
if (!redisClient) {
|
||||
redisClient = createClient({ url: redisUrl });
|
||||
redisClient.on('error', (err) => console.error('Redis error:', err));
|
||||
}
|
||||
if (!redisClient.isOpen) {
|
||||
if (!redisConnectPromise) {
|
||||
redisConnectPromise = redisClient.connect().then(() => {}).finally(() => {
|
||||
redisConnectPromise = null;
|
||||
});
|
||||
}
|
||||
await redisConnectPromise;
|
||||
}
|
||||
return redisClient;
|
||||
}
|
||||
|
||||
function base62(bytes: Uint8Array): string {
|
||||
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
@@ -52,37 +75,44 @@ async function shareSet(code: string, payload: SharePayload): Promise<void> {
|
||||
const key = `share:${code}`;
|
||||
// 30 days TTL
|
||||
const exSec = 60 * 60 * 24 * 30;
|
||||
if (isVercel && !hasKvEnv) {
|
||||
throw new Error('Vercel KV is not configured for this project.');
|
||||
if (isVercel && !hasRedisEnv && !hasKvEnv) {
|
||||
throw new Error('No storage configured (need REDIS_URL or Vercel KV env vars).');
|
||||
}
|
||||
|
||||
try {
|
||||
// Prefer Redis integration if present (your project has REDIS_URL).
|
||||
if (hasRedisEnv) {
|
||||
const r = await getRedis();
|
||||
await r.set(key, JSON.stringify(payload), { EX: exSec });
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Vercel KV REST (Upstash)
|
||||
if (hasKvEnv) {
|
||||
await kv.set(key, payload, { ex: exSec });
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// fall through to local store (dev only)
|
||||
}
|
||||
|
||||
// Local dev: best-effort in-memory store.
|
||||
// Local dev only: best-effort in-memory store.
|
||||
localShareStore.set(code, payload);
|
||||
}
|
||||
|
||||
async function shareGet(code: string): Promise<SharePayload | null> {
|
||||
const key = `share:${code}`;
|
||||
if (isVercel && !hasKvEnv) {
|
||||
throw new Error('Vercel KV is not configured for this project.');
|
||||
if (isVercel && !hasRedisEnv && !hasKvEnv) {
|
||||
throw new Error('No storage configured (need REDIS_URL or Vercel KV env vars).');
|
||||
}
|
||||
|
||||
if (hasRedisEnv) {
|
||||
const r = await getRedis();
|
||||
const val = await r.get(key);
|
||||
if (!val) return null;
|
||||
return JSON.parse(val) as SharePayload;
|
||||
}
|
||||
|
||||
try {
|
||||
if (hasKvEnv) {
|
||||
const val = await kv.get<SharePayload>(key);
|
||||
return val ?? null;
|
||||
}
|
||||
} catch {
|
||||
// fall through to local store (dev only)
|
||||
}
|
||||
|
||||
return localShareStore.get(code) ?? null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user