apps/web: create api/crm/graphql

This commit is contained in:
vchikalkin 2024-04-12 12:59:02 +03:00
parent 5d1aa99d75
commit 00378ece8d
10 changed files with 173 additions and 8 deletions

View File

@ -0,0 +1,62 @@
import { seconds } from '@/utils/time';
export const queryTTL: Record<string, number | false> = {
GetAddProductType: seconds().fromHours(12),
GetAddproductTypes: seconds().fromHours(12),
GetAgent: seconds().fromHours(12),
GetBrand: seconds().fromHours(3),
GetBrands: seconds().fromHours(3),
GetCoefficients: seconds().fromHours(12),
GetConfiguration: seconds().fromHours(3),
GetConfigurations: seconds().fromMinutes(15),
GetCurrencyChanges: seconds().fromHours(1),
GetDealer: seconds().fromHours(1),
GetDealerPerson: seconds().fromHours(1),
GetDealerPersons: seconds().fromHours(1),
GetDealers: seconds().fromMinutes(15),
GetFuelCards: seconds().fromHours(12),
GetGPSBrands: seconds().fromHours(24),
GetGPSModels: seconds().fromHours(24),
GetImportProgram: seconds().fromHours(12),
GetInsNSIBTypes: seconds().fromHours(12),
GetInsuranceCompanies: seconds().fromHours(12),
GetInsuranceCompany: seconds().fromHours(12),
GetLead: false,
GetLeadUrl: seconds().fromHours(12),
GetLeads: false,
GetLeaseObjectType: seconds().fromHours(24),
GetLeaseObjectTypes: seconds().fromHours(24),
GetLeasingWithoutKaskoTypes: seconds().fromHours(12),
GetModel: seconds().fromHours(3),
GetModels: seconds().fromMinutes(15),
GetOpportunities: false,
GetOpportunity: false,
GetOpportunityUrl: seconds().fromHours(12),
GetProduct: seconds().fromHours(12),
GetProducts: seconds().fromHours(12),
GetQuote: false,
GetQuoteData: false,
GetQuoteUrl: seconds().fromHours(12),
GetQuotes: false,
GetRate: seconds().fromHours(12),
GetRates: seconds().fromHours(12),
GetRegion: seconds().fromHours(24),
GetRegions: seconds().fromHours(24),
GetRegistrationTypes: seconds().fromHours(12),
GetRewardCondition: seconds().fromHours(1),
GetRewardConditions: seconds().fromHours(1),
GetRoles: seconds().fromHours(12),
GetSotCoefficientType: seconds().fromHours(12),
GetSubsidies: seconds().fromHours(12),
GetSubsidy: seconds().fromHours(12),
GetSystemUser: seconds().fromHours(12),
GetTarif: seconds().fromHours(12),
GetTarifs: seconds().fromHours(12),
GetTechnicalCards: seconds().fromHours(12),
GetTelematicTypes: seconds().fromHours(12),
GetTown: seconds().fromHours(24),
GetTowns: seconds().fromHours(24),
GetTrackerTypes: seconds().fromHours(12),
GetTransactionCurrencies: seconds().fromHours(12),
GetTransactionCurrency: seconds().fromHours(12),
};

View File

@ -3,6 +3,11 @@ const { z } = require('zod');
const envSchema = z.object({
BASE_PATH: z.string().optional().default(''),
PORT: z.string().optional(),
REDIS_HOST: z.string(),
REDIS_PORT: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default('6379'),
SENTRY_AUTH_TOKEN: z.string(),
SENTRY_DSN: z.string(),
SENTRY_ENVIRONMENT: z.string(),
@ -16,7 +21,6 @@ const envSchema = z.object({
URL_CRM_CREATEKP_DIRECT: z.string(),
URL_CRM_DOWNLOADKP_BASE: z.string(),
URL_CRM_GRAPHQL_DIRECT: z.string(),
URL_CRM_GRAPHQL_PROXY: z.string().default('http://api:3001/proxy/graphql'),
URL_ELT_KASKO_DIRECT: z.string(),
URL_ELT_OSAGO_DIRECT: z.string(),
URL_GET_USER_DIRECT: z.string(),

View File

@ -21,7 +21,7 @@ function getUrls() {
PORT,
URL_ELT_KASKO_DIRECT,
URL_ELT_OSAGO_DIRECT,
URL_CRM_GRAPHQL_PROXY,
URL_CRM_GRAPHQL_DIRECT,
URL_CACHE_GET_QUERIES_DIRECT,
URL_CACHE_DELETE_QUERY_DIRECT,
URL_CACHE_RESET_QUERIES_DIRECT,
@ -41,7 +41,7 @@ function getUrls() {
URL_CORE_FINGAP: URL_CORE_FINGAP_DIRECT,
URL_CRM_CREATEKP: URL_CRM_CREATEKP_DIRECT,
URL_CRM_DOWNLOADKP: withBasePath(urls.URL_CRM_DOWNLOADKP_PROXY),
URL_CRM_GRAPHQL: URL_CRM_GRAPHQL_PROXY,
URL_CRM_GRAPHQL: URL_CRM_GRAPHQL_DIRECT,
URL_ELT_KASKO: URL_ELT_KASKO_DIRECT,
URL_ELT_OSAGO: URL_ELT_OSAGO_DIRECT,
URL_GET_USER: URL_GET_USER_DIRECT,

View File

@ -8,7 +8,7 @@ module.exports = {
URL_CORE_FINGAP_PROXY: '/api/core/fingap',
URL_CRM_CREATEKP_PROXY: '/api/crm/create-kp',
URL_CRM_DOWNLOADKP_PROXY: '/api/crm/download-kp',
URL_CRM_GRAPHQL_PROXY: '/api/graphql/crm',
URL_CRM_GRAPHQL_PROXY: '/api/crm/graphql',
URL_ELT_KASKO_PROXY: '/api/elt/kasko',
URL_ELT_OSAGO_PROXY: '/api/elt/osago',
URL_GET_USER_PROXY: '/api/auth/user',

View File

@ -49,10 +49,6 @@ module.exports = withSentryConfig(
async rewrites() {
return [
{
destination: env.URL_CRM_GRAPHQL_PROXY + '/:path*',
source: urls.URL_CRM_GRAPHQL_PROXY + '/:path*',
},
{
destination: env.URL_CRM_DOWNLOADKP_BASE + '/:path*',
source: urls.URL_CRM_DOWNLOADKP_PROXY + '/:path*',

View File

@ -26,6 +26,7 @@
"@trpc/server": "^10.45.1",
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"ioredis": "^5.3.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"modern-normalize": "^2.0.0",

View File

@ -0,0 +1,50 @@
import { queryTTL } from '@/config/graphql/ttl';
import getUrls from '@/config/urls';
import { createRedisInstance } from '@/redis/client';
import { HttpError } from '@/utils/error';
import type { ApolloQueryResult, GraphQLRequest } from '@apollo/client';
import type { NextApiRequest, NextApiResponse } from 'next';
const { URL_CRM_GRAPHQL } = getUrls();
const redis = createRedisInstance();
function getHeaders(req: NextApiRequest) {
const headers = new Headers();
Object.keys(req.headers).forEach((key) => {
const value = req.headers[key];
if (value !== undefined && typeof value === 'string') headers.set(key, value);
});
return headers;
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const headers = getHeaders(req);
const { operationName, variables } = req.body as GraphQLRequest;
const key = `${operationName} ${JSON.stringify(variables)}`;
const cached = await redis.get(key);
if (cached) return res.send({ ...JSON.parse(cached), cached: true });
const response = await fetch(URL_CRM_GRAPHQL, {
body: JSON.stringify(req.body),
headers,
method: req.method,
});
const data = (await response.json()) as ApolloQueryResult<unknown>;
if (!response.ok || data?.error || data?.errors?.length) {
throw new HttpError(response.statusText, response.status || 500);
}
if (operationName) {
const ttl = queryTTL[operationName];
if (data && ttl !== false) await redis.set(key, JSON.stringify(data), 'EX', ttl);
}
return res.send(data);
}

36
apps/web/redis/client.ts Normal file
View File

@ -0,0 +1,36 @@
/* eslint-disable no-console */
import envSchema from '@/config/schema/env';
import type { RedisOptions } from 'ioredis';
import { Redis } from 'ioredis';
const { REDIS_HOST, REDIS_PORT } = envSchema.parse(process.env);
export function createRedisInstance() {
try {
const options: RedisOptions = {
enableAutoPipelining: true,
host: REDIS_HOST,
lazyConnect: true,
maxRetriesPerRequest: 0,
port: REDIS_PORT,
retryStrategy: (times: number) => {
if (times > 3) {
throw new Error(`[Redis] Could not connect after ${times} attempts`);
}
return Math.min(times * 200, 1000);
},
showFriendlyErrorStack: true,
};
const redis = new Redis(options);
redis.on('error', (error: unknown) => {
console.warn('[Redis] Error connecting', error);
});
return redis;
} catch (error) {
throw new Error(`[Redis] Could not create a Redis instance. ${error}`);
}
}

13
apps/web/utils/time.ts Normal file
View File

@ -0,0 +1,13 @@
export function seconds() {
return {
fromDays(days: number) {
return days * 24 * 60 * 60;
},
fromHours(hours: number) {
return hours * 60 * 60;
},
fromMinutes(minutes: number) {
return minutes * 60;
},
};
}

3
pnpm-lock.yaml generated
View File

@ -156,6 +156,9 @@ importers:
dayjs:
specifier: ^1.11.10
version: 1.11.10
ioredis:
specifier: ^5.3.2
version: 5.3.2
mobx:
specifier: ^6.12.0
version: 6.12.0