mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-05 16:19:55 -06:00
chore: caching for action and actionClass services (#1264)
This commit is contained in:
18
packages/lib/action/cache.ts
Normal file
18
packages/lib/action/cache.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { revalidateTag } from "next/cache";
|
||||
|
||||
interface RevalidateProps {
|
||||
environmentId?: string;
|
||||
}
|
||||
|
||||
export const actionCache = {
|
||||
tag: {
|
||||
byEnvironmentId(environmentId: string): string {
|
||||
return `environments-${environmentId}-actions`;
|
||||
},
|
||||
},
|
||||
revalidate({ environmentId }: RevalidateProps): void {
|
||||
if (environmentId) {
|
||||
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -8,12 +8,12 @@ import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { getActionClassCacheTag } from "../actionClass/service";
|
||||
import { actionClassCache } from "../actionClass/cache";
|
||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { getSessionCached } from "../session/service";
|
||||
import { createActionClass, getActionClassByEnvironmentIdAndName } from "../actionClass/service";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
export const getActionsCacheTag = (environmentId: string): string => `environments-${environmentId}-actions`;
|
||||
import { actionCache } from "./cache";
|
||||
|
||||
export const getActionsByEnvironmentId = async (
|
||||
environmentId: string,
|
||||
@@ -60,12 +60,13 @@ export const getActionsByEnvironmentId = async (
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`environments-${environmentId}-actionClasses`],
|
||||
[`getActionsByEnvironmentId-${environmentId}-${page}`],
|
||||
{
|
||||
tags: [getActionsCacheTag(environmentId)],
|
||||
tags: [actionCache.tag.byEnvironmentId(environmentId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
// since the unstable_cache function does not support deserialization of dates, we need to manually deserialize them
|
||||
// https://github.com/vercel/next.js/issues/51613
|
||||
return actions.map((action) => ({
|
||||
@@ -76,6 +77,7 @@ export const getActionsByEnvironmentId = async (
|
||||
|
||||
export const createAction = async (data: TActionInput): Promise<TAction> => {
|
||||
validateInputs([data, ZActionInput]);
|
||||
|
||||
const { environmentId, name, properties, sessionId } = data;
|
||||
|
||||
let eventType: TActionClassType = "code";
|
||||
@@ -89,6 +91,16 @@ export const createAction = async (data: TActionInput): Promise<TAction> => {
|
||||
throw new ResourceNotFoundError("Session", sessionId);
|
||||
}
|
||||
|
||||
let actionClass = await getActionClassByEnvironmentIdAndName(environmentId, name);
|
||||
|
||||
if (!actionClass) {
|
||||
actionClass = await createActionClass(environmentId, {
|
||||
name,
|
||||
type: eventType,
|
||||
environmentId,
|
||||
});
|
||||
}
|
||||
|
||||
const action = await prisma.event.create({
|
||||
data: {
|
||||
properties,
|
||||
@@ -98,91 +110,101 @@ export const createAction = async (data: TActionInput): Promise<TAction> => {
|
||||
},
|
||||
},
|
||||
eventClass: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name,
|
||||
type: eventType,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
connect: {
|
||||
id: actionClass.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
eventClass: true,
|
||||
},
|
||||
});
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(sessionId);
|
||||
revalidateTag(getActionClassCacheTag(name, environmentId));
|
||||
revalidateTag(getActionsCacheTag(environmentId));
|
||||
actionCache.revalidate({
|
||||
environmentId,
|
||||
});
|
||||
|
||||
return {
|
||||
id: action.id,
|
||||
createdAt: action.createdAt,
|
||||
sessionId: action.sessionId,
|
||||
properties: action.properties,
|
||||
actionClass: action.eventClass,
|
||||
actionClass,
|
||||
};
|
||||
};
|
||||
|
||||
export const getActionCountInLastHour = async (actionClassId: string): Promise<number> => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
try {
|
||||
const numEventsLastHour = await prisma.event.count({
|
||||
where: {
|
||||
eventClassId: actionClassId,
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
return numEventsLastHour;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export const getActionCountInLastHour = async (actionClassId: string): Promise<number> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
|
||||
export const getActionCountInLast24Hours = async (actionClassId: string): Promise<number> => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
try {
|
||||
const numEventsLast24Hours = await prisma.event.count({
|
||||
where: {
|
||||
eventClassId: actionClassId,
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
return numEventsLast24Hours;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
try {
|
||||
const numEventsLastHour = await prisma.event.count({
|
||||
where: {
|
||||
eventClassId: actionClassId,
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
return numEventsLastHour;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getActionCountInLastHour-${actionClassId}`],
|
||||
{
|
||||
tags: [actionClassCache.tag.byId(actionClassId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
export const getActionCountInLast7Days = async (actionClassId: string): Promise<number> => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
try {
|
||||
const numEventsLast7Days = await prisma.event.count({
|
||||
where: {
|
||||
eventClassId: actionClassId,
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
return numEventsLast7Days;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export const getActionCountInLast24Hours = async (actionClassId: string): Promise<number> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
|
||||
try {
|
||||
const numEventsLast24Hours = await prisma.event.count({
|
||||
where: {
|
||||
eventClassId: actionClassId,
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
return numEventsLast24Hours;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getActionCountInLast24Hours-${actionClassId}`],
|
||||
{
|
||||
tags: [actionClassCache.tag.byId(actionClassId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
export const getActionCountInLast7Days = async (actionClassId: string): Promise<number> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
|
||||
try {
|
||||
const numEventsLast7Days = await prisma.event.count({
|
||||
where: {
|
||||
eventClassId: actionClassId,
|
||||
createdAt: {
|
||||
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
return numEventsLast7Days;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getActionCountInLast7Days-${actionClassId}`],
|
||||
{
|
||||
tags: [actionClassCache.tag.byId(actionClassId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getActionClass } from "./service";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { actionClassCache } from "./cache";
|
||||
|
||||
export const canUserAccessActionClass = async (userId: string, actionClassId: string): Promise<boolean> =>
|
||||
await unstable_cache(
|
||||
@@ -23,5 +24,8 @@ export const canUserAccessActionClass = async (userId: string, actionClassId: st
|
||||
},
|
||||
|
||||
[`users-${userId}-actionClasses-${actionClassId}`],
|
||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [`actionClasses-${actionClassId}`] }
|
||||
{
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
tags: [actionClassCache.tag.byId(actionClassId)],
|
||||
}
|
||||
)();
|
||||
|
||||
34
packages/lib/actionClass/cache.ts
Normal file
34
packages/lib/actionClass/cache.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { revalidateTag } from "next/cache";
|
||||
|
||||
interface RevalidateProps {
|
||||
environmentId?: string;
|
||||
name?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const actionClassCache = {
|
||||
tag: {
|
||||
byNameAndEnvironmentId(environmentId: string, name: string): string {
|
||||
return `environments-${environmentId}-actionClass-${name}`;
|
||||
},
|
||||
byEnvironmentId(environmentId: string): string {
|
||||
return `environments-${environmentId}-actionClasses`;
|
||||
},
|
||||
byId(id: string): string {
|
||||
return `actionClasses-${id}`;
|
||||
},
|
||||
},
|
||||
revalidate({ environmentId, name, id }: RevalidateProps): void {
|
||||
if (environmentId) {
|
||||
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||
}
|
||||
|
||||
if (id) {
|
||||
revalidateTag(this.tag.byId(id));
|
||||
}
|
||||
|
||||
if (name && environmentId) {
|
||||
revalidateTag(this.tag.byNameAndEnvironmentId(name, environmentId));
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -7,14 +7,9 @@ import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { ZOptionalNumber, ZString } from "@formbricks/types/v1/common";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
export const getActionClassCacheTag = (name: string, environmentId: string): string =>
|
||||
`environments-${environmentId}-actionClass-${name}`;
|
||||
|
||||
const getActionClassesCacheTag = (environmentId: string): string =>
|
||||
`environments-${environmentId}-actionClasses`;
|
||||
import { actionClassCache } from "./cache";
|
||||
|
||||
const select = {
|
||||
id: true,
|
||||
@@ -31,6 +26,7 @@ export const getActionClasses = (environmentId: string, page?: number): Promise<
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
const actionClasses = await prisma.eventClass.findMany({
|
||||
where: {
|
||||
@@ -49,34 +45,73 @@ export const getActionClasses = (environmentId: string, page?: number): Promise<
|
||||
throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`);
|
||||
}
|
||||
},
|
||||
[`environments-${environmentId}-actionClasses`],
|
||||
[`getActionClasses-${environmentId}-${page}`],
|
||||
{
|
||||
tags: [getActionClassesCacheTag(environmentId)],
|
||||
tags: [actionClassCache.tag.byEnvironmentId(environmentId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
export const getActionClass = async (actionClassId: string): Promise<TActionClass | null> => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
try {
|
||||
let actionClass = await prisma.eventClass.findUnique({
|
||||
where: {
|
||||
id: actionClassId,
|
||||
},
|
||||
select,
|
||||
});
|
||||
export const getActionClassByEnvironmentIdAndName = async (
|
||||
environmentId: string,
|
||||
name: string
|
||||
): Promise<TActionClass | null> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [name, ZString]);
|
||||
|
||||
return actionClass;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when fetching action`);
|
||||
}
|
||||
};
|
||||
try {
|
||||
const actionClass = await prisma.eventClass.findFirst({
|
||||
where: {
|
||||
name,
|
||||
environmentId,
|
||||
},
|
||||
select,
|
||||
});
|
||||
|
||||
return actionClass;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when fetching action`);
|
||||
}
|
||||
},
|
||||
[`getActionClass-${environmentId}-${name}`],
|
||||
{
|
||||
tags: [actionClassCache.tag.byNameAndEnvironmentId(environmentId, name)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
export const getActionClass = async (actionClassId: string): Promise<TActionClass | null> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
|
||||
try {
|
||||
const actionClass = await prisma.eventClass.findUnique({
|
||||
where: {
|
||||
id: actionClassId,
|
||||
},
|
||||
select,
|
||||
});
|
||||
|
||||
return actionClass;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when fetching action`);
|
||||
}
|
||||
},
|
||||
[`getActionClass-${actionClassId}`],
|
||||
{
|
||||
tags: [actionClassCache.tag.byId(actionClassId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
export const deleteActionClass = async (
|
||||
environmentId: string,
|
||||
actionClassId: string
|
||||
): Promise<TActionClass> => {
|
||||
validateInputs([environmentId, ZId], [actionClassId, ZId]);
|
||||
|
||||
try {
|
||||
const result = await prisma.eventClass.delete({
|
||||
where: {
|
||||
@@ -86,8 +121,10 @@ export const deleteActionClass = async (
|
||||
});
|
||||
if (result === null) throw new ResourceNotFoundError("Action", actionClassId);
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(getActionClassesCacheTag(result.environmentId));
|
||||
actionClassCache.revalidate({
|
||||
environmentId,
|
||||
id: actionClassId,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -102,8 +139,9 @@ export const createActionClass = async (
|
||||
actionClass: TActionClassInput
|
||||
): Promise<TActionClass> => {
|
||||
validateInputs([environmentId, ZId], [actionClass, ZActionClassInput]);
|
||||
|
||||
try {
|
||||
const result = await prisma.eventClass.create({
|
||||
const actionClassPrisma = await prisma.eventClass.create({
|
||||
data: {
|
||||
name: actionClass.name,
|
||||
description: actionClass.description,
|
||||
@@ -116,10 +154,13 @@ export const createActionClass = async (
|
||||
select,
|
||||
});
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(getActionClassesCacheTag(environmentId));
|
||||
actionClassCache.revalidate({
|
||||
name: actionClassPrisma.name,
|
||||
environmentId: actionClassPrisma.environmentId,
|
||||
id: actionClassPrisma.id,
|
||||
});
|
||||
|
||||
return result;
|
||||
return actionClassPrisma;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when creating an action for environment ${environmentId}`);
|
||||
}
|
||||
@@ -131,6 +172,7 @@ export const updateActionClass = async (
|
||||
inputActionClass: Partial<TActionClassInput>
|
||||
): Promise<TActionClass> => {
|
||||
validateInputs([environmentId, ZId], [actionClassId, ZId], [inputActionClass, ZActionClassInput.partial()]);
|
||||
|
||||
try {
|
||||
const result = await prisma.eventClass.update({
|
||||
where: {
|
||||
@@ -148,29 +190,14 @@ export const updateActionClass = async (
|
||||
});
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(getActionClassCacheTag(result.name, result.environmentId));
|
||||
revalidateTag(getActionClassesCacheTag(result.environmentId));
|
||||
actionClassCache.revalidate({
|
||||
environmentId: result.environmentId,
|
||||
name: result.name,
|
||||
id: result.id,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when updating an action for environment ${environmentId}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getActionClassCached = async (name: string, environmentId: string) =>
|
||||
unstable_cache(
|
||||
async (): Promise<TActionClass | null> => {
|
||||
validateInputs([name, ZString], [environmentId, ZId]);
|
||||
return await prisma.eventClass.findFirst({
|
||||
where: {
|
||||
name,
|
||||
environmentId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[`environments-${environmentId}-actionClasses-${name}`],
|
||||
{
|
||||
tags: [getActionClassesCacheTag(environmentId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -49,7 +49,7 @@ export const ZActionClassInput = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
noCodeConfig: ZActionClassNoCodeConfig.nullish(),
|
||||
type: z.enum(["code", "noCode"]),
|
||||
type: z.enum(["code", "noCode", "automatic"]),
|
||||
});
|
||||
|
||||
export const ZActionClassAutomaticInput = z.object({
|
||||
|
||||
Reference in New Issue
Block a user