feature(dashboard): add ability to filter out events by profile id and ip (#101)
This commit is contained in:
committed by
GitHub
parent
27ee623584
commit
f4ad97d87d
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "projects" ADD COLUMN "cors" TEXT,
|
||||
ADD COLUMN "crossDomain" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "domain" TEXT,
|
||||
ADD COLUMN "filters" JSONB NOT NULL DEFAULT '[]';
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The `cors` column on the `projects` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "projects" DROP COLUMN "cors",
|
||||
ADD COLUMN "cors" TEXT[] DEFAULT ARRAY[]::TEXT[];
|
||||
@@ -80,6 +80,11 @@ model Project {
|
||||
organizationId String
|
||||
eventsCount Int @default(0)
|
||||
types ProjectType[] @default([])
|
||||
domain String?
|
||||
cors String[] @default([])
|
||||
crossDomain Boolean @default(false)
|
||||
/// [IPrismaProjectFilters]
|
||||
filters Json @default("[]")
|
||||
|
||||
events Event[]
|
||||
profiles Profile[]
|
||||
|
||||
@@ -39,7 +39,7 @@ export const CLICKHOUSE_OPTIONS: NodeClickHouseClientConfigOptions = {
|
||||
|
||||
export const originalCh = createClient({
|
||||
// TODO: remove this after migration
|
||||
url: process.env.CLICKHOUSE_URL_CLUSTER ?? process.env.CLICKHOUSE_URL,
|
||||
url: process.env.CLICKHOUSE_URL_DIRECT ?? process.env.CLICKHOUSE_URL,
|
||||
...CLICKHOUSE_OPTIONS,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { cacheable } from '@openpanel/redis';
|
||||
import type { Client, Prisma } from '../prisma-client';
|
||||
import { db } from '../prisma-client';
|
||||
|
||||
@@ -21,3 +22,16 @@ export async function getClientsByOrganizationId(organizationId: string) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getClientById(
|
||||
id: string,
|
||||
): Promise<IServiceClientWithProject | null> {
|
||||
return db.client.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
project: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const getClientByIdCached = cacheable(getClientById, 60 * 60 * 24);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
IIntegrationConfig,
|
||||
INotificationRuleConfig,
|
||||
IProjectFilters,
|
||||
} from '@openpanel/validation';
|
||||
import type { INotificationPayload } from './services/notification.service';
|
||||
|
||||
@@ -9,5 +10,6 @@ declare global {
|
||||
type IPrismaNotificationRuleConfig = INotificationRuleConfig;
|
||||
type IPrismaIntegrationConfig = IIntegrationConfig;
|
||||
type IPrismaNotificationPayload = INotificationPayload;
|
||||
type IPrismaProjectFilters = IProjectFilters[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { stripTrailingSlash } from '@openpanel/common';
|
||||
import type { Prisma } from '@openpanel/db';
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
@@ -47,8 +46,6 @@ export const clientRouter = createTRPCRouter({
|
||||
name: z.string(),
|
||||
projectId: z.string(),
|
||||
organizationId: z.string(),
|
||||
cors: z.string().nullable(),
|
||||
crossDomain: z.boolean().optional(),
|
||||
type: z.enum(['read', 'write', 'root']).optional(),
|
||||
}),
|
||||
)
|
||||
@@ -59,9 +56,7 @@ export const clientRouter = createTRPCRouter({
|
||||
projectId: input.projectId,
|
||||
name: input.name,
|
||||
type: input.type ?? 'write',
|
||||
cors: input.cors ? stripTrailingSlash(input.cors) : null,
|
||||
secret: await hashPassword(secret),
|
||||
crossDomain: input.crossDomain ?? false,
|
||||
};
|
||||
|
||||
const client = await db.client.create({ data });
|
||||
|
||||
@@ -2,11 +2,14 @@ import { z } from 'zod';
|
||||
|
||||
import {
|
||||
db,
|
||||
getClientById,
|
||||
getClientByIdCached,
|
||||
getId,
|
||||
getProjectByIdCached,
|
||||
getProjectsByOrganizationId,
|
||||
} from '@openpanel/db';
|
||||
|
||||
import { zProject } from '@openpanel/validation';
|
||||
import { getProjectAccess } from '../access';
|
||||
import { TRPCAccessError } from '../errors';
|
||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||
@@ -24,13 +27,12 @@ export const projectRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.input(zProject.partial())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (!input.id) {
|
||||
throw new Error('Project ID is required to update a project');
|
||||
}
|
||||
|
||||
const access = await getProjectAccess({
|
||||
userId: ctx.session.userId,
|
||||
projectId: input.id,
|
||||
@@ -39,30 +41,52 @@ export const projectRouter = createTRPCRouter({
|
||||
if (!access) {
|
||||
throw TRPCAccessError('You do not have access to this project');
|
||||
}
|
||||
|
||||
const res = await db.project.update({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
data: {
|
||||
name: input.name,
|
||||
crossDomain: input.crossDomain,
|
||||
filters: input.filters,
|
||||
cors: input.cors,
|
||||
domain: input.domain,
|
||||
},
|
||||
include: {
|
||||
clients: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await getProjectByIdCached.clear(input.id);
|
||||
await Promise.all([
|
||||
getProjectByIdCached.clear(input.id),
|
||||
res.clients.map((client) => {
|
||||
getClientByIdCached.clear(client.id);
|
||||
}),
|
||||
]);
|
||||
return res;
|
||||
}),
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string().min(1),
|
||||
organizationId: z.string(),
|
||||
}),
|
||||
zProject.omit({ id: true }).merge(
|
||||
z.object({
|
||||
organizationId: z.string(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.mutation(async ({ input: { name, organizationId } }) => {
|
||||
.mutation(async ({ input }) => {
|
||||
return db.project.create({
|
||||
data: {
|
||||
id: await getId('project', name),
|
||||
organizationId,
|
||||
name: name,
|
||||
id: await getId('project', input.name),
|
||||
organizationId: input.organizationId,
|
||||
name: input.name,
|
||||
domain: input.domain,
|
||||
cors: input.cors,
|
||||
crossDomain: input.crossDomain,
|
||||
filters: [],
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -271,3 +271,31 @@ export const zCreateNotificationRule = z.object({
|
||||
sendToEmail: z.boolean(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
export const zProjectFilterIp = z.object({
|
||||
type: z.literal('ip'),
|
||||
ip: z.string(),
|
||||
});
|
||||
export type IProjectFilterIp = z.infer<typeof zProjectFilterIp>;
|
||||
|
||||
export const zProjectFilterProfileId = z.object({
|
||||
type: z.literal('profile_id'),
|
||||
profileId: z.string(),
|
||||
});
|
||||
export type IProjectFilterProfileId = z.infer<typeof zProjectFilterProfileId>;
|
||||
|
||||
export const zProjectFilters = z.discriminatedUnion('type', [
|
||||
zProjectFilterIp,
|
||||
zProjectFilterProfileId,
|
||||
]);
|
||||
export type IProjectFilters = z.infer<typeof zProjectFilters>;
|
||||
|
||||
export const zProject = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().min(1),
|
||||
filters: z.array(zProjectFilters).default([]),
|
||||
domain: z.string().url().or(z.literal('').or(z.null())),
|
||||
cors: z.array(z.string()).default([]),
|
||||
crossDomain: z.boolean().default(false),
|
||||
});
|
||||
export type IProjectEdit = z.infer<typeof zProject>;
|
||||
|
||||
Reference in New Issue
Block a user