- Utworzono API w Bun i TypeScript do obsługi Redis (GET/POST/DELETE) z różnymi typami danych (string, list, set, hash). - Dodano `docker-compose.yaml` i `dockerfile` do uruchamiania Redis i API jako kontenery. - Skonfigurowano `.dockerignore` i `.gitignore` dla czystości repozytorium. - Użyto `bun.lockb` oraz `package.json` do zależności i blokady wersji. - Skonfigurowano `tsconfig.json` dla kompilatora TypeScript.
196 lines
5.5 KiB
TypeScript
196 lines
5.5 KiB
TypeScript
import { serve } from 'bun';
|
|
import { createClient } from 'redis';
|
|
|
|
const client = createClient({
|
|
url: Bun.env.IS_DOCKER ? 'redis://redis-stack:6379' : 'redis://localhost:6379'
|
|
});
|
|
|
|
client.on('error', (err: Error) => console.error('Redis Client Error', err));
|
|
await client.connect();
|
|
|
|
const healthCheck = async () => {
|
|
try {
|
|
await client.ping();
|
|
return { status: 'OK' };
|
|
} catch (error: unknown) {
|
|
return {
|
|
status: 'ERROR',
|
|
message: error instanceof Error ? error.message : 'Nieznany błąd'
|
|
};
|
|
}
|
|
};
|
|
|
|
const handleRedisAction = async (
|
|
method: string,
|
|
key?: string,
|
|
value?: any,
|
|
ttl?: number,
|
|
type: string = 'string'
|
|
) => {
|
|
switch (method) {
|
|
case 'GET':
|
|
if (!key) return { error: 'Brak klucza' };
|
|
|
|
const keyExists = await client.exists(key);
|
|
if (!keyExists) return { value: null, type: null };
|
|
|
|
const valueType = await client.type(key);
|
|
switch (valueType) {
|
|
case 'string':
|
|
const getValue = await client.get(key);
|
|
return { value: getValue ? JSON.parse(getValue) : null, type: valueType };
|
|
|
|
case 'list':
|
|
const getList = await client.lRange(key, 0, -1);
|
|
return {
|
|
value: getList ? getList.map(item => JSON.parse(item)) : [],
|
|
type: valueType
|
|
};
|
|
|
|
case 'hash':
|
|
const getHash = await client.hGetAll(key);
|
|
const parsedHash = Object.fromEntries(
|
|
Object.entries(getHash).map(([k, v]) => [k, JSON.parse(v)])
|
|
);
|
|
return { value: parsedHash || {}, type: valueType };
|
|
|
|
case 'set':
|
|
const getSet = await client.sMembers(key);
|
|
return {
|
|
value: getSet ? getSet.map(item => JSON.parse(item)) : [],
|
|
type: valueType
|
|
};
|
|
|
|
default:
|
|
return { error: 'Nieobsługiwany typ danych' };
|
|
}
|
|
|
|
case 'POST':
|
|
if (!key) return { error: 'Brak klucza' };
|
|
|
|
switch (type) {
|
|
case 'string':
|
|
if (!value) return { error: 'Brak wartości' };
|
|
|
|
await client.set(key, JSON.stringify(value));
|
|
if (ttl && ttl > 0) await client.expire(key, ttl);
|
|
|
|
return { message: 'Klucz string ustawiony' };
|
|
|
|
case 'hash':
|
|
if (!value || typeof value !== 'object')
|
|
return { error: 'Brak wartości dla hash lub niepoprawny typ' };
|
|
|
|
const stringifiedHash = Object.fromEntries(
|
|
Object.entries(value).map(([k, v]) => [k, JSON.stringify(v)])
|
|
);
|
|
await client.hSet(key, stringifiedHash);
|
|
if (ttl && ttl > 0) await client.expire(key, ttl);
|
|
|
|
return { message: 'Hash ustawiony' };
|
|
|
|
case 'list':
|
|
if (!Array.isArray(value)) return { error: 'Wartość musi być tablicą' };
|
|
|
|
const stringifiedList = value.map(item => JSON.stringify(item));
|
|
await client.rPush(key, stringifiedList);
|
|
if (ttl && ttl > 0) await client.expire(key, ttl);
|
|
|
|
return { message: 'Lista ustawiona' };
|
|
|
|
case 'set':
|
|
if (!Array.isArray(value)) return { error: 'Wartość musi być tablicą' };
|
|
|
|
const stringifiedSet = value.map(item => JSON.stringify(item));
|
|
await client.sAdd(key, stringifiedSet);
|
|
if (ttl && ttl > 0) await client.expire(key, ttl);
|
|
|
|
return { message: 'Set ustawiony' };
|
|
|
|
// case 'zset':
|
|
// if (
|
|
// !Array.isArray(value) ||
|
|
// !value.every((item: any) => item.score !== undefined && item.value !== undefined)
|
|
// )
|
|
// return { error: 'Wartość musi być tablicą obiektów z polem "score" i "value"' };
|
|
// for (const item of value) {
|
|
// await client.zAdd(key, { score: item.score, value: item.value });
|
|
// }
|
|
// console.log('SET (zset)', key, value);
|
|
// return { message: 'Sorted Set ustawiony' };
|
|
|
|
default:
|
|
return { error: 'Nieobsługiwany typ danych' };
|
|
}
|
|
|
|
case 'DELETE':
|
|
if (!key) return { error: 'Brak klucza' };
|
|
|
|
if (key === 'FLUSHALL') {
|
|
await client.flushAll();
|
|
return { message: 'Wszystkie klucze zostały usunięte' };
|
|
}
|
|
|
|
await client.del(key);
|
|
return { message: 'Klucz usunięty' };
|
|
|
|
default:
|
|
return { error: 'Nieobsługiwana metoda' };
|
|
}
|
|
};
|
|
|
|
serve({
|
|
port: 5001,
|
|
async fetch(req: Request) {
|
|
const url = new URL(req.url);
|
|
const method = req.method;
|
|
|
|
const headers: { [key: string]: string } = {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type'
|
|
};
|
|
|
|
if (method === 'OPTIONS') {
|
|
return new Response('', { status: 200, headers });
|
|
}
|
|
|
|
if (url.pathname === '/api/redis') {
|
|
try {
|
|
const body = method !== 'GET' && method !== 'DELETE' ? await req.json() : null;
|
|
const key =
|
|
method === 'GET' || method === 'DELETE' ? url.searchParams.get('key') : body?.key;
|
|
const value = body?.value;
|
|
const ttl = body?.ttl ? parseInt(body.ttl, 10) : 0;
|
|
const type = body?.type;
|
|
|
|
const result = await handleRedisAction(method, key, value, ttl, type);
|
|
|
|
if (result?.error)
|
|
return new Response(JSON.stringify({ error: result.error }), { status: 400, headers });
|
|
|
|
return new Response(JSON.stringify(result), { status: 200, headers });
|
|
} catch (error) {
|
|
return new Response(
|
|
JSON.stringify({ error: error instanceof Error ? error.message : 'Nieznany błąd' }),
|
|
{ status: 400, headers }
|
|
);
|
|
}
|
|
} else if (url.pathname === '/api/health') {
|
|
const health = await healthCheck();
|
|
return new Response(JSON.stringify(health), {
|
|
status: health.status === 'OK' ? 200 : 500,
|
|
headers
|
|
});
|
|
}
|
|
|
|
return new Response(JSON.stringify({ error: 'Nieobsługiwany adres URL' }), {
|
|
status: 404,
|
|
headers
|
|
});
|
|
}
|
|
});
|
|
|
|
console.log('Redis API uruchomiony na porcie 5001');
|