refactor packages

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-19 10:55:15 +01:00
parent ae8482c1e3
commit 2f3c5ddf76
142 changed files with 2234 additions and 5507 deletions

101
packages/constants/index.ts Normal file
View File

@@ -0,0 +1,101 @@
export const NOT_SET_VALUE = '(not set)';
export const operators = {
is: 'Is',
isNot: 'Is not',
contains: 'Contains',
doesNotContain: 'Not contains',
} as const;
export const chartTypes = {
linear: 'Linear',
bar: 'Bar',
histogram: 'Histogram',
pie: 'Pie',
metric: 'Metric',
area: 'Area',
map: 'Map',
} as const;
export const lineTypes = {
monotone: 'Monotone',
monotoneX: 'Monotone X',
monotoneY: 'Monotone Y',
linear: 'Linear',
natural: 'Natural',
basis: 'Basis',
step: 'Step',
stepBefore: 'Step before',
stepAfter: 'Step after',
basisClosed: 'Basis closed',
basisOpen: 'Basis open',
bumpX: 'Bump X',
bumpY: 'Bump Y',
bump: 'Bump',
linearClosed: 'Linear closed',
} as const;
export const intervals = {
minute: 'minute',
day: 'day',
hour: 'hour',
month: 'month',
} as const;
export const alphabetIds = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
] as const;
export const timeRanges = {
'30min': '30min',
'1h': '1h',
today: 'today',
'24h': '24h',
'7d': '7d',
'14d': '14d',
'1m': '1m',
'3m': '3m',
'6m': '6m',
'1y': '1y',
} as const;
export const metrics = {
sum: 'sum',
average: 'average',
min: 'min',
max: 'max',
} as const;
export function isMinuteIntervalEnabledByRange(range: keyof typeof timeRanges) {
return range === '30min' || range === '1h';
}
export function isHourIntervalEnabledByRange(range: keyof typeof timeRanges) {
return (
isMinuteIntervalEnabledByRange(range) ||
range === 'today' ||
range === '24h'
);
}
export function getDefaultIntervalByRange(
range: keyof typeof timeRanges
): keyof typeof intervals {
if (range === '30min' || range === '1h') {
return 'minute';
} else if (range === 'today' || range === '24h') {
return 'hour';
} else if (range === '7d' || range === '14d' || range === '1m') {
return 'day';
}
return 'month';
}

View File

@@ -0,0 +1,26 @@
{
"name": "@mixan/constants",
"version": "0.0.1",
"main": "index.ts",
"scripts": {
"lint": "eslint .",
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@mixan/eslint-config": "workspace:*",
"@mixan/prettier-config": "workspace:*",
"@mixan/tsconfig": "workspace:*",
"eslint": "^8.48.0",
"prettier": "^3.0.3",
"prisma": "^5.1.1",
"typescript": "^5.2.2"
},
"eslintConfig": {
"root": true,
"extends": [
"@mixan/eslint-config/base"
]
},
"prettier": "@mixan/prettier-config"
}

View File

@@ -0,0 +1,12 @@
{
"extends": "@mixan/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["."],
"exclude": ["node_modules"]
}

View File

@@ -1,8 +1,14 @@
export * from './src/prisma-client';
export * from './src/prisma-types';
export * from './src/clickhouse-client';
export * from './src/sql-builder';
export * from './src/services/salt';
export * from './src/services/event.service';
export * from './src/services/share.service';
export * from './src/services/chart.service';
export * from './src/services/clients.service';
export * from './src/services/dashboard.service';
export * from './src/services/event.service';
export * from './src/services/organization.service';
export * from './src/services/profile.service';
export * from './src/services/project.service';
export * from './src/services/reports.service';
export * from './src/services/salt.service';
export * from './src/services/share.service';
export * from './src/services/user.service';

View File

@@ -12,9 +12,12 @@
"with-env": "dotenv -e ../../.env -c --"
},
"dependencies": {
"@clerk/nextjs": "^4.29.7",
"@clickhouse/client": "^0.2.9",
"@mixan/common": "workspace:*",
"@mixan/constants": "workspace:*",
"@mixan/redis": "workspace:*",
"@mixan/validation": "workspace:*",
"@prisma/client": "^5.1.1",
"ramda": "^0.29.1",
"uuid": "^9.0.1"

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "ChartType" ADD VALUE 'map';

View File

@@ -97,6 +97,7 @@ enum ChartType {
pie
metric
area
map
}
model Dashboard {

View File

@@ -1,16 +0,0 @@
/* eslint-disable */
import { Profile } from '@prisma/client';
export type IDBEvent = {
id: string;
name: string;
profile_id?: string;
project_id: string;
properties: Record<string, string>;
created_at: string;
};
export type IDBProfile = Omit<Profile, 'properties'> & {
properties: Record<string, unknown>;
};

View File

@@ -1,3 +1,5 @@
import type { IChartEventFilter, IGetChartDataInput } from '@mixan/validation';
import { formatClickhouseDate } from '../clickhouse-client';
import type { SqlBuilderObject } from '../sql-builder';
import { createSqlBuilder } from '../sql-builder';
@@ -8,8 +10,6 @@ function log(sql: string) {
return sql;
}
type IGetChartDataInput = any;
export function getChartSql({
event,
breakdowns,
@@ -107,7 +107,7 @@ export function getChartSql({
export function getEventFiltersWhereClause(
sb: SqlBuilderObject,
filters: any[]
filters: IChartEventFilter[]
) {
filters.forEach((filter, index) => {
const id = `f${index}`;

View File

@@ -0,0 +1,19 @@
import type { Client } from '../prisma-client';
import { db } from '../prisma-client';
import type { IServiceProject } from './project.service';
export type IServiceClient = Client;
export type IServiceClientWithProject = Client & {
project: Exclude<IServiceProject, null>;
};
export function getClientsByOrganizationId(organizationId: string) {
return db.client.findMany({
where: {
organization_slug: organizationId,
},
include: {
project: true,
},
});
}

View File

@@ -0,0 +1,50 @@
import { db } from '../prisma-client';
export type IServiceDashboard = Awaited<ReturnType<typeof getDashboardById>>;
export type IServiceDashboards = Awaited<
ReturnType<typeof getDashboardsByProjectId>
>;
export async function getDashboardById(id: string) {
const dashboard = await db.dashboard.findUnique({
where: {
id,
},
include: {
project: true,
},
});
if (!dashboard) {
return null;
}
return dashboard;
}
export async function getDashboardsByOrganization(organizationSlug: string) {
return db.dashboard.findMany({
where: {
organization_slug: organizationSlug,
},
include: {
project: true,
},
orderBy: {
reports: {
_count: 'desc',
},
},
});
}
export function getDashboardsByProjectId(projectId: string) {
return db.dashboard.findMany({
where: {
project_id: projectId,
},
include: {
project: true,
},
});
}

View File

@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
import { randomSplitName, toDots } from '@mixan/common';
import { redis, redisPub } from '@mixan/redis';
import type { IChartEventFilter } from '@mixan/validation';
import {
ch,
@@ -12,9 +13,9 @@ import {
} from '../clickhouse-client';
import type { EventMeta, Prisma } from '../prisma-client';
import { db } from '../prisma-client';
import type { IDBProfile } from '../prisma-types';
import { createSqlBuilder } from '../sql-builder';
import { getEventFiltersWhereClause } from './chart.service';
import type { IServiceProfile } from './profile.service';
export interface IClickhouseEvent {
id: string;
@@ -40,7 +41,7 @@ export interface IClickhouseEvent {
model: string;
// They do not exist here. Just make ts happy for now
profile?: IDBProfile;
profile?: IServiceProfile;
meta?: EventMeta;
}
@@ -100,7 +101,7 @@ export interface IServiceCreateEventPayload {
referrer: string | undefined;
referrerName: string | undefined;
referrerType: string | undefined;
profile: IDBProfile | undefined;
profile: IServiceProfile | undefined;
meta: EventMeta | undefined;
}
@@ -131,9 +132,7 @@ export async function getEvents(
});
for (const event of events) {
event.profile = profiles.find((p) => p.id === event.profile_id) as
| IDBProfile
| undefined;
event.profile = profiles.find((p) => p.id === event.profile_id);
}
}
@@ -251,7 +250,8 @@ interface GetEventListOptions {
profileId?: string;
take: number;
cursor?: number;
filters: any[];
events?: string[] | null;
filters?: IChartEventFilter[];
}
export async function getEventList({
@@ -259,18 +259,29 @@ export async function getEventList({
take,
projectId,
profileId,
events,
filters,
}: GetEventListOptions) {
const { sb, getSql } = createSqlBuilder();
const { sb, getSql, join } = createSqlBuilder();
sb.limit = take;
sb.offset = (cursor ?? 0) * take;
sb.where.projectId = `project_id = '${projectId}'`;
if (profileId) {
sb.where.profileId = `profile_id = '${profileId}'`;
}
getEventFiltersWhereClause(sb, filters);
if (events && events.length > 0) {
sb.where.events = `name IN (${join(
events.map((n) => `'${n}'`),
','
)})`;
}
if (filters) {
getEventFiltersWhereClause(sb, filters);
}
// if (cursor) {
// sb.where.cursor = `created_at <= '${formatClickhouseDate(cursor)}'`;
@@ -284,15 +295,25 @@ export async function getEventList({
export async function getEventsCount({
projectId,
profileId,
events,
filters,
}: Omit<GetEventListOptions, 'cursor' | 'take'>) {
const { sb, getSql } = createSqlBuilder();
const { sb, getSql, join } = createSqlBuilder();
sb.where.projectId = `project_id = '${projectId}'`;
if (profileId) {
sb.where.profileId = `profile_id = '${profileId}'`;
}
getEventFiltersWhereClause(sb, filters);
if (events && events.length > 0) {
sb.where.events = `name IN (${join(
events.map((n) => `'${n}'`),
','
)})`;
}
if (filters) {
getEventFiltersWhereClause(sb, filters);
}
const res = await chQuery<{ count: number }>(
getSql().replace('*', 'count(*) as count')

View File

@@ -0,0 +1,67 @@
import { auth, clerkClient } from '@clerk/nextjs';
import type {
Organization,
OrganizationInvitation,
} from '@clerk/nextjs/dist/types/server';
import { db } from '../prisma-client';
export type IServiceOrganization = Awaited<
ReturnType<typeof getCurrentOrganizations>
>[number];
export type IServiceInvites = Awaited<ReturnType<typeof getInvites>>;
function transformOrganization(org: Organization) {
return {
id: org.id,
name: org.name,
slug: org.slug,
};
}
export async function getCurrentOrganizations() {
const session = auth();
const organizations = await clerkClient.users.getOrganizationMembershipList({
userId: session.userId!,
});
return organizations.map((item) => transformOrganization(item.organization));
}
export function getOrganizationBySlug(slug: string) {
return clerkClient.organizations
.getOrganization({ slug })
.then(transformOrganization)
.catch(() => null);
}
export async function getOrganizationByProjectId(projectId: string) {
const project = await db.project.findUniqueOrThrow({
where: {
id: projectId,
},
});
return clerkClient.organizations.getOrganization({
slug: project.organization_slug,
});
}
export function transformInvite(invite: OrganizationInvitation) {
return {
id: invite.id,
email: invite.emailAddress,
role: invite.role,
status: invite.status,
createdAt: invite.createdAt,
updatedAt: invite.updatedAt,
};
}
export async function getInvites(organizationId: string) {
return await clerkClient.organizations
.getOrganizationInvitationList({
organizationId,
})
.then((invites) => invites.map(transformInvite));
}

View File

@@ -0,0 +1,35 @@
import { db } from '../prisma-client';
export type IServiceProfile = Awaited<ReturnType<typeof getProfileById>>;
export function getProfileById(id: string) {
return db.profile.findUniqueOrThrow({
where: {
id,
},
});
}
export function getProfilesByExternalId(
externalId: string | null,
projectId: string
) {
if (externalId === null) {
return [];
}
return db.profile.findMany({
where: {
external_id: externalId,
project_id: projectId,
},
});
}
export function getProfile(id: string) {
return db.profile.findUniqueOrThrow({
where: {
id,
},
});
}

View File

@@ -0,0 +1,30 @@
import { db } from '../prisma-client';
export type IServiceProject = Awaited<ReturnType<typeof getProjectById>>;
export function getProjectById(id: string) {
return db.project.findUnique({
where: {
id,
},
});
}
export function getProjectsByOrganizationSlug(slug: string) {
return db.project.findMany({
where: {
organization_slug: slug,
},
});
}
export async function getProjectWithMostEvents(slug: string) {
return db.project.findFirst({
where: {
organization_slug: slug,
},
orderBy: {
eventsCount: 'desc',
},
});
}

View File

@@ -0,0 +1,85 @@
import { alphabetIds, lineTypes, timeRanges } from '@mixan/constants';
import type {
IChartBreakdown,
IChartEvent,
IChartEventFilter,
IChartInput,
IChartLineType,
IChartRange,
} from '@mixan/validation';
import { db } from '../prisma-client';
import type { Report as DbReport } from '../prisma-client';
export type IServiceReport = Awaited<ReturnType<typeof getReportById>>;
export function transformFilter(
filter: Partial<IChartEventFilter>,
index: number
): IChartEventFilter {
return {
id: filter.id ?? alphabetIds[index] ?? 'A',
name: filter.name ?? 'Unknown Filter',
operator: filter.operator ?? 'is',
value:
typeof filter.value === 'string' ? [filter.value] : filter.value ?? [],
};
}
export function transformReportEvent(
event: Partial<IChartEvent>,
index: number
): IChartEvent {
return {
segment: event.segment ?? 'event',
filters: (event.filters ?? []).map(transformFilter),
id: event.id ?? alphabetIds[index]!,
name: event.name || 'unknown_event',
displayName: event.displayName,
property: event.property,
};
}
export function transformReport(
report: DbReport
): IChartInput & { id: string } {
return {
id: report.id,
projectId: report.project_id,
events: (report.events as IChartEvent[]).map(transformReportEvent),
breakdowns: report.breakdowns as IChartBreakdown[],
chartType: report.chart_type,
lineType: (report.line_type as IChartLineType) ?? lineTypes.monotone,
interval: report.interval,
name: report.name || 'Untitled',
range: (report.range as IChartRange) ?? timeRanges['1m'],
previous: report.previous ?? false,
formula: report.formula ?? undefined,
metric: report.metric ?? 'sum',
unit: report.unit ?? undefined,
};
}
export function getReportsByDashboardId(dashboardId: string) {
return db.report
.findMany({
where: {
dashboard_id: dashboardId,
},
})
.then((reports) => reports.map(transformReport));
}
export async function getReportById(id: string) {
const report = await db.report.findUnique({
where: {
id,
},
});
if (!report) {
return null;
}
return transformReport(report);
}

View File

@@ -0,0 +1,24 @@
import { auth, clerkClient } from '@clerk/nextjs';
import type { User } from '@clerk/nextjs/dist/types/server';
export function transformUser(user: User) {
return {
name: `${user.firstName} ${user.lastName}`,
email: user.emailAddresses[0]?.emailAddress ?? '',
id: user.id,
lastName: user.lastName ?? '',
firstName: user.firstName ?? '',
};
}
export async function getCurrentUser() {
const session = auth();
if (!session.userId) {
return null;
}
return getUserById(session.userId);
}
export async function getUserById(id: string) {
return clerkClient.users.getUser(id).then(transformUser);
}

View File

@@ -1,168 +1 @@
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type MixanJson = Record<string, any>;
// Deprecated
export interface EventPayload {
name: string;
time: string;
profileId: string | null;
properties: MixanJson;
}
// Deprecated
export interface ProfilePayload {
first_name?: string;
last_name?: string;
email?: string;
avatar?: string;
id?: string;
properties?: MixanJson;
}
export type BatchPayload =
| {
type: 'increment';
payload: BatchProfileIncrementPayload;
}
| {
type: 'decrement';
payload: BatchProfileDecrementPayload;
}
| {
type: 'event';
payload: BatchEventPayload;
}
| {
type: 'create_profile';
payload: BatchCreateProfilePayload;
}
| {
type: 'update_profile';
payload: BatchUpdateProfilePayload;
}
| {
type: 'update_session';
payload: BatchUpdateSessionPayload;
}
| {
type: 'set_profile_property';
payload: BatchSetProfilePropertyPayload;
};
export interface BatchSetProfilePropertyPayload {
profileId: string;
name: string;
value: any;
update: boolean;
}
export interface CreateProfileResponse {
id: string;
}
export interface BatchCreateProfilePayload {
profileId: string;
properties?: MixanJson;
}
export interface BatchUpdateSessionPayload {
profileId: string;
properties?: MixanJson;
}
export interface BatchEventPayload {
name: string;
time: string;
profileId: string;
properties: MixanJson;
}
export interface BatchUpdateProfilePayload {
first_name?: string;
last_name?: string;
email?: string;
avatar?: string;
id?: string;
properties?: MixanJson;
profileId: string;
}
export interface ProfileIncrementPayload {
name: string;
value: number;
profileId: string;
}
export interface ProfileDecrementPayload {
name: string;
value: number;
profileId: string;
}
export interface BatchProfileIncrementPayload {
name: string;
value: number;
profileId: string;
}
export interface BatchProfileDecrementPayload {
name: string;
value: number;
profileId: string;
}
export interface MixanIssue {
field: string;
message: string;
value: any;
}
export interface MixanErrorResponse {
status: 'error';
code: number;
message: string;
issues?: MixanIssue[] | undefined;
stack?: string | undefined;
}
export interface MixanResponse<T> {
result: T;
status: 'ok';
}
// NEW
export interface PostEventPayload {
name: string;
timestamp: string;
profileId?: string;
properties?: Record<string, unknown> & {
title?: string | undefined;
referrer?: string | undefined;
path?: string | undefined;
};
}
export interface UpdateProfilePayload {
profileId?: string;
id?: string;
first_name?: string;
last_name?: string;
email?: string;
avatar?: string;
properties?: MixanJson;
}
export interface IncrementProfilePayload {
profileId?: string;
property: string;
value: number;
}
export interface DecrementProfilePayload {
profileId?: string;
property: string;
value: number;
}
export * from './src/sdk.types';

View File

@@ -0,0 +1,168 @@
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type MixanJson = Record<string, any>;
// Deprecated
export interface EventPayload {
name: string;
time: string;
profileId: string | null;
properties: MixanJson;
}
// Deprecated
export interface ProfilePayload {
first_name?: string;
last_name?: string;
email?: string;
avatar?: string;
id?: string;
properties?: MixanJson;
}
export type BatchPayload =
| {
type: 'increment';
payload: BatchProfileIncrementPayload;
}
| {
type: 'decrement';
payload: BatchProfileDecrementPayload;
}
| {
type: 'event';
payload: BatchEventPayload;
}
| {
type: 'create_profile';
payload: BatchCreateProfilePayload;
}
| {
type: 'update_profile';
payload: BatchUpdateProfilePayload;
}
| {
type: 'update_session';
payload: BatchUpdateSessionPayload;
}
| {
type: 'set_profile_property';
payload: BatchSetProfilePropertyPayload;
};
export interface BatchSetProfilePropertyPayload {
profileId: string;
name: string;
value: any;
update: boolean;
}
export interface CreateProfileResponse {
id: string;
}
export interface BatchCreateProfilePayload {
profileId: string;
properties?: MixanJson;
}
export interface BatchUpdateSessionPayload {
profileId: string;
properties?: MixanJson;
}
export interface BatchEventPayload {
name: string;
time: string;
profileId: string;
properties: MixanJson;
}
export interface BatchUpdateProfilePayload {
first_name?: string;
last_name?: string;
email?: string;
avatar?: string;
id?: string;
properties?: MixanJson;
profileId: string;
}
export interface ProfileIncrementPayload {
name: string;
value: number;
profileId: string;
}
export interface ProfileDecrementPayload {
name: string;
value: number;
profileId: string;
}
export interface BatchProfileIncrementPayload {
name: string;
value: number;
profileId: string;
}
export interface BatchProfileDecrementPayload {
name: string;
value: number;
profileId: string;
}
export interface MixanIssue {
field: string;
message: string;
value: any;
}
export interface MixanErrorResponse {
status: 'error';
code: number;
message: string;
issues?: MixanIssue[] | undefined;
stack?: string | undefined;
}
export interface MixanResponse<T> {
result: T;
status: 'ok';
}
// NEW
export interface PostEventPayload {
name: string;
timestamp: string;
profileId?: string;
properties?: Record<string, unknown> & {
title?: string | undefined;
referrer?: string | undefined;
path?: string | undefined;
};
}
export interface UpdateProfilePayload {
profileId?: string;
id?: string;
first_name?: string;
last_name?: string;
email?: string;
avatar?: string;
properties?: MixanJson;
}
export interface IncrementProfilePayload {
profileId?: string;
property: string;
value: number;
}
export interface DecrementProfilePayload {
profileId?: string;
property: string;
value: number;
}

View File

@@ -0,0 +1,2 @@
export * from './src/index';
export * from './src/types.validation';

View File

@@ -0,0 +1,32 @@
{
"name": "@mixan/validation",
"version": "0.0.1",
"main": "index.ts",
"scripts": {
"lint": "eslint .",
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@mixan/constants": "workspace:*",
"zod": "^3.22.4"
},
"devDependencies": {
"@mixan/eslint-config": "workspace:*",
"@mixan/prettier-config": "workspace:*",
"@mixan/tsconfig": "workspace:*",
"@mixan/types": "workspace:*",
"@types/node": "^18.16.0",
"eslint": "^8.48.0",
"prettier": "^3.0.3",
"prisma": "^5.1.1",
"typescript": "^5.2.2"
},
"eslintConfig": {
"root": true,
"extends": [
"@mixan/eslint-config/base"
]
},
"prettier": "@mixan/prettier-config"
}

View File

@@ -0,0 +1,88 @@
import { z } from 'zod';
import {
chartTypes,
intervals,
lineTypes,
metrics,
operators,
timeRanges,
} from '@mixan/constants';
export function objectToZodEnums<K extends string>(
obj: Record<K, any>
): [K, ...K[]] {
const [firstKey, ...otherKeys] = Object.keys(obj) as K[];
return [firstKey!, ...otherKeys];
}
export const mapKeys = objectToZodEnums;
export const zChartEvent = z.object({
id: z.string(),
name: z.string(),
displayName: z.string().optional(),
property: z.string().optional(),
segment: z.enum([
'event',
'user',
'user_average',
'one_event_per_user',
'property_sum',
'property_average',
]),
filters: z.array(
z.object({
id: z.string(),
name: z.string(),
operator: z.enum(objectToZodEnums(operators)),
value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())),
})
),
});
export const zChartBreakdown = z.object({
id: z.string(),
name: z.string(),
});
export const zChartEvents = z.array(zChartEvent);
export const zChartBreakdowns = z.array(zChartBreakdown);
export const zChartType = z.enum(objectToZodEnums(chartTypes));
export const zLineType = z.enum(objectToZodEnums(lineTypes));
export const zTimeInterval = z.enum(objectToZodEnums(intervals));
export const zMetric = z.enum(objectToZodEnums(metrics));
export const zChartInput = z.object({
name: z.string(),
chartType: zChartType,
lineType: zLineType,
interval: zTimeInterval,
events: zChartEvents,
breakdowns: zChartBreakdowns,
range: z.enum(objectToZodEnums(timeRanges)),
previous: z.boolean(),
formula: z.string().optional(),
metric: zMetric,
unit: z.string().optional(),
previousIndicatorInverted: z.boolean().optional(),
projectId: z.string(),
startDate: z.string().nullish(),
endDate: z.string().nullish(),
});
export const zInviteUser = z.object({
email: z.string().email(),
organizationSlug: z.string(),
role: z.enum(['admin', 'org:member']),
});
export const zShareOverview = z.object({
organizationId: z.string(),
projectId: z.string(),
password: z.string().nullable(),
public: z.boolean(),
});

View File

@@ -0,0 +1,33 @@
import type { z } from 'zod';
import type { timeRanges } from '@mixan/constants';
import type {
zChartBreakdown,
zChartEvent,
zChartInput,
zChartType,
zLineType,
zMetric,
zTimeInterval,
} from './index';
export type IChartInput = z.infer<typeof zChartInput>;
export type IChartEvent = z.infer<typeof zChartEvent>;
export type IChartEventFilter = IChartEvent['filters'][number];
export type IChartEventFilterValue =
IChartEvent['filters'][number]['value'][number];
export type IChartEventFilterOperator =
IChartEvent['filters'][number]['operator'];
export type IChartBreakdown = z.infer<typeof zChartBreakdown>;
export type IInterval = z.infer<typeof zTimeInterval>;
export type IChartType = z.infer<typeof zChartType>;
export type IChartMetric = z.infer<typeof zMetric>;
export type IChartLineType = z.infer<typeof zLineType>;
export type IChartRange = keyof typeof timeRanges;
export type IGetChartDataInput = {
event: IChartEvent;
projectId: string;
startDate: string;
endDate: string;
} & Omit<IChartInput, 'events' | 'name' | 'startDate' | 'endDate' | 'range'>;

View File

@@ -0,0 +1,12 @@
{
"extends": "@mixan/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["."],
"exclude": ["node_modules"]
}