Dodaj mikroserwis Redis API z obsługą Docker i Bun (Migracja api do oddzielnego repozytorium)
- 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.
This commit is contained in:
195
index.ts
Normal file
195
index.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
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');
|
||||
Reference in New Issue
Block a user