diff --git a/Dockerfile b/Dockerfile index 49ef8b47..393dd9e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Install dependencies only when needed -FROM node:18-alpine AS deps +FROM node:22-alpine AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /app @@ -9,7 +9,7 @@ RUN yarn config set network-timeout 300000 RUN yarn install --frozen-lockfile # Rebuild the source code only when needed -FROM node:18-alpine AS builder +FROM node:22-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . @@ -26,7 +26,7 @@ ENV NEXT_TELEMETRY_DISABLED 1 RUN yarn build-docker # Production image, copy all the files and run next -FROM node:18-alpine AS runner +FROM node:22-alpine AS runner WORKDIR /app ARG NODE_OPTIONS diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index 415b5a8e..2892eefd 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -194,5 +194,5 @@ export async function POST(request: Request) { const token = createToken({ websiteId, sessionId, visitId, iat }, secret()); - return json({ cache: token, websiteId, sessionId, visitId, iat }); + return json({ cache: token }); } diff --git a/src/components/hooks/useApi.ts b/src/components/hooks/useApi.ts index be386a29..d8a05d92 100644 --- a/src/components/hooks/useApi.ts +++ b/src/components/hooks/useApi.ts @@ -2,16 +2,16 @@ import { useCallback } from 'react'; import * as reactQuery from '@tanstack/react-query'; import { getClientAuthToken } from '@/lib/client'; import { SHARE_TOKEN_HEADER } from '@/lib/constants'; -import { httpGet, httpPost, httpPut, httpDelete } from '@/lib/fetch'; +import { httpGet, httpPost, httpPut, httpDelete, FetchResponse } from '@/lib/fetch'; import useStore from '@/store/app'; const selector = (state: { shareToken: { token?: string } }) => state.shareToken; -async function handleResponse(data: any): Promise { - if (data.error) { - return Promise.reject(new Error(data.error)); +async function handleResponse(res: FetchResponse): Promise { + if (!res.ok) { + return Promise.reject(new Error(res.error)); } - return Promise.resolve(data); + return Promise.resolve(res.data); } function handleError(err: Error | string) { diff --git a/src/components/hooks/useCountryNames.ts b/src/components/hooks/useCountryNames.ts index 691167e1..12f2f0dd 100644 --- a/src/components/hooks/useCountryNames.ts +++ b/src/components/hooks/useCountryNames.ts @@ -10,7 +10,7 @@ export function useCountryNames(locale: string) { const [list, setList] = useState(countryNames[locale] || enUS); async function loadData(locale: string) { - const data = await httpGet(`${process.env.basePath || ''}/intl/country/${locale}.json`); + const { data } = await httpGet(`${process.env.basePath || ''}/intl/country/${locale}.json`); if (data) { countryNames[locale] = data; diff --git a/src/components/hooks/useLanguageNames.ts b/src/components/hooks/useLanguageNames.ts index d00b0968..8c28d560 100644 --- a/src/components/hooks/useLanguageNames.ts +++ b/src/components/hooks/useLanguageNames.ts @@ -10,7 +10,7 @@ export function useLanguageNames(locale) { const [list, setList] = useState(languageNames[locale] || enUS); async function loadData(locale) { - const data = await httpGet(`${process.env.basePath || ''}/intl/language/${locale}.json`); + const { data } = await httpGet(`${process.env.basePath || ''}/intl/language/${locale}.json`); if (data) { languageNames[locale] = data; diff --git a/src/components/hooks/useLocale.ts b/src/components/hooks/useLocale.ts index 30f78037..863b20a5 100644 --- a/src/components/hooks/useLocale.ts +++ b/src/components/hooks/useLocale.ts @@ -20,7 +20,9 @@ export function useLocale() { const dateLocale = getDateLocale(locale); async function loadMessages(locale: string) { - messages[locale] = await httpGet(`${process.env.basePath || ''}/intl/messages/${locale}.json`); + const { data } = await httpGet(`${process.env.basePath || ''}/intl/messages/${locale}.json`); + + messages[locale] = data; } async function saveLocale(value: string) { diff --git a/src/components/metrics/MetricsTable.tsx b/src/components/metrics/MetricsTable.tsx index 33b457b5..616262cb 100644 --- a/src/components/metrics/MetricsTable.tsx +++ b/src/components/metrics/MetricsTable.tsx @@ -72,7 +72,7 @@ export function MetricsTable({ return filter(arr); }, items); } else { - items = dataFilter(data); + items = dataFilter(items); } } diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 13abde9d..7b6291c0 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -2,7 +2,7 @@ import { ClickHouseClient, createClient } from '@clickhouse/client'; import { formatInTimeZone } from 'date-fns-tz'; import debug from 'debug'; import { CLICKHOUSE } from '@/lib/db'; -import { getWebsite } from '@/queries/index'; +import { getWebsite } from '@/queries'; import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants'; import { maxDate } from './date'; import { filtersToArray } from './params'; @@ -95,7 +95,7 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}) arr.push(`and ${mapFilter(column, operator, name)}`); if (name === 'referrer') { - arr.push('and referrer_domain != {websiteDomain:String}'); + arr.push('and referrer_domain != hostname'); } } @@ -157,7 +157,7 @@ async function pagedQuery( ) { const { page = 1, pageSize, orderBy, sortDescending = false } = pageParams; const size = +pageSize || DEFAULT_PAGE_SIZE; - const offset = +size * (page - 1); + const offset = +size * (+page - 1); const direction = sortDescending ? 'desc' : 'asc'; const statements = [ diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 754da56f..ee9e160f 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,6 +1,18 @@ import { buildUrl } from '@/lib/url'; -export async function request(method: string, url: string, body?: string, headers: object = {}) { +export interface FetchResponse { + ok: boolean; + status: number; + data?: any; + error?: any; +} + +export async function request( + method: string, + url: string, + body?: string, + headers: object = {}, +): Promise { return fetch(url, { method, cache: 'no-cache', @@ -10,7 +22,16 @@ export async function request(method: string, url: string, body?: string, header ...headers, }, body, - }).then(res => res.json()); + }).then(async res => { + const data = await res.json(); + + return { + ok: res.ok, + status: res.status, + data: res.ok ? data : undefined, + error: res.ok ? undefined : data, + }; + }); } export async function httpGet(url: string, params: object = {}, headers: object = {}) { diff --git a/src/queries/sql/events/getEventMetrics.ts b/src/queries/sql/events/getEventMetrics.ts index d06789f4..c961c55b 100644 --- a/src/queries/sql/events/getEventMetrics.ts +++ b/src/queries/sql/events/getEventMetrics.ts @@ -86,9 +86,5 @@ async function clickhouseQuery( `; } - return rawQuery(sql, params).then(a => { - return Object.values(a).map(a => { - return { x: a.x, t: a.t, y: Number(a.y) }; - }); - }); + return rawQuery(sql, params); } diff --git a/src/queries/sql/events/getEventUsage.ts b/src/queries/sql/events/getEventUsage.ts index 0e1806d6..9ff69a07 100644 --- a/src/queries/sql/events/getEventUsage.ts +++ b/src/queries/sql/events/getEventUsage.ts @@ -30,9 +30,5 @@ function clickhouseQuery( startDate, endDate, }, - ).then(a => { - return Object.values(a).map(a => { - return { websiteId: a.websiteId, count: Number(a.count) }; - }); - }); + ); } diff --git a/src/queries/sql/events/getWebsiteEvents.ts b/src/queries/sql/events/getWebsiteEvents.ts index 5559d5bd..6fe7a0a1 100644 --- a/src/queries/sql/events/getWebsiteEvents.ts +++ b/src/queries/sql/events/getWebsiteEvents.ts @@ -14,7 +14,7 @@ export function getWebsiteEvents( async function relationalQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { const { pagedRawQuery, parseFilters } = prisma; - const { query } = pageParams; + const { search } = pageParams; const { filterQuery, params } = await parseFilters(websiteId, { ...filters, }); @@ -43,16 +43,16 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar and created_at between {{startDate}} and {{endDate}} ${filterQuery} ${ - query - ? `and ((event_name ${like} {{query}} and event_type = 2) - or (url_path ${like} {{query}} and event_type = 1))` + search + ? `and ((event_name ${like} {{search}} and event_type = 2) + or (url_path ${like} {{search}} and event_type = 1))` : '' } order by created_at desc limit 1000) select * from events `, - { ...params, query: `%${query}%` }, + { ...params, query: `%${search}%` }, pageParams, ); } @@ -60,7 +60,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar async function clickhouseQuery(websiteId: string, filters: QueryFilters, pageParams?: PageParams) { const { pagedQuery, parseFilters } = clickhouse; const { params, dateQuery, filterQuery } = await parseFilters(websiteId, filters); - const { query } = pageParams; + const { search } = pageParams; return pagedQuery( ` @@ -83,16 +83,16 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar ${dateQuery} ${filterQuery} ${ - query - ? `and ((positionCaseInsensitive(event_name, {query:String}) > 0 and event_type = 2) - or (positionCaseInsensitive(url_path, {query:String}) > 0 and event_type = 1))` + search + ? `and ((positionCaseInsensitive(event_name, {search:String}) > 0 and event_type = 2) + or (positionCaseInsensitive(url_path, {search:String}) > 0 and event_type = 1))` : '' } order by created_at desc limit 1000) select * from events `, - { ...params, query }, + { ...params, search }, pageParams, ); } diff --git a/src/queries/sql/getChannelMetrics.ts b/src/queries/sql/getChannelMetrics.ts index a7591a80..a2223870 100644 --- a/src/queries/sql/getChannelMetrics.ts +++ b/src/queries/sql/getChannelMetrics.ts @@ -51,9 +51,5 @@ async function clickhouseQuery( order by visitors desc `; - return rawQuery(sql, params).then(a => { - return Object.values(a).map(a => { - return { ...a, visitors: Number(a.visitors) }; - }); - }); + return rawQuery(sql, params); } diff --git a/src/queries/sql/getRealtimeData.ts b/src/queries/sql/getRealtimeData.ts index e07dfc31..a9b06814 100644 --- a/src/queries/sql/getRealtimeData.ts +++ b/src/queries/sql/getRealtimeData.ts @@ -1,4 +1,4 @@ -import { getPageviewStats, getRealtimeActivity, getSessionStats } from '@/queries/index'; +import { getPageviewStats, getRealtimeActivity, getSessionStats } from '@/queries'; function increment(data: object, key: string) { if (key) { diff --git a/src/queries/sql/pageviews/getPageviewMetrics.ts b/src/queries/sql/pageviews/getPageviewMetrics.ts index f6041929..4a0d3361 100644 --- a/src/queries/sql/pageviews/getPageviewMetrics.ts +++ b/src/queries/sql/pageviews/getPageviewMetrics.ts @@ -40,7 +40,7 @@ async function relationalQuery( let entryExitQuery = ''; let excludeDomain = ''; if (column === 'referrer_domain') { - excludeDomain = `and website_event.referrer_domain != {{websiteDomain}} + excludeDomain = `and website_event.referrer_domain != website_event.hostname and website_event.referrer_domain is not null`; } @@ -175,9 +175,5 @@ async function clickhouseQuery( `; } - return rawQuery(sql, params).then((result: any) => { - return Object.values(result).map((a: any) => { - return { x: a.x, y: Number(a.y) }; - }); - }); + return rawQuery(sql, params); } diff --git a/src/queries/sql/reports/getInsights.ts b/src/queries/sql/reports/getInsights.ts index 7178072e..d7cdc283 100644 --- a/src/queries/sql/reports/getInsights.ts +++ b/src/queries/sql/reports/getInsights.ts @@ -115,18 +115,7 @@ async function clickhouseQuery( limit 500 `, params, - ).then(a => { - return Object.values(a).map(a => { - return { - ...a, - views: Number(a.views), - visitors: Number(a.visitors), - visits: Number(a.visits), - bounces: Number(a.bounces), - totaltime: Number(a.totaltime), - }; - }); - }); + ); } function parseFields(fields: { name: any }[]) { diff --git a/src/queries/sql/sessions/getSessionMetrics.ts b/src/queries/sql/sessions/getSessionMetrics.ts index e3bd1bba..010989b5 100644 --- a/src/queries/sql/sessions/getSessionMetrics.ts +++ b/src/queries/sql/sessions/getSessionMetrics.ts @@ -115,9 +115,5 @@ async function clickhouseQuery( `; } - return rawQuery(sql, params).then(a => { - return Object.values(a).map(a => { - return { x: a.x, y: Number(a.y), country: a.country }; - }); - }); + return rawQuery(sql, params); }