🙊 escape sql strings
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
"pino-pretty": "^10.3.1",
|
||||
"ramda": "^0.29.1",
|
||||
"sharp": "^0.33.2",
|
||||
"sqlstring": "^2.3.3",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"url-metadata": "^4.1.0",
|
||||
"uuid": "^9.0.1"
|
||||
@@ -34,6 +35,7 @@
|
||||
"@openpanel/sdk": "workspace:*",
|
||||
"@openpanel/tsconfig": "workspace:*",
|
||||
"@types/ramda": "^0.29.6",
|
||||
"@types/sqlstring": "^2.3.2",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent';
|
||||
import { isSameDomain, parsePath } from '@/utils/url';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { omit } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { generateDeviceId, getTime, toISOString } from '@openpanel/common';
|
||||
@@ -103,7 +104,7 @@ export async function postEvent(
|
||||
const [event] = await withTiming(
|
||||
'Get last event (server-event)',
|
||||
getEvents(
|
||||
`SELECT * FROM events WHERE name = 'screen_view' AND profile_id = '${profileId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM events WHERE name = 'screen_view' AND profile_id = ${escape(profileId)} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`
|
||||
)
|
||||
);
|
||||
|
||||
@@ -212,7 +213,7 @@ export async function postEvent(
|
||||
'Get session start event',
|
||||
Promise.all([
|
||||
getEvents(
|
||||
`SELECT * FROM events WHERE name = 'session_start' AND device_id = '${deviceId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM events WHERE name = 'session_start' AND device_id = ${escape(deviceId)} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`
|
||||
),
|
||||
findJobByPrefix(eventsQueue, `event:${projectId}:${deviceId}:`),
|
||||
])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { escape } from 'sqlstring';
|
||||
import type * as WebSocket from 'ws';
|
||||
|
||||
import { getSafeJson } from '@openpanel/common';
|
||||
@@ -19,7 +20,7 @@ export async function test(
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const [event] = await getEvents(
|
||||
`SELECT * FROM events WHERE project_id = '${req.params.projectId}' AND name = 'screen_view' LIMIT 1`
|
||||
`SELECT * FROM events WHERE project_id = ${escape(req.params.projectId)} AND name = 'screen_view' LIMIT 1`
|
||||
);
|
||||
if (!event) {
|
||||
return reply.status(404).send('No event found');
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"short-unique-id": "^5.0.3",
|
||||
"slugify": "^1.6.6",
|
||||
"sonner": "^1.4.0",
|
||||
"sqlstring": "^2.3.3",
|
||||
"superjson": "^1.13.3",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -109,6 +110,7 @@
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@types/request-ip": "^0.0.41",
|
||||
"@types/sqlstring": "^2.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Widget } from '@/components/widget';
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { db, getEvents } from '@openpanel/db';
|
||||
|
||||
@@ -21,7 +22,7 @@ export default async function EventConversionsListServer({ projectId }: Props) {
|
||||
}
|
||||
|
||||
const events = await getEvents(
|
||||
`SELECT * FROM events WHERE project_id = '${projectId}' AND name IN (${conversions.map((c) => `'${c.name}'`).join(', ')}) ORDER BY created_at DESC LIMIT 20;`,
|
||||
`SELECT * FROM events WHERE project_id = ${escape(projectId)} AND name IN (${conversions.map((c) => escape(c.name)).join(', ')}) ORDER BY created_at DESC LIMIT 20;`,
|
||||
{
|
||||
profile: true,
|
||||
meta: true,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { chQuery } from '@openpanel/db';
|
||||
|
||||
@@ -20,7 +21,7 @@ export default async function ProfileLastSeenServer({ projectId }: Props) {
|
||||
// Days since last event from users
|
||||
// group by days
|
||||
const res = await chQuery<Row>(
|
||||
`SELECT age('days',created_at, now()) as days, count(distinct profile_id) as count FROM events where project_id = '${projectId}' group by days order by days ASC`
|
||||
`SELECT age('days',created_at, now()) as days, count(distinct profile_id) as count FROM events where project_id = ${escape(projectId)} group by days order by days ASC`
|
||||
);
|
||||
|
||||
const take = 18;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Widget, WidgetHead } from '@/components/widget';
|
||||
import { WidgetTable } from '@/components/widget-table';
|
||||
import { getProfileName } from '@/utils/getters';
|
||||
import Link from 'next/link';
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { chQuery, getProfiles } from '@openpanel/db';
|
||||
|
||||
@@ -19,7 +20,7 @@ export default async function ProfileTopServer({
|
||||
// Days since last event from users
|
||||
// group by days
|
||||
const res = await chQuery<{ profile_id: string; count: number }>(
|
||||
`SELECT profile_id, count(*) as count from events where profile_id != '' and project_id = '${projectId}' group by profile_id order by count() DESC LIMIT 10`
|
||||
`SELECT profile_id, count(*) as count from events where profile_id != '' and project_id = ${escape(projectId)} group by profile_id order by count() DESC LIMIT 10`
|
||||
);
|
||||
const profiles = await getProfiles({ ids: res.map((r) => r.profile_id) });
|
||||
const list = res.map((item) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { shortNumber } from '@/hooks/useNumerFormatter';
|
||||
import Link from 'next/link';
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import type { IServiceProject } from '@openpanel/db';
|
||||
import { chQuery } from '@openpanel/db';
|
||||
@@ -13,19 +14,19 @@ export async function ProjectCard({
|
||||
}: IServiceProject) {
|
||||
const [chart, [data]] = await Promise.all([
|
||||
chQuery<{ value: number; date: string }>(
|
||||
`SELECT countDistinct(profile_id) as value, toStartOfDay(created_at) as date FROM events WHERE project_id = '${id}' AND name = 'session_start' AND created_at >= now() - interval '1 month' GROUP BY date ORDER BY date ASC`
|
||||
`SELECT countDistinct(profile_id) as value, toStartOfDay(created_at) as date FROM events WHERE project_id = ${escape(id)} AND name = 'session_start' AND created_at >= now() - interval '1 month' GROUP BY date ORDER BY date ASC`
|
||||
),
|
||||
chQuery<{ total: number; month: number; day: number }>(
|
||||
`
|
||||
SELECT
|
||||
(
|
||||
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = '${id}'
|
||||
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = ${escape(id)}
|
||||
) as total,
|
||||
(
|
||||
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = '${id}' AND created_at >= now() - interval '1 month'
|
||||
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = ${escape(id)} AND created_at >= now() - interval '1 month'
|
||||
) as month,
|
||||
(
|
||||
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = '${id}' AND created_at >= now() - interval '1 day'
|
||||
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = ${escape(id)} AND created_at >= now() - interval '1 day'
|
||||
) as day
|
||||
`
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { round } from '@/utils/math';
|
||||
import { subDays } from 'date-fns';
|
||||
import * as mathjs from 'mathjs';
|
||||
import { repeat, reverse, sort } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { alphabetIds, NOT_SET_VALUE } from '@openpanel/constants';
|
||||
import {
|
||||
@@ -430,7 +431,7 @@ export async function getFunnelData({ projectId, ...payload }: IChartInput) {
|
||||
const funnels = payload.events.map((event) => {
|
||||
const { sb, getWhere } = createSqlBuilder();
|
||||
sb.where = getEventFiltersWhereClause(event.filters);
|
||||
sb.where.name = `name = '${event.name}'`;
|
||||
sb.where.name = `name = ${escape(event.name)}`;
|
||||
return getWhere().replace('WHERE ', '');
|
||||
});
|
||||
|
||||
@@ -438,7 +439,7 @@ export async function getFunnelData({ projectId, ...payload }: IChartInput) {
|
||||
session_id,
|
||||
windowFunnel(6048000000000000,'strict_increase')(toUnixTimestamp(created_at), ${funnels.join(', ')}) AS level
|
||||
FROM events
|
||||
WHERE (project_id = '${projectId}' AND created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')
|
||||
WHERE (project_id = ${escape(projectId)} AND created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')
|
||||
GROUP BY session_id`;
|
||||
|
||||
const sql = `SELECT level, count() AS count FROM (${innerSql}) GROUP BY level ORDER BY level DESC`;
|
||||
@@ -446,7 +447,7 @@ export async function getFunnelData({ projectId, ...payload }: IChartInput) {
|
||||
const [funnelRes, sessionRes] = await Promise.all([
|
||||
chQuery<{ level: number; count: number }>(sql),
|
||||
chQuery<{ count: number }>(
|
||||
`SELECT count(name) as count FROM events WHERE project_id = '${projectId}' AND name = 'session_start' AND (created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')`
|
||||
`SELECT count(name) as count FROM events WHERE project_id = ${escape(projectId)} AND name = 'session_start' AND (created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')`
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@/trpc/api/trpc';
|
||||
import { average, max, min, round, sum } from '@/utils/math';
|
||||
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { chQuery, createSqlBuilder } from '@openpanel/db';
|
||||
@@ -60,7 +61,7 @@ export const chartRouter = createTRPCRouter({
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ input: { projectId } }) => {
|
||||
const events = await chQuery<{ name: string }>(
|
||||
`SELECT DISTINCT name FROM events WHERE project_id = '${projectId}'`
|
||||
`SELECT DISTINCT name FROM events WHERE project_id = ${escape(projectId)}`
|
||||
);
|
||||
|
||||
return [
|
||||
@@ -76,8 +77,8 @@ export const chartRouter = createTRPCRouter({
|
||||
.query(async ({ input: { projectId, event } }) => {
|
||||
const events = await chQuery<{ keys: string[] }>(
|
||||
`SELECT distinct mapKeys(properties) as keys from events where ${
|
||||
event && event !== '*' ? `name = '${event}' AND ` : ''
|
||||
} project_id = '${projectId}';`
|
||||
event && event !== '*' ? `name = ${escape(event)} AND ` : ''
|
||||
} project_id = ${escape(projectId)};`
|
||||
);
|
||||
|
||||
const properties = events
|
||||
@@ -122,14 +123,14 @@ export const chartRouter = createTRPCRouter({
|
||||
)
|
||||
.query(async ({ input: { event, property, projectId } }) => {
|
||||
const { sb, getSql } = createSqlBuilder();
|
||||
sb.where.project_id = `project_id = '${projectId}'`;
|
||||
sb.where.project_id = `project_id = ${escape(projectId)}`;
|
||||
if (event !== '*') {
|
||||
sb.where.event = `name = '${event}'`;
|
||||
sb.where.event = `name = ${escape(event)}`;
|
||||
}
|
||||
if (property.startsWith('properties.')) {
|
||||
sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, '${property
|
||||
.replace(/^properties\./, '')
|
||||
.replace('.*.', '.%.')}')) as values`;
|
||||
sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
property.replace(/^properties\./, '').replace('.*.', '.%.')
|
||||
)})) as values`;
|
||||
} else {
|
||||
sb.select.values = `distinct ${property} as values`;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
publicProcedure,
|
||||
} from '@/trpc/api/trpc';
|
||||
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { chQuery, createSqlBuilder } from '@openpanel/db';
|
||||
@@ -13,7 +14,7 @@ export const profileRouter = createTRPCRouter({
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ input: { projectId } }) => {
|
||||
const events = await chQuery<{ keys: string[] }>(
|
||||
`SELECT distinct mapKeys(properties) as keys from profiles where project_id = '${projectId}';`
|
||||
`SELECT distinct mapKeys(properties) as keys from profiles where project_id = ${escape(projectId)};`
|
||||
);
|
||||
|
||||
const properties = events
|
||||
@@ -40,11 +41,11 @@ export const profileRouter = createTRPCRouter({
|
||||
.query(async ({ input: { property, projectId } }) => {
|
||||
const { sb, getSql } = createSqlBuilder();
|
||||
sb.from = 'profiles';
|
||||
sb.where.project_id = `project_id = '${projectId}'`;
|
||||
sb.where.project_id = `project_id = ${escape(projectId)}`;
|
||||
if (property.startsWith('properties.')) {
|
||||
sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, '${property
|
||||
.replace(/^properties\./, '')
|
||||
.replace('.*.', '.%.')}')) as values`;
|
||||
sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
property.replace(/^properties\./, '').replace('.*.', '.%.')
|
||||
)})) as values`;
|
||||
} else {
|
||||
sb.select.values = `${property} as values`;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@openpanel/validation": "workspace:*",
|
||||
"@prisma/client": "^5.1.1",
|
||||
"ramda": "^0.29.1",
|
||||
"sqlstring": "^2.3.3",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -28,6 +29,7 @@
|
||||
"@openpanel/tsconfig": "workspace:*",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/ramda": "^0.29.6",
|
||||
"@types/sqlstring": "^2.3.2",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"eslint": "^8.48.0",
|
||||
"prettier": "^3.0.3",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import type {
|
||||
IChartEventFilter,
|
||||
IGetChartDataInput,
|
||||
} from '@openpanel/validation';
|
||||
|
||||
import { formatClickhouseDate } from '../clickhouse-client';
|
||||
import type { SqlBuilderObject } from '../sql-builder';
|
||||
import { createSqlBuilder } from '../sql-builder';
|
||||
|
||||
function log(sql: string) {
|
||||
@@ -25,10 +26,10 @@ export function getChartSql({
|
||||
createSqlBuilder();
|
||||
|
||||
sb.where = getEventFiltersWhereClause(event.filters);
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
if (event.name !== '*') {
|
||||
sb.select.label = `'${event.name}' as label`;
|
||||
sb.where.eventName = `name = '${event.name}'`;
|
||||
sb.select.label = `${escape(event.name)} as label`;
|
||||
sb.where.eventName = `name = ${escape(event.name)}`;
|
||||
}
|
||||
|
||||
sb.select.count = `count(*) as count`;
|
||||
@@ -64,10 +65,10 @@ export function getChartSql({
|
||||
const breakdown = breakdowns[0]!;
|
||||
if (breakdown) {
|
||||
const value = breakdown.name.startsWith('properties.')
|
||||
? `mapValues(mapExtractKeyLike(properties, '${breakdown.name
|
||||
.replace(/^properties\./, '')
|
||||
.replace('.*.', '.%.')}'))`
|
||||
: breakdown.name;
|
||||
? `mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
breakdown.name.replace(/^properties\./, '').replace('.*.', '.%.')
|
||||
)}))`
|
||||
: escape(breakdown.name);
|
||||
sb.select.label = breakdown.name.startsWith('properties.')
|
||||
? `arrayElement(${value}, 1) as label`
|
||||
: `${breakdown.name} as label`;
|
||||
@@ -120,32 +121,32 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
if (value.length === 0) return;
|
||||
|
||||
if (name.startsWith('properties.')) {
|
||||
const whereFrom = `mapValues(mapExtractKeyLike(properties, '${name
|
||||
.replace(/^properties\./, '')
|
||||
.replace('.*.', '.%.')}'))`;
|
||||
const whereFrom = `mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
name.replace(/^properties\./, '').replace('.*.', '.%.')
|
||||
)}))`;
|
||||
|
||||
switch (operator) {
|
||||
case 'is': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x = '${String(val).trim()}'`)
|
||||
.map((val) => `x = ${escape(String(val).trim())}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
case 'isNot': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x != '${String(val).trim()}'`)
|
||||
.map((val) => `x != ${escape(String(val).trim())}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
case 'contains': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x LIKE '%${String(val).trim()}%'`)
|
||||
.map((val) => `x LIKE ${escape(`%${String(val).trim()}%`)}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
case 'doesNotContain': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x NOT LIKE '%${String(val).trim()}%'`)
|
||||
.map((val) => `x NOT LIKE ${escape(`%${String(val).trim()}%`)}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
@@ -154,25 +155,27 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
switch (operator) {
|
||||
case 'is': {
|
||||
where[id] = `${name} IN (${value
|
||||
.map((val) => `'${String(val).trim()}'`)
|
||||
.map((val) => escape(String(val).trim()))
|
||||
.join(', ')})`;
|
||||
break;
|
||||
}
|
||||
case 'isNot': {
|
||||
where[id] = `${name} NOT IN (${value
|
||||
.map((val) => `'${String(val).trim()}'`)
|
||||
.map((val) => escape(String(val).trim()))
|
||||
.join(', ')})`;
|
||||
break;
|
||||
}
|
||||
case 'contains': {
|
||||
where[id] = value
|
||||
.map((val) => `${name} LIKE '%${String(val).trim()}%'`)
|
||||
.map((val) => `${name} LIKE ${escape(`%${String(val).trim()}%`)}`)
|
||||
.join(' OR ');
|
||||
break;
|
||||
}
|
||||
case 'doesNotContain': {
|
||||
where[id] = value
|
||||
.map((val) => `${name} NOT LIKE '%${String(val).trim()}%'`)
|
||||
.map(
|
||||
(val) => `${name} NOT LIKE ${escape(`%${String(val).trim()}%`)}`
|
||||
)
|
||||
.join(' OR ');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { omit, uniq } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { randomSplitName, toDots } from '@openpanel/common';
|
||||
@@ -261,15 +262,15 @@ export async function getEventList({
|
||||
|
||||
sb.limit = take;
|
||||
sb.offset = Math.max(0, (cursor ?? 0) * take);
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
|
||||
if (profileId) {
|
||||
sb.where.deviceId = `device_id IN (SELECT device_id as did FROM openpanel.events WHERE profile_id = '${profileId}' group by did)`;
|
||||
sb.where.deviceId = `device_id IN (SELECT device_id as did FROM openpanel.events WHERE profile_id = ${escape(profileId)} group by did)`;
|
||||
}
|
||||
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((n) => `'${n}'`),
|
||||
events.map((event) => escape(event)),
|
||||
','
|
||||
)})`;
|
||||
}
|
||||
@@ -297,14 +298,14 @@ export async function getEventsCount({
|
||||
filters,
|
||||
}: Omit<GetEventListOptions, 'cursor' | 'take'>) {
|
||||
const { sb, getSql, join } = createSqlBuilder();
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
if (profileId) {
|
||||
sb.where.profileId = `profile_id = '${profileId}'`;
|
||||
sb.where.profileId = `profile_id = ${escape(profileId)}`;
|
||||
}
|
||||
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((n) => `'${n}'`),
|
||||
events.map((event) => escape(event)),
|
||||
','
|
||||
)})`;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { toDots, toObject } from '@openpanel/common';
|
||||
import type { IChartEventFilter } from '@openpanel/validation';
|
||||
|
||||
@@ -11,7 +13,7 @@ export async function getProfileById(id: string) {
|
||||
}
|
||||
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT *, created_at as max_created_at FROM profiles WHERE id = '${id}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT *, created_at as max_created_at FROM profiles WHERE id = ${escape(id)} ORDER BY created_at DESC LIMIT 1`
|
||||
);
|
||||
|
||||
if (!profile) {
|
||||
@@ -53,7 +55,7 @@ export async function getProfiles({ ids }: GetProfilesOptions) {
|
||||
`SELECT
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
WHERE id IN (${ids.map((id) => `'${id}'`).join(',')})
|
||||
WHERE id IN (${ids.map((id) => escape(id)).join(',')})
|
||||
GROUP BY id
|
||||
`
|
||||
);
|
||||
@@ -66,7 +68,7 @@ function getProfileInnerSelect(projectId: string) {
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
GROUP BY id
|
||||
HAVING project_id = '${projectId}')`;
|
||||
HAVING project_id = ${escape(projectId)})`;
|
||||
}
|
||||
|
||||
export async function getProfileList({
|
||||
@@ -120,7 +122,7 @@ export async function getProfilesByExternalId(
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
GROUP BY id
|
||||
HAVING project_id = '${projectId}' AND external_id = '${externalId}'
|
||||
HAVING project_id = ${escape(projectId)} AND external_id = ${escape(externalId)}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -192,7 +194,7 @@ export async function upsertProfile({
|
||||
projectId,
|
||||
}: IServiceUpsertProfile) {
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT * FROM profiles WHERE id = '${id}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM profiles WHERE id = ${escape(id)} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`
|
||||
);
|
||||
|
||||
await ch.insert({
|
||||
|
||||
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
@@ -65,6 +65,9 @@ importers:
|
||||
sharp:
|
||||
specifier: ^0.33.2
|
||||
version: 0.33.2
|
||||
sqlstring:
|
||||
specifier: ^2.3.3
|
||||
version: 2.3.3
|
||||
ua-parser-js:
|
||||
specifier: ^1.0.37
|
||||
version: 1.0.37
|
||||
@@ -90,6 +93,9 @@ importers:
|
||||
'@types/ramda':
|
||||
specifier: ^0.29.6
|
||||
version: 0.29.10
|
||||
'@types/sqlstring':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2
|
||||
'@types/ua-parser-js':
|
||||
specifier: ^0.7.39
|
||||
version: 0.7.39
|
||||
@@ -345,6 +351,9 @@ importers:
|
||||
sonner:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||
sqlstring:
|
||||
specifier: ^2.3.3
|
||||
version: 2.3.3
|
||||
superjson:
|
||||
specifier: ^1.13.3
|
||||
version: 1.13.3
|
||||
@@ -397,6 +406,9 @@ importers:
|
||||
'@types/request-ip':
|
||||
specifier: ^0.0.41
|
||||
version: 0.0.41
|
||||
'@types/sqlstring':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^6.21.0
|
||||
version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
@@ -776,6 +788,9 @@ importers:
|
||||
ramda:
|
||||
specifier: ^0.29.1
|
||||
version: 0.29.1
|
||||
sqlstring:
|
||||
specifier: ^2.3.3
|
||||
version: 2.3.3
|
||||
uuid:
|
||||
specifier: ^9.0.1
|
||||
version: 9.0.1
|
||||
@@ -795,6 +810,9 @@ importers:
|
||||
'@types/ramda':
|
||||
specifier: ^0.29.6
|
||||
version: 0.29.10
|
||||
'@types/sqlstring':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2
|
||||
'@types/uuid':
|
||||
specifier: ^9.0.8
|
||||
version: 9.0.8
|
||||
@@ -7012,6 +7030,10 @@ packages:
|
||||
'@types/mime': 3.0.4
|
||||
'@types/node': 18.19.17
|
||||
|
||||
/@types/sqlstring@2.3.2:
|
||||
resolution: {integrity: sha512-lVRe4Iz9UNgiHelKVo8QlC8fb5nfY8+p+jNQNE+UVsuuVlQnWhyWmQ/wF5pE8Ys6TdjfVpqTG9O9i2vi6E0+Sg==}
|
||||
dev: true
|
||||
|
||||
/@types/stack-trace@0.0.29:
|
||||
resolution: {integrity: sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==}
|
||||
dev: false
|
||||
@@ -15958,6 +15980,11 @@ packages:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
dev: false
|
||||
|
||||
/sqlstring@2.3.3:
|
||||
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/ssri@8.0.1:
|
||||
resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
Reference in New Issue
Block a user