139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import type { GQLRequest, GQLResponse } from './types';
|
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
import {
|
|
All,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpException,
|
|
HttpStatus,
|
|
Inject,
|
|
Query,
|
|
Req,
|
|
Res,
|
|
} from '@nestjs/common';
|
|
import type { Cache } from 'cache-manager';
|
|
import { FastifyReply, FastifyRequest } from 'fastify';
|
|
import { env } from 'src/config/env';
|
|
import { extractDocumentId, getQueryType } from 'src/utils/query';
|
|
import { getQueryTTL } from './lib/utils';
|
|
|
|
type RedisStore = Omit<Cache, 'set'> & {
|
|
set: (key: string, value: unknown, { ttl }: { ttl: number }) => Promise<void>;
|
|
};
|
|
|
|
@Controller('api')
|
|
export class ProxyController {
|
|
constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: RedisStore) {}
|
|
|
|
@All('/graphql')
|
|
public async graphql(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
|
|
const { operationName, query, variables } = req.body as GQLRequest;
|
|
|
|
const queryType = getQueryType(query);
|
|
|
|
const key = `${operationName} ${JSON.stringify(variables)}`;
|
|
|
|
if (queryType.action === 'query') {
|
|
const cached = await this.cacheManager.get(key);
|
|
if (cached) return reply.send(cached);
|
|
}
|
|
|
|
const response = await fetch(env.URL_GRAPHQL, {
|
|
body: JSON.stringify({ operationName, query, variables }),
|
|
headers: {
|
|
Authorization: req.headers.authorization,
|
|
'Content-Type': 'application/json',
|
|
Cookie: req.headers.cookie,
|
|
},
|
|
method: req.method,
|
|
});
|
|
|
|
const data = (await response.json()) as GQLResponse;
|
|
|
|
if (!response.ok || data?.error || data?.errors?.length)
|
|
throw new HttpException(
|
|
response.statusText,
|
|
response.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
|
|
if (queryType.action === 'mutation' && queryType.entity) {
|
|
const documentId = extractDocumentId(data);
|
|
const keys = await this.cacheManager.store.keys(`*${queryType.entity}*`);
|
|
for (const key of keys) {
|
|
if (key.includes(documentId)) {
|
|
await this.cacheManager.del(key);
|
|
|
|
// console.log(`🗑 Cache invalidated (by key): ${key}`);
|
|
continue;
|
|
}
|
|
|
|
const value = await this.cacheManager.get(key);
|
|
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
if (serialized?.includes(documentId)) {
|
|
await this.cacheManager.del(key);
|
|
|
|
// console.log(`🗑 Cache invalidated (by value): ${key}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const ttl = getQueryTTL(operationName);
|
|
if (queryType.action === 'query' && data && ttl !== false)
|
|
await this.cacheManager.set(key, data, { ttl });
|
|
|
|
return reply.send(data);
|
|
}
|
|
|
|
@Get('/get-queries')
|
|
public async getQueriesList(@Res() reply: FastifyReply) {
|
|
const keys: string[] = await this.cacheManager.store.keys('*');
|
|
|
|
const entries = await Promise.all(
|
|
keys.map(async (key) => {
|
|
try {
|
|
const value = await this.cacheManager.get(key);
|
|
return { key, value };
|
|
} catch (e) {
|
|
return { key, error: e.message };
|
|
}
|
|
}),
|
|
);
|
|
|
|
return reply.send(entries);
|
|
}
|
|
|
|
@Delete('/delete-query')
|
|
public async deleteQuery(@Query('queryKey') queryKey: string, @Res() reply: FastifyReply) {
|
|
try {
|
|
await this.cacheManager.del(queryKey);
|
|
|
|
return reply.send('ok');
|
|
} catch (error) {
|
|
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
@Delete('/reset')
|
|
public async reset(@Res() reply: FastifyReply) {
|
|
try {
|
|
await this.cacheManager.reset();
|
|
|
|
return reply.send('ok');
|
|
} catch (error) {
|
|
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
@Get('/get-query')
|
|
public async getQueryValue(@Query('queryKey') queryKey: string, @Res() reply: FastifyReply) {
|
|
try {
|
|
const value = await this.cacheManager.get(queryKey);
|
|
|
|
return reply.send(value);
|
|
} catch (error) {
|
|
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
}
|