chore: move logics to FilterSegmentRepository (#21648)
* chore: move logics to FilterSegmentRepository * add explicit select * replace TRPCError with Error * convert to non static methods and implement the interface * apply feedback --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
This commit is contained in:
co-authored by
sean-brydon
parent
b39404860f
commit
8078c5657b
+18
-15
@@ -1,4 +1,4 @@
|
||||
import prismock from "../../../../../../../tests/libs/__mocks__/prisma";
|
||||
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -6,12 +6,15 @@ import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { createHandler } from "../create.handler";
|
||||
import { type TCreateFilterSegmentInputSchema } from "../create.schema";
|
||||
import { FilterSegmentRepository } from "../../filterSegment";
|
||||
import type { TCreateFilterSegmentInputSchema } from "../../filterSegment.type";
|
||||
|
||||
describe("createHandler", () => {
|
||||
const repository = new FilterSegmentRepository();
|
||||
|
||||
describe("FilterSegmentRepository.create()", () => {
|
||||
const userId = 1;
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
id: userId,
|
||||
name: "Test User",
|
||||
} as NonNullable<TrpcSessionUser>;
|
||||
|
||||
@@ -40,8 +43,8 @@ describe("createHandler", () => {
|
||||
name: "My Bookings",
|
||||
};
|
||||
|
||||
const result = await createHandler({
|
||||
ctx: { user: mockUser },
|
||||
const result = await repository.create({
|
||||
userId,
|
||||
input,
|
||||
});
|
||||
|
||||
@@ -74,8 +77,8 @@ describe("createHandler", () => {
|
||||
name: "Team Bookings",
|
||||
};
|
||||
|
||||
const result = await createHandler({
|
||||
ctx: { user: mockUser },
|
||||
const result = await repository.create({
|
||||
userId,
|
||||
input,
|
||||
});
|
||||
|
||||
@@ -109,8 +112,8 @@ describe("createHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
createHandler({
|
||||
ctx: { user: mockUser },
|
||||
repository.create({
|
||||
userId,
|
||||
input,
|
||||
})
|
||||
).rejects.toThrow("You must be a team admin or owner to create team segments");
|
||||
@@ -124,8 +127,8 @@ describe("createHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
createHandler({
|
||||
ctx: { user: mockUser },
|
||||
repository.create({
|
||||
userId,
|
||||
// @ts-expect-error - Testing invalid input where teamId is missing for TEAM scope
|
||||
input: invalidInput,
|
||||
})
|
||||
@@ -141,8 +144,8 @@ describe("createHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
createHandler({
|
||||
ctx: { user: mockUser },
|
||||
repository.create({
|
||||
userId,
|
||||
// @ts-expect-error - Testing invalid input where teamId is present for USER scope
|
||||
input: invalidInput,
|
||||
})
|
||||
+27
-30
@@ -1,16 +1,19 @@
|
||||
import prismock from "../../../../../../../tests/libs/__mocks__/prisma";
|
||||
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { deleteHandler } from "../delete.handler";
|
||||
import { type TDeleteFilterSegmentInputSchema } from "../delete.schema";
|
||||
import { FilterSegmentRepository } from "../../filterSegment";
|
||||
import { type TDeleteFilterSegmentInputSchema } from "../../filterSegment.type";
|
||||
|
||||
describe("deleteHandler", () => {
|
||||
const repository = new FilterSegmentRepository();
|
||||
|
||||
describe("FilterSegmentRepository.delete()", () => {
|
||||
const userId = 1;
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
id: userId,
|
||||
name: "Test User",
|
||||
} as NonNullable<TrpcSessionUser>;
|
||||
|
||||
@@ -34,15 +37,12 @@ describe("deleteHandler", () => {
|
||||
id: segment.id,
|
||||
};
|
||||
|
||||
const result = await deleteHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
id: segment.id,
|
||||
message: "Filter segment deleted successfully",
|
||||
});
|
||||
await expect(
|
||||
repository.delete({
|
||||
userId,
|
||||
id: segment.id,
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
|
||||
// Verify segment is deleted
|
||||
const deletedSegment = await prismock.filterSegment.findUnique({
|
||||
@@ -90,15 +90,12 @@ describe("deleteHandler", () => {
|
||||
id: segment.id,
|
||||
};
|
||||
|
||||
const result = await deleteHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
id: segment.id,
|
||||
message: "Filter segment deleted successfully",
|
||||
});
|
||||
await expect(
|
||||
repository.delete({
|
||||
userId,
|
||||
id: segment.id,
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
|
||||
// Verify segment is deleted
|
||||
const deletedSegment = await prismock.filterSegment.findUnique({
|
||||
@@ -113,9 +110,9 @@ describe("deleteHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
deleteHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
repository.delete({
|
||||
userId,
|
||||
id: 999,
|
||||
})
|
||||
).rejects.toThrow("Filter segment not found or you don't have permission to delete it");
|
||||
});
|
||||
@@ -160,9 +157,9 @@ describe("deleteHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
deleteHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
repository.delete({
|
||||
userId,
|
||||
id: segment.id,
|
||||
})
|
||||
).rejects.toThrow("Filter segment not found or you don't have permission to delete it");
|
||||
});
|
||||
+90
-17
@@ -1,16 +1,19 @@
|
||||
import prismock from "../../../../../../../tests/libs/__mocks__/prisma";
|
||||
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { listHandler } from "../list.handler";
|
||||
import { type TListFilterSegmentsInputSchema } from "../list.schema";
|
||||
import { FilterSegmentRepository } from "../../filterSegment";
|
||||
import { type TListFilterSegmentsInputSchema } from "../../filterSegment.type";
|
||||
|
||||
describe("listHandler", () => {
|
||||
const repository = new FilterSegmentRepository();
|
||||
|
||||
describe("FilterSegmentRepository.get()", () => {
|
||||
const userId = 1;
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
id: userId,
|
||||
name: "Test User",
|
||||
} as NonNullable<TrpcSessionUser>;
|
||||
|
||||
@@ -48,9 +51,9 @@ describe("listHandler", () => {
|
||||
tableIdentifier: "bookings",
|
||||
};
|
||||
|
||||
const result = await listHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
const result = await repository.get({
|
||||
userId,
|
||||
tableIdentifier: input.tableIdentifier,
|
||||
});
|
||||
|
||||
expect(result.segments).toHaveLength(2);
|
||||
@@ -132,9 +135,9 @@ describe("listHandler", () => {
|
||||
tableIdentifier: "bookings",
|
||||
};
|
||||
|
||||
const result = await listHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
const result = await repository.get({
|
||||
userId,
|
||||
tableIdentifier: input.tableIdentifier,
|
||||
});
|
||||
|
||||
expect(result.segments).toHaveLength(2);
|
||||
@@ -216,9 +219,9 @@ describe("listHandler", () => {
|
||||
tableIdentifier: "bookings",
|
||||
};
|
||||
|
||||
const result = await listHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
const result = await repository.get({
|
||||
userId,
|
||||
tableIdentifier: input.tableIdentifier,
|
||||
});
|
||||
|
||||
expect(result.segments).toHaveLength(2);
|
||||
@@ -251,9 +254,9 @@ describe("listHandler", () => {
|
||||
tableIdentifier: "bookings",
|
||||
};
|
||||
|
||||
const result = await listHandler({
|
||||
ctx: { user: mockUser },
|
||||
input,
|
||||
const result = await repository.get({
|
||||
userId,
|
||||
tableIdentifier: input.tableIdentifier,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -261,4 +264,74 @@ describe("listHandler", () => {
|
||||
preferredSegmentId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return segments with user preferences", async () => {
|
||||
// Create a filter segment
|
||||
const segment = await prismock.filterSegment.create({
|
||||
data: {
|
||||
userId: mockUser.id,
|
||||
scope: "USER",
|
||||
tableIdentifier: "contacts",
|
||||
name: "Important Contacts",
|
||||
activeFilters: [{ f: "priority" }],
|
||||
sorting: [{ id: "lastContact", desc: true }],
|
||||
columnVisibility: {},
|
||||
columnSizing: {},
|
||||
perPage: 50,
|
||||
},
|
||||
});
|
||||
|
||||
// Create a user preference for this segment
|
||||
await prismock.userFilterSegmentPreference.create({
|
||||
data: {
|
||||
userId: mockUser.id,
|
||||
tableIdentifier: "contacts",
|
||||
segmentId: segment.id,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await repository.get({
|
||||
userId: mockUser.id,
|
||||
tableIdentifier: "contacts",
|
||||
});
|
||||
|
||||
expect(result.segments).toHaveLength(1);
|
||||
expect(result.preferredSegmentId).toBe(segment.id);
|
||||
expect(result.segments[0].name).toBe("Important Contacts");
|
||||
});
|
||||
|
||||
it("should handle segments with null JSON fields gracefully", async () => {
|
||||
// Create segment with minimal data (some fields will be null/undefined)
|
||||
await prismock.filterSegment.create({
|
||||
data: {
|
||||
userId: mockUser.id,
|
||||
scope: "USER",
|
||||
tableIdentifier: "reports",
|
||||
name: "Basic Report",
|
||||
activeFilters: undefined, // This should be parsed to []
|
||||
sorting: undefined, // This should be parsed to []
|
||||
columnVisibility: undefined, // This should be parsed to {}
|
||||
columnSizing: undefined, // This should be parsed to {}
|
||||
perPage: 10,
|
||||
searchTerm: null,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await repository.get({
|
||||
userId: mockUser.id,
|
||||
tableIdentifier: "reports",
|
||||
});
|
||||
|
||||
expect(result.segments).toHaveLength(1);
|
||||
expect(result.segments[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "Basic Report",
|
||||
activeFilters: [], // Should default to empty array
|
||||
sorting: [], // Should default to empty array
|
||||
columnVisibility: {}, // Should default to empty object
|
||||
columnSizing: {}, // Should default to empty object
|
||||
perPage: 10,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
+16
-13
@@ -1,4 +1,4 @@
|
||||
import prismock from "../../../../../../../tests/libs/__mocks__/prisma";
|
||||
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -6,12 +6,15 @@ import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { updateHandler } from "../update.handler";
|
||||
import { type TUpdateFilterSegmentInputSchema } from "../update.schema";
|
||||
import { FilterSegmentRepository } from "../../filterSegment";
|
||||
import { type TUpdateFilterSegmentInputSchema } from "../../filterSegment.type";
|
||||
|
||||
describe("updateHandler", () => {
|
||||
const repository = new FilterSegmentRepository();
|
||||
|
||||
describe("FilterSegmentRepository.update()", () => {
|
||||
const userId = 1;
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
id: userId,
|
||||
name: "Test User",
|
||||
} as NonNullable<TrpcSessionUser>;
|
||||
|
||||
@@ -61,8 +64,8 @@ describe("updateHandler", () => {
|
||||
],
|
||||
};
|
||||
|
||||
const result = await updateHandler({
|
||||
ctx: { user: mockUser },
|
||||
const result = await repository.update({
|
||||
userId,
|
||||
input,
|
||||
});
|
||||
|
||||
@@ -130,8 +133,8 @@ describe("updateHandler", () => {
|
||||
],
|
||||
};
|
||||
|
||||
const result = await updateHandler({
|
||||
ctx: { user: mockUser },
|
||||
const result = await repository.update({
|
||||
userId,
|
||||
input,
|
||||
});
|
||||
|
||||
@@ -171,8 +174,8 @@ describe("updateHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
updateHandler({
|
||||
ctx: { user: mockUser },
|
||||
repository.update({
|
||||
userId,
|
||||
input,
|
||||
})
|
||||
).rejects.toThrow("Filter segment not found or you don't have permission to update it");
|
||||
@@ -236,8 +239,8 @@ describe("updateHandler", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
updateHandler({
|
||||
ctx: { user: mockUser },
|
||||
repository.update({
|
||||
userId,
|
||||
input,
|
||||
})
|
||||
).rejects.toThrow("Filter segment not found or you don't have permission to update it");
|
||||
@@ -1,7 +1,281 @@
|
||||
import type { FilterSegmentOutput } from "@calcom/features/data-table/lib/types";
|
||||
import {
|
||||
ZActiveFilters,
|
||||
ZSortingState,
|
||||
ZColumnSizing,
|
||||
ZColumnVisibility,
|
||||
} from "@calcom/features/data-table/lib/types";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { FilterSegment, UserFilterSegmentPreference } from "@calcom/prisma/client";
|
||||
|
||||
export class FilterSegmentRepository {
|
||||
static async setPreference({
|
||||
import type { TCreateFilterSegmentInputSchema, TUpdateFilterSegmentInputSchema } from "./filterSegment.type";
|
||||
|
||||
export interface IFilterSegmentRepository {
|
||||
get({ userId, tableIdentifier }: { userId: number; tableIdentifier: string }): Promise<{
|
||||
segments: FilterSegmentOutput[];
|
||||
preferredSegmentId: number | null;
|
||||
}>;
|
||||
|
||||
create({
|
||||
userId,
|
||||
input,
|
||||
}: {
|
||||
userId: number;
|
||||
input: TCreateFilterSegmentInputSchema;
|
||||
}): Promise<FilterSegment>;
|
||||
|
||||
update({
|
||||
userId,
|
||||
input,
|
||||
}: {
|
||||
userId: number;
|
||||
input: TUpdateFilterSegmentInputSchema;
|
||||
}): Promise<FilterSegment>;
|
||||
|
||||
delete({ userId, id }: { userId: number; id: number }): Promise<void>;
|
||||
|
||||
setPreference({
|
||||
userId,
|
||||
tableIdentifier,
|
||||
segmentId,
|
||||
}: {
|
||||
userId: number;
|
||||
tableIdentifier: string;
|
||||
segmentId: number | null;
|
||||
}): Promise<UserFilterSegmentPreference | null>;
|
||||
}
|
||||
|
||||
export class FilterSegmentRepository implements IFilterSegmentRepository {
|
||||
async get({ userId, tableIdentifier }: { userId: number; tableIdentifier: string }) {
|
||||
// Get all teams that the user is a member of
|
||||
const userTeamIds = await prisma.membership
|
||||
.findMany({
|
||||
where: {
|
||||
userId,
|
||||
accepted: true,
|
||||
},
|
||||
select: {
|
||||
teamId: true,
|
||||
},
|
||||
})
|
||||
.then((memberships) => memberships.map((m) => m.teamId));
|
||||
|
||||
// Fetch both user-scoped and team-scoped segments
|
||||
const segments = await prisma.filterSegment.findMany({
|
||||
where: {
|
||||
tableIdentifier,
|
||||
OR: [
|
||||
// User-scoped segments created by the current user
|
||||
{
|
||||
scope: "USER",
|
||||
userId,
|
||||
},
|
||||
// Team-scoped segments for teams the user belongs to
|
||||
{
|
||||
scope: "TEAM",
|
||||
teamId: {
|
||||
in: userTeamIds,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
tableIdentifier: true,
|
||||
scope: true,
|
||||
activeFilters: true,
|
||||
sorting: true,
|
||||
columnVisibility: true,
|
||||
columnSizing: true,
|
||||
perPage: true,
|
||||
searchTerm: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ scope: "desc" }, // USER segments first, then TEAM segments
|
||||
{ createdAt: "desc" }, // Newest first within each scope
|
||||
],
|
||||
});
|
||||
|
||||
const parsedSegments: FilterSegmentOutput[] = segments.map((segment) => ({
|
||||
...segment,
|
||||
activeFilters: ZActiveFilters.catch([]).parse(segment.activeFilters),
|
||||
sorting: ZSortingState.catch([]).parse(segment.sorting),
|
||||
columnVisibility: ZColumnVisibility.catch({}).parse(segment.columnVisibility),
|
||||
columnSizing: ZColumnSizing.catch({}).parse(segment.columnSizing),
|
||||
}));
|
||||
|
||||
const preference = await prisma.userFilterSegmentPreference.findUnique({
|
||||
where: {
|
||||
userId_tableIdentifier: {
|
||||
userId,
|
||||
tableIdentifier,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
segmentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
segments: parsedSegments,
|
||||
preferredSegmentId: preference?.segmentId || null,
|
||||
};
|
||||
}
|
||||
|
||||
async create({ userId, input }: { userId: number; input: TCreateFilterSegmentInputSchema }) {
|
||||
const { scope, teamId, ...filterData } = input;
|
||||
|
||||
// If scope is TEAM, verify user has admin/owner permissions
|
||||
if (scope === "TEAM") {
|
||||
if (!teamId) {
|
||||
throw new Error("Team ID is required for team scope");
|
||||
}
|
||||
|
||||
const membership = await prisma.membership.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
userId,
|
||||
teamId,
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
throw new Error("You must be a team admin or owner to create team segments");
|
||||
}
|
||||
}
|
||||
|
||||
// For USER scope, ensure no teamId is provided
|
||||
if (scope === "USER" && teamId) {
|
||||
throw new Error("Team ID is not allowed for user scope");
|
||||
}
|
||||
|
||||
// Create the filter segment
|
||||
const filterSegment = await prisma.filterSegment.create({
|
||||
data: {
|
||||
...filterData,
|
||||
scope,
|
||||
...(scope === "TEAM" ? { teamId } : {}),
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
return filterSegment;
|
||||
}
|
||||
|
||||
async update({ userId, input }: { userId: number; input: TUpdateFilterSegmentInputSchema }) {
|
||||
const { id, name, activeFilters, sorting, columnVisibility, columnSizing, perPage, searchTerm } = input;
|
||||
|
||||
// First, fetch the existing segment to check permissions
|
||||
const existingSegment = await prisma.filterSegment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
...(input.scope === "TEAM"
|
||||
? {
|
||||
scope: "TEAM",
|
||||
teamId: input.teamId,
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
scope: "USER",
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingSegment) {
|
||||
throw new Error("Filter segment not found or you don't have permission to update it");
|
||||
}
|
||||
|
||||
// Update the filter segment with only the allowed fields
|
||||
const updatedSegment = await prisma.filterSegment.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
activeFilters,
|
||||
sorting,
|
||||
columnVisibility,
|
||||
columnSizing,
|
||||
perPage,
|
||||
searchTerm,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedSegment;
|
||||
}
|
||||
|
||||
async delete({ userId, id }: { userId: number; id: number }) {
|
||||
// First, fetch the existing segment to check permissions
|
||||
const existingSegment = await prisma.filterSegment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
OR: [
|
||||
{
|
||||
scope: "TEAM",
|
||||
teamId: { not: null },
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "USER",
|
||||
userId,
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingSegment) {
|
||||
throw new Error("Filter segment not found or you don't have permission to delete it");
|
||||
}
|
||||
|
||||
// Delete the filter segment
|
||||
await prisma.filterSegment.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async setPreference({
|
||||
userId,
|
||||
tableIdentifier,
|
||||
segmentId,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { FilterSegmentOutput } from "@calcom/features/data-table/lib/types";
|
||||
import {
|
||||
ZActiveFilters,
|
||||
ZSortingState,
|
||||
ZColumnSizing,
|
||||
ZColumnVisibility,
|
||||
} from "@calcom/features/data-table/lib/types";
|
||||
|
||||
export type FilterSegmentsListResponse = {
|
||||
segments: FilterSegmentOutput[];
|
||||
preferredSegmentId: number | null;
|
||||
};
|
||||
|
||||
export const ZListFilterSegmentsInputSchema = z.object({
|
||||
tableIdentifier: z.string(),
|
||||
});
|
||||
|
||||
export type TListFilterSegmentsInputSchema = z.infer<typeof ZListFilterSegmentsInputSchema>;
|
||||
|
||||
export const ZDeleteFilterSegmentInputSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
});
|
||||
|
||||
export type TDeleteFilterSegmentInputSchema = z.infer<typeof ZDeleteFilterSegmentInputSchema>;
|
||||
|
||||
// Base schema for common fields
|
||||
const baseCreateSchema = {
|
||||
name: z.string().min(1),
|
||||
tableIdentifier: z.string().min(1),
|
||||
activeFilters: ZActiveFilters.optional(),
|
||||
sorting: ZSortingState.optional(),
|
||||
columnVisibility: ZColumnVisibility.optional(),
|
||||
columnSizing: ZColumnSizing.optional(),
|
||||
searchTerm: z.string().nullable().optional(),
|
||||
perPage: z.number().int().min(1),
|
||||
};
|
||||
|
||||
// Schema for team scope - requires teamId
|
||||
const teamCreateSchema = z.object({
|
||||
scope: z.literal("TEAM"),
|
||||
teamId: z.number().int().positive(),
|
||||
...baseCreateSchema,
|
||||
});
|
||||
|
||||
// Schema for user scope - no teamId allowed
|
||||
const userCreateSchema = z.object({
|
||||
scope: z.literal("USER"),
|
||||
teamId: z.undefined().optional(),
|
||||
...baseCreateSchema,
|
||||
});
|
||||
|
||||
export const ZCreateFilterSegmentInputSchema = z.discriminatedUnion("scope", [
|
||||
teamCreateSchema,
|
||||
userCreateSchema,
|
||||
]);
|
||||
|
||||
export type TCreateFilterSegmentInputSchema = z.infer<typeof ZCreateFilterSegmentInputSchema>;
|
||||
|
||||
export const ZSetFilterSegmentPreferenceInputSchema = z.object({
|
||||
tableIdentifier: z.string(),
|
||||
segmentId: z.number().nullable(),
|
||||
});
|
||||
|
||||
export type TSetFilterSegmentPreferenceInputSchema = z.infer<typeof ZSetFilterSegmentPreferenceInputSchema>;
|
||||
|
||||
// Base schema for common fields that can be updated
|
||||
const baseUpdateSchema = {
|
||||
name: z.string().min(1).optional(),
|
||||
tableIdentifier: z.string().min(1).optional(),
|
||||
activeFilters: ZActiveFilters.optional(),
|
||||
sorting: ZSortingState.optional(),
|
||||
columnVisibility: ZColumnVisibility.optional(),
|
||||
columnSizing: ZColumnSizing.optional(),
|
||||
searchTerm: z.string().nullable().optional(),
|
||||
perPage: z.number().int().min(1).optional(),
|
||||
};
|
||||
|
||||
// Schema for team scope updates
|
||||
const teamUpdateSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
scope: z.literal("TEAM"),
|
||||
teamId: z.number().int().positive(),
|
||||
...baseUpdateSchema,
|
||||
});
|
||||
|
||||
// Schema for user scope updates
|
||||
const userUpdateSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
scope: z.literal("USER"),
|
||||
...baseUpdateSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateFilterSegmentInputSchema = z.discriminatedUnion("scope", [
|
||||
teamUpdateSchema,
|
||||
userUpdateSchema,
|
||||
]);
|
||||
|
||||
export type TUpdateFilterSegmentInputSchema = z.infer<typeof ZUpdateFilterSegmentInputSchema>;
|
||||
@@ -1,17 +1,13 @@
|
||||
import {
|
||||
ZCreateFilterSegmentInputSchema,
|
||||
ZDeleteFilterSegmentInputSchema,
|
||||
ZListFilterSegmentsInputSchema,
|
||||
ZSetFilterSegmentPreferenceInputSchema,
|
||||
ZUpdateFilterSegmentInputSchema,
|
||||
} from "@calcom/lib/server/repository/filterSegment.type";
|
||||
|
||||
import authedProcedure from "../../../procedures/authedProcedure";
|
||||
import { router } from "../../../trpc";
|
||||
import { ZCreateFilterSegmentInputSchema } from "./create.schema";
|
||||
import { ZDeleteFilterSegmentInputSchema } from "./delete.schema";
|
||||
import { ZListFilterSegmentsInputSchema } from "./list.schema";
|
||||
import { ZSetFilterSegmentPreferenceInputSchema } from "./preference.schema";
|
||||
import { ZUpdateFilterSegmentInputSchema } from "./update.schema";
|
||||
|
||||
type FilterSegmentsRouterHandlerCache = {
|
||||
list?: typeof import("./list.handler").listHandler;
|
||||
create?: typeof import("./create.handler").createHandler;
|
||||
update?: typeof import("./update.handler").updateHandler;
|
||||
delete?: typeof import("./delete.handler").deleteHandler;
|
||||
};
|
||||
|
||||
export const filterSegmentsRouter = router({
|
||||
list: authedProcedure.input(ZListFilterSegmentsInputSchema).query(async ({ input, ctx }) => {
|
||||
@@ -24,27 +20,27 @@ export const filterSegmentsRouter = router({
|
||||
}),
|
||||
|
||||
create: authedProcedure.input(ZCreateFilterSegmentInputSchema).mutation(async ({ input, ctx }) => {
|
||||
const { createHandler } = await import("./create.handler");
|
||||
const { createFilterSegmentHandler } = await import("./create.handler");
|
||||
|
||||
return createHandler({
|
||||
return createFilterSegmentHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
|
||||
update: authedProcedure.input(ZUpdateFilterSegmentInputSchema).mutation(async ({ input, ctx }) => {
|
||||
const { updateHandler } = await import("./update.handler");
|
||||
const { updateFilterSegmentHandler } = await import("./update.handler");
|
||||
|
||||
return updateHandler({
|
||||
return updateFilterSegmentHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
|
||||
delete: authedProcedure.input(ZDeleteFilterSegmentInputSchema).mutation(async ({ input, ctx }) => {
|
||||
const { deleteHandler } = await import("./delete.handler");
|
||||
const { deleteFilterSegmentHandler } = await import("./delete.handler");
|
||||
|
||||
return deleteHandler({
|
||||
return deleteFilterSegmentHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
@@ -53,9 +49,9 @@ export const filterSegmentsRouter = router({
|
||||
setPreference: authedProcedure
|
||||
.input(ZSetFilterSegmentPreferenceInputSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { setPreferenceHandler } = await import("./preference.handler");
|
||||
const { setFilterSegmentPreferenceHandler } = await import("./preference.handler");
|
||||
|
||||
return setPreferenceHandler({
|
||||
return setFilterSegmentPreferenceHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { FilterSegmentRepository } from "@calcom/lib/server/repository/filterSegment";
|
||||
import type { TCreateFilterSegmentInputSchema } from "@calcom/lib/server/repository/filterSegment.type";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TCreateFilterSegmentInputSchema } from "./create.schema";
|
||||
|
||||
export const createHandler = async ({
|
||||
export const createFilterSegmentHandler = async ({
|
||||
ctx,
|
||||
input,
|
||||
}: {
|
||||
@@ -14,54 +11,9 @@ export const createHandler = async ({
|
||||
};
|
||||
input: TCreateFilterSegmentInputSchema;
|
||||
}) => {
|
||||
const { scope, teamId, ...filterData } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
// If scope is TEAM, verify user has admin/owner permissions
|
||||
if (scope === "TEAM") {
|
||||
if (!teamId) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Team ID is required for team scope",
|
||||
});
|
||||
}
|
||||
|
||||
const membership = await prisma.membership.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
teamId,
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "You must be a team admin or owner to create team segments",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For USER scope, ensure no teamId is provided
|
||||
if (scope === "USER" && teamId) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Team ID is not allowed for user scope",
|
||||
});
|
||||
}
|
||||
|
||||
// Create the filter segment
|
||||
const filterSegment = await prisma.filterSegment.create({
|
||||
data: {
|
||||
...filterData,
|
||||
scope,
|
||||
...(scope === "TEAM" ? { teamId } : {}),
|
||||
userId,
|
||||
},
|
||||
const repository = new FilterSegmentRepository();
|
||||
return await repository.create({
|
||||
userId: ctx.user.id,
|
||||
input,
|
||||
});
|
||||
|
||||
return filterSegment;
|
||||
};
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ZActiveFilters, ZColumnSizing, ZColumnVisibility, ZSortingState } from "./types";
|
||||
|
||||
// Base schema for common fields
|
||||
const baseCreateSchema = {
|
||||
name: z.string().min(1),
|
||||
tableIdentifier: z.string().min(1),
|
||||
activeFilters: ZActiveFilters.optional(),
|
||||
sorting: ZSortingState.optional(),
|
||||
columnVisibility: ZColumnVisibility.optional(),
|
||||
columnSizing: ZColumnSizing.optional(),
|
||||
searchTerm: z.string().nullable().optional(),
|
||||
perPage: z.number().int().min(1),
|
||||
};
|
||||
|
||||
// Schema for team scope - requires teamId
|
||||
const teamCreateSchema = z.object({
|
||||
scope: z.literal("TEAM"),
|
||||
teamId: z.number().int().positive(),
|
||||
...baseCreateSchema,
|
||||
});
|
||||
|
||||
// Schema for user scope - no teamId allowed
|
||||
const userCreateSchema = z.object({
|
||||
scope: z.literal("USER"),
|
||||
teamId: z.undefined(),
|
||||
...baseCreateSchema,
|
||||
});
|
||||
|
||||
export const ZCreateFilterSegmentInputSchema = z.discriminatedUnion("scope", [
|
||||
teamCreateSchema,
|
||||
userCreateSchema,
|
||||
]);
|
||||
|
||||
export type TCreateFilterSegmentInputSchema = z.infer<typeof ZCreateFilterSegmentInputSchema>;
|
||||
@@ -1,11 +1,8 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { FilterSegmentRepository } from "@calcom/lib/server/repository/filterSegment";
|
||||
import type { TDeleteFilterSegmentInputSchema } from "@calcom/lib/server/repository/filterSegment.type";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TDeleteFilterSegmentInputSchema } from "./delete.schema";
|
||||
|
||||
export const deleteHandler = async ({
|
||||
export const deleteFilterSegmentHandler = async ({
|
||||
ctx,
|
||||
input,
|
||||
}: {
|
||||
@@ -14,54 +11,14 @@ export const deleteHandler = async ({
|
||||
};
|
||||
input: TDeleteFilterSegmentInputSchema;
|
||||
}) => {
|
||||
const { id } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
// First, fetch the existing segment to check permissions
|
||||
const existingSegment = await prisma.filterSegment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
OR: [
|
||||
{
|
||||
scope: "TEAM",
|
||||
teamId: { not: null },
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "USER",
|
||||
userId,
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingSegment) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Filter segment not found or you don't have permission to delete it",
|
||||
});
|
||||
}
|
||||
|
||||
// Delete the filter segment
|
||||
await prisma.filterSegment.delete({
|
||||
where: { id },
|
||||
const repository = new FilterSegmentRepository();
|
||||
await repository.delete({
|
||||
userId: ctx.user.id,
|
||||
id: input.id,
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
id: input.id,
|
||||
message: "Filter segment deleted successfully",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZDeleteFilterSegmentInputSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
});
|
||||
|
||||
export type TDeleteFilterSegmentInputSchema = z.infer<typeof ZDeleteFilterSegmentInputSchema>;
|
||||
@@ -1,15 +1,7 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { FilterSegmentRepository } from "@calcom/lib/server/repository/filterSegment";
|
||||
import type { TListFilterSegmentsInputSchema } from "@calcom/lib/server/repository/filterSegment.type";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import type { TListFilterSegmentsInputSchema } from "./list.schema";
|
||||
import {
|
||||
ZActiveFilters,
|
||||
ZSortingState,
|
||||
ZColumnSizing,
|
||||
ZColumnVisibility,
|
||||
type FilterSegmentOutput,
|
||||
} from "./types";
|
||||
|
||||
export const listHandler = async ({
|
||||
ctx,
|
||||
input,
|
||||
@@ -19,77 +11,9 @@ export const listHandler = async ({
|
||||
};
|
||||
input: TListFilterSegmentsInputSchema;
|
||||
}) => {
|
||||
const { tableIdentifier } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
// Get all teams that the user is a member of
|
||||
const userTeamIds = await prisma.membership
|
||||
.findMany({
|
||||
where: {
|
||||
userId,
|
||||
accepted: true,
|
||||
},
|
||||
select: {
|
||||
teamId: true,
|
||||
},
|
||||
})
|
||||
.then((memberships) => memberships.map((m) => m.teamId));
|
||||
|
||||
// Fetch both user-scoped and team-scoped segments
|
||||
const segments = await prisma.filterSegment.findMany({
|
||||
where: {
|
||||
tableIdentifier,
|
||||
OR: [
|
||||
// User-scoped segments created by the current user
|
||||
{
|
||||
scope: "USER",
|
||||
userId,
|
||||
},
|
||||
// Team-scoped segments for teams the user belongs to
|
||||
{
|
||||
scope: "TEAM",
|
||||
teamId: {
|
||||
in: userTeamIds,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ scope: "desc" }, // USER segments first, then TEAM segments
|
||||
{ createdAt: "desc" }, // Newest first within each scope
|
||||
],
|
||||
const repository = new FilterSegmentRepository();
|
||||
return await repository.get({
|
||||
userId: ctx.user.id,
|
||||
tableIdentifier: input.tableIdentifier,
|
||||
});
|
||||
|
||||
const parsedSegments: FilterSegmentOutput[] = segments.map((segment) => ({
|
||||
...segment,
|
||||
activeFilters: ZActiveFilters.catch([]).parse(segment.activeFilters),
|
||||
sorting: ZSortingState.catch([]).parse(segment.sorting),
|
||||
columnVisibility: ZColumnVisibility.catch({}).parse(segment.columnVisibility),
|
||||
columnSizing: ZColumnSizing.catch({}).parse(segment.columnSizing),
|
||||
}));
|
||||
|
||||
const preference = await prisma.userFilterSegmentPreference.findUnique({
|
||||
where: {
|
||||
userId_tableIdentifier: {
|
||||
userId,
|
||||
tableIdentifier,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
segmentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
segments: parsedSegments,
|
||||
preferredSegmentId: preference?.segmentId || null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZListFilterSegmentsInputSchema = z.object({
|
||||
tableIdentifier: z.string(),
|
||||
});
|
||||
|
||||
export type TListFilterSegmentsInputSchema = z.infer<typeof ZListFilterSegmentsInputSchema>;
|
||||
@@ -1,9 +1,8 @@
|
||||
import { FilterSegmentRepository } from "@calcom/lib/server/repository/filterSegment";
|
||||
import type { TSetFilterSegmentPreferenceInputSchema } from "@calcom/lib/server/repository/filterSegment.type";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import type { TSetFilterSegmentPreferenceInputSchema } from "./preference.schema";
|
||||
|
||||
export const setPreferenceHandler = async ({
|
||||
export const setFilterSegmentPreferenceHandler = async ({
|
||||
ctx,
|
||||
input,
|
||||
}: {
|
||||
@@ -12,12 +11,10 @@ export const setPreferenceHandler = async ({
|
||||
};
|
||||
input: TSetFilterSegmentPreferenceInputSchema;
|
||||
}) => {
|
||||
const { tableIdentifier, segmentId } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await FilterSegmentRepository.setPreference({
|
||||
userId,
|
||||
tableIdentifier,
|
||||
segmentId,
|
||||
const repository = new FilterSegmentRepository();
|
||||
return await repository.setPreference({
|
||||
userId: ctx.user.id,
|
||||
tableIdentifier: input.tableIdentifier,
|
||||
segmentId: input.segmentId,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZSetFilterSegmentPreferenceInputSchema = z.object({
|
||||
tableIdentifier: z.string(),
|
||||
segmentId: z.number().nullable(),
|
||||
});
|
||||
|
||||
export type TSetFilterSegmentPreferenceInputSchema = z.infer<typeof ZSetFilterSegmentPreferenceInputSchema>;
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { FilterSegmentOutput } from "@calcom/features/data-table/lib/types";
|
||||
|
||||
export type { SortingState, ColumnSizingState, VisibilityState } from "@tanstack/react-table";
|
||||
export {
|
||||
type ActiveFilter,
|
||||
type FilterSegmentOutput,
|
||||
ZActiveFilters,
|
||||
ZSortingState,
|
||||
ZColumnSizing,
|
||||
ZColumnVisibility,
|
||||
} from "@calcom/features/data-table/lib/types";
|
||||
|
||||
export type FilterSegmentsListResponse = {
|
||||
segments: FilterSegmentOutput[];
|
||||
preferredSegmentId: number | null;
|
||||
};
|
||||
@@ -1,11 +1,8 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { FilterSegmentRepository } from "@calcom/lib/server/repository/filterSegment";
|
||||
import type { TUpdateFilterSegmentInputSchema } from "@calcom/lib/server/repository/filterSegment.type";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TUpdateFilterSegmentInputSchema } from "./update.schema";
|
||||
|
||||
export const updateHandler = async ({
|
||||
export const updateFilterSegmentHandler = async ({
|
||||
ctx,
|
||||
input,
|
||||
}: {
|
||||
@@ -14,59 +11,9 @@ export const updateHandler = async ({
|
||||
};
|
||||
input: TUpdateFilterSegmentInputSchema;
|
||||
}) => {
|
||||
const { id, name, activeFilters, sorting, columnVisibility, columnSizing, perPage, searchTerm } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
// First, fetch the existing segment to check permissions
|
||||
const existingSegment = await prisma.filterSegment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
...(input.scope === "TEAM"
|
||||
? {
|
||||
scope: "TEAM",
|
||||
teamId: input.teamId,
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
scope: "USER",
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
const repository = new FilterSegmentRepository();
|
||||
return await repository.update({
|
||||
userId: ctx.user.id,
|
||||
input,
|
||||
});
|
||||
|
||||
if (!existingSegment) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Filter segment not found or you don't have permission to update it",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the filter segment with only the allowed fields
|
||||
const updatedSegment = await prisma.filterSegment.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
activeFilters,
|
||||
sorting,
|
||||
columnVisibility,
|
||||
columnSizing,
|
||||
perPage,
|
||||
searchTerm,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedSegment;
|
||||
};
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ZActiveFilters, ZColumnSizing, ZColumnVisibility, ZSortingState } from "./types";
|
||||
|
||||
// Base schema for common fields that can be updated
|
||||
const baseUpdateSchema = {
|
||||
name: z.string().min(1).optional(),
|
||||
tableIdentifier: z.string().min(1).optional(),
|
||||
activeFilters: ZActiveFilters.optional(),
|
||||
sorting: ZSortingState.optional(),
|
||||
columnVisibility: ZColumnVisibility.optional(),
|
||||
columnSizing: ZColumnSizing.optional(),
|
||||
searchTerm: z.string().nullable().optional(),
|
||||
perPage: z.number().int().min(1).optional(),
|
||||
};
|
||||
|
||||
// Schema for team scope updates
|
||||
const teamUpdateSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
scope: z.literal("TEAM"),
|
||||
teamId: z.number().int().positive(),
|
||||
...baseUpdateSchema,
|
||||
});
|
||||
|
||||
// Schema for user scope updates
|
||||
const userUpdateSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
scope: z.literal("USER"),
|
||||
...baseUpdateSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateFilterSegmentInputSchema = z.discriminatedUnion("scope", [
|
||||
teamUpdateSchema,
|
||||
userUpdateSchema,
|
||||
]);
|
||||
|
||||
export type TUpdateFilterSegmentInputSchema = z.infer<typeof ZUpdateFilterSegmentInputSchema>;
|
||||
Reference in New Issue
Block a user