mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 05:40:02 -06:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c47face662 | ||
|
|
2361cf4b5a | ||
|
|
3720c7690d | ||
|
|
3de073f93a | ||
|
|
841b96c5bb | ||
|
|
3bb6ce3250 | ||
|
|
82c986baa4 | ||
|
|
d72283df55 | ||
|
|
fcfea44d7f | ||
|
|
5f71b91704 | ||
|
|
94e872025d | ||
|
|
7ad7a255b6 |
3
.github/workflows/release-docker.yml
vendored
3
.github/workflows/release-docker.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
SECRET=$(openssl rand -hex 32)
|
SECRET=$(openssl rand -hex 32)
|
||||||
echo "NEXTAUTH_SECRET=$SECRET" >> $GITHUB_ENV
|
echo "NEXTAUTH_SECRET=$SECRET" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Generate Random NEXTAUTH_SECRET
|
- name: Generate Random ENCRYPTION_KEY
|
||||||
run: |
|
run: |
|
||||||
SECRET=$(openssl rand -hex 32)
|
SECRET=$(openssl rand -hex 32)
|
||||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||||
@@ -55,3 +55,4 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }}
|
NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }}
|
||||||
DATABASE_URL=${{ env.DATABASE_URL }}
|
DATABASE_URL=${{ env.DATABASE_URL }}
|
||||||
|
ENCRYPTION_KEY=${{ env.ENCRYPTION_KEY }}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formbricks/js": "workspace:*",
|
"@formbricks/js": "workspace:*",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"next": "13.5.5",
|
"next": "14.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "18.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ You should store constants in `packages/lib/constants`
|
|||||||
|
|
||||||
## Types should be in the packages folder
|
## Types should be in the packages folder
|
||||||
|
|
||||||
You should store type in `packages/types/v1`
|
You should store type in `packages/types`
|
||||||
|
|
||||||
## Read environment variables from `.env.mjs`
|
## Read environment variables from `.env.mjs`
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default function Header() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const stickyNavClass = stickyNav
|
const stickyNavClass = stickyNav
|
||||||
? `bg-transparent shadow-md backdrop-blur-lg fixed top-0 z-30 w-full`
|
? `bg-transparent dark:bg-slate-900/[0.8] shadow-md backdrop-blur-lg fixed top-0 z-30 w-full`
|
||||||
: "relative";
|
: "relative";
|
||||||
return (
|
return (
|
||||||
<Popover className={`${stickyNavClass}`} as="header">
|
<Popover className={`${stickyNavClass}`} as="header">
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
|||||||
"Survey granular user segments at any point in the user journey. Gather up to 6x more insights with targeted micro-surveys. All open-source.",
|
"Survey granular user segments at any point in the user journey. Gather up to 6x more insights with targeted micro-surveys. All open-source.",
|
||||||
href: "https://formbricks.com",
|
href: "https://formbricks.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Firecamp",
|
||||||
|
description: "vscode for apis, open-source postman/insomnia alternative",
|
||||||
|
href: "https://firecamp.io",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Ghostfolio",
|
name: "Ghostfolio",
|
||||||
description:
|
description:
|
||||||
@@ -138,6 +143,12 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
|||||||
description:
|
description:
|
||||||
"Sniffnet is a network monitoring tool to help you easily keep track of your Internet traffic.",
|
"Sniffnet is a network monitoring tool to help you easily keep track of your Internet traffic.",
|
||||||
href: "https://www.sniffnet.net",
|
href: "https://www.sniffnet.net",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Spark.NET",
|
||||||
|
description:
|
||||||
|
"The .NET Web Framework for Makers. Build production ready, full-stack web applications fast without sweating the small stuff.",
|
||||||
|
href: "https://spark-framework.net",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Tolgee",
|
name: "Tolgee",
|
||||||
@@ -173,17 +184,6 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
|||||||
description: "Webstudio is an open source alternative to Webflow",
|
description: "Webstudio is an open source alternative to Webflow",
|
||||||
href: "https://webstudio.is",
|
href: "https://webstudio.is",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Spark.NET",
|
|
||||||
description:
|
|
||||||
"The .NET Web Framework for Makers. Build production ready, full-stack web applications fast without sweating the small stuff.",
|
|
||||||
href: "https://spark-framework.net",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Firecamp",
|
|
||||||
description: "vscode for apis, open-source postman/insomnia alternative",
|
|
||||||
href: "https://firecamp.io",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,21 +50,11 @@ const HowTo = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const SideQuests = [
|
const SideQuests = [
|
||||||
{
|
|
||||||
points: "Join the Tribe Tweet (100 Points)",
|
|
||||||
quest: "Tweet a single “🧱” emoji before the 7th of October EOD to join the #FormTribe.",
|
|
||||||
proof: "Share the link to the tweet in the “side-quest” channel.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
points: "Spread the Word Tweet (100 Points)",
|
points: "Spread the Word Tweet (100 Points)",
|
||||||
quest: "Tweet “🧱🚀” on the day of the ProductHunt launch to spread the word.",
|
quest: "Tweet “🧱🚀” on the day of the ProductHunt launch to spread the word.",
|
||||||
proof: "Share the link to the tweet in the “side-quest” channel.",
|
proof: "Share the link to the tweet in the “side-quest” channel.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
points: "Setup Insights (200 Points)",
|
|
||||||
quest: "Screen record yourself setting up the Formbricks dev environment.",
|
|
||||||
proof: "Upload to WeTransfer and send to johannes@formbricks.com",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
points: "Meme Magic (50 Points + up to 100 Points)",
|
points: "Meme Magic (50 Points + up to 100 Points)",
|
||||||
quest:
|
quest:
|
||||||
@@ -82,25 +72,15 @@ const SideQuests = [
|
|||||||
quest: "Illustrate a captivating background for survey enthusiasts (more infos on Notion).",
|
quest: "Illustrate a captivating background for survey enthusiasts (more infos on Notion).",
|
||||||
proof: "Share the design in the “side-quest” channel.",
|
proof: "Share the design in the “side-quest” channel.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
points: "Transform Animation to CSS (350 Points per background)",
|
|
||||||
quest: "Animate an existing background to CSS versions (more infos on Notion).",
|
|
||||||
proof: "Share the animated background.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
points: "Enhance Docs (50-250 Points)",
|
|
||||||
quest:
|
|
||||||
"Add a new section to our docs where you see gaps. Follow the current style of documentation incl. code snippets and screenshots. Pls no spam.",
|
|
||||||
proof: "Open a PR with “docs” in the title",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
points: "Starry-eyed Supporter (250 Points)",
|
points: "Starry-eyed Supporter (250 Points)",
|
||||||
quest: "Get five friends to star our repository.",
|
quest: "Get five friends to star our repository.",
|
||||||
proof: "Share 5 screenshots of the chats where you asked them and they confirmed + their GitHub names",
|
proof: "Share 5 screenshots of the chats where you asked them and they confirmed + their GitHub names",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
points: "Bug Hunter (50-250 Points)",
|
points: "Bug Hunter (100 Points)",
|
||||||
quest: "Find and report any functionality bugs.",
|
quest:
|
||||||
|
"Find and report any bugs in our core product. We will close all bugs on the landing page bc we don't have time for that before the launch :)",
|
||||||
proof: "Open a bug issue in our repository.",
|
proof: "Open a bug issue in our repository.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -109,11 +89,6 @@ const SideQuests = [
|
|||||||
"Find someone whose name would be funny as a play on words with “brick”. Then, with the help of AI, create a brick version of this person like Brick Astley, Brickj Minaj, etc. For extra points, tweet it, tag us and score +5 for each like.",
|
"Find someone whose name would be funny as a play on words with “brick”. Then, with the help of AI, create a brick version of this person like Brick Astley, Brickj Minaj, etc. For extra points, tweet it, tag us and score +5 for each like.",
|
||||||
proof: "Share your art or link to the tweet in the “side-quest” channel.",
|
proof: "Share your art or link to the tweet in the “side-quest” channel.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
points: "SEO Sage (50-250 Points)",
|
|
||||||
quest: "Provide detailed SEO recommendations or improvements for our main website.",
|
|
||||||
proof: "Share your insights.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
points: "Community Connector (50 points each, up to 250 points)",
|
points: "Community Connector (50 points each, up to 250 points)",
|
||||||
quest:
|
quest:
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ ENV DATABASE_URL=$DATABASE_URL
|
|||||||
ARG NEXTAUTH_SECRET
|
ARG NEXTAUTH_SECRET
|
||||||
ENV NEXTAUTH_SECRET=$NEXTAUTH_SECRET
|
ENV NEXTAUTH_SECRET=$NEXTAUTH_SECRET
|
||||||
|
|
||||||
|
ARG ENCRYPTION_KEY
|
||||||
|
ENV ENCRYPTION_KEY=$ENCRYPTION_KEY
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function MembersSettingsPage({ params }) {
|
|||||||
throw new Error("Environment not found");
|
throw new Error("Environment not found");
|
||||||
}
|
}
|
||||||
const tags = await getTagsByEnvironmentId(params.environmentId);
|
const tags = await getTagsByEnvironmentId(params.environmentId);
|
||||||
const environmentTagsCount = await getTagsOnResponsesCount();
|
const environmentTagsCount = await getTagsOnResponsesCount(params.environmentId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync";
|
import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync";
|
||||||
import { responses } from "@/app/lib/api/response";
|
import { responses } from "@/app/lib/api/response";
|
||||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||||
import { createAttributeClass, getAttributeClassByNameCached } from "@formbricks/lib/attributeClass/service";
|
import { createAttributeClass, getAttributeClassByName } from "@formbricks/lib/attributeClass/service";
|
||||||
import { personCache } from "@formbricks/lib/person/cache";
|
import { personCache } from "@formbricks/lib/person/cache";
|
||||||
import { getPerson, updatePersonAttribute } from "@formbricks/lib/person/service";
|
import { getPerson, updatePersonAttribute } from "@formbricks/lib/person/service";
|
||||||
import { ZJsPeopleAttributeInput } from "@formbricks/types/js";
|
import { ZJsPeopleAttributeInput } from "@formbricks/types/js";
|
||||||
@@ -35,7 +35,7 @@ export async function POST(req: Request, { params }): Promise<NextResponse> {
|
|||||||
return responses.notFoundResponse("Person", personId, true);
|
return responses.notFoundResponse("Person", personId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let attributeClass = await getAttributeClassByNameCached(environmentId, key);
|
let attributeClass = await getAttributeClassByName(environmentId, key);
|
||||||
|
|
||||||
// create new attribute class if not found
|
// create new attribute class if not found
|
||||||
if (attributeClass === null) {
|
if (attributeClass === null) {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
|
|||||||
import { SERVICES_REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||||
import { displayCache } from "@formbricks/lib/display/cache";
|
import { displayCache } from "@formbricks/lib/display/cache";
|
||||||
import { getDisplaysByPersonId } from "@formbricks/lib/display/service";
|
import { getDisplaysByPersonId } from "@formbricks/lib/display/service";
|
||||||
import { getProductByEnvironmentIdCached, getProductCacheTag } from "@formbricks/lib/product/service";
|
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||||
import { getSurveyCacheTag, getSurveys } from "@formbricks/lib/survey/service";
|
import { productCache } from "@formbricks/lib/product/cache";
|
||||||
|
import { getSurveys } from "@formbricks/lib/survey/service";
|
||||||
|
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||||
import { TPerson } from "@formbricks/types/people";
|
import { TPerson } from "@formbricks/types/people";
|
||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
@@ -23,8 +25,8 @@ export const getSyncSurveysCached = (environmentId: string, person: TPerson) =>
|
|||||||
{
|
{
|
||||||
tags: [
|
tags: [
|
||||||
displayCache.tag.byPersonId(person.id),
|
displayCache.tag.byPersonId(person.id),
|
||||||
getSurveyCacheTag(environmentId),
|
surveyCache.tag.byEnvironmentId(environmentId),
|
||||||
getProductCacheTag(environmentId),
|
productCache.tag.byEnvironmentId(environmentId),
|
||||||
],
|
],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
@@ -35,7 +37,7 @@ export const getSyncSurveys = async (
|
|||||||
person: TPerson
|
person: TPerson
|
||||||
): Promise<TSurveyWithTriggers[]> => {
|
): Promise<TSurveyWithTriggers[]> => {
|
||||||
// get recontactDays from product
|
// get recontactDays from product
|
||||||
const product = await getProductByEnvironmentIdCached(environmentId);
|
const product = await getProductByEnvironmentId(environmentId);
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error("Product not found");
|
throw new Error("Product not found");
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { MAU_LIMIT } from "@formbricks/lib/constants";
|
|||||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||||
import { createPerson, getMonthlyActivePeopleCount, getPerson } from "@formbricks/lib/person/service";
|
import { createPerson, getMonthlyActivePeopleCount, getPerson } from "@formbricks/lib/person/service";
|
||||||
import { getProductByEnvironmentIdCached } from "@formbricks/lib/product/service";
|
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||||
import { createSession, extendSession, getSessionCached } from "@formbricks/lib/session/service";
|
import { createSession, extendSession, getSession } from "@formbricks/lib/session/service";
|
||||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||||
import { TEnvironment } from "@formbricks/types/environment";
|
import { TEnvironment } from "@formbricks/types/environment";
|
||||||
import { TJsState } from "@formbricks/types/js";
|
import { TJsState } from "@formbricks/types/js";
|
||||||
@@ -41,7 +41,7 @@ export const getUpdatedState = async (
|
|||||||
// don't allow new people or sessions
|
// don't allow new people or sessions
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
const session = await getSessionCached(sessionId);
|
const session = await getSession(sessionId);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
// don't allow new sessions
|
// don't allow new sessions
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
@@ -74,7 +74,7 @@ export const getUpdatedState = async (
|
|||||||
session = await createSession(person.id);
|
session = await createSession(person.id);
|
||||||
} else {
|
} else {
|
||||||
// check validity of person & session
|
// check validity of person & session
|
||||||
session = await getSessionCached(sessionId);
|
session = await getSession(sessionId);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
// create a new session
|
// create a new session
|
||||||
session = await createSession(person.id);
|
session = await createSession(person.id);
|
||||||
@@ -102,7 +102,7 @@ export const getUpdatedState = async (
|
|||||||
const [surveys, noCodeActionClasses, product] = await Promise.all([
|
const [surveys, noCodeActionClasses, product] = await Promise.all([
|
||||||
getSyncSurveysCached(environmentId, person),
|
getSyncSurveysCached(environmentId, person),
|
||||||
getActionClasses(environmentId),
|
getActionClasses(environmentId),
|
||||||
getProductByEnvironmentIdCached(environmentId),
|
getProductByEnvironmentId(environmentId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ImageResponse, NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
|
import { ImageResponse } from "next/og";
|
||||||
// App router includes @vercel/og.
|
// App router includes @vercel/og.
|
||||||
// No need to install it.
|
// No need to install it.
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import { createId } from "@paralleldrive/cuid2";
|
|||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
assetPrefix: process.env.ASSET_PREFIX_URL || undefined,
|
assetPrefix: process.env.ASSET_PREFIX_URL || undefined,
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
experimental: {
|
|
||||||
serverActions: true,
|
|
||||||
},
|
|
||||||
transpilePackages: ["@formbricks/database", "@formbricks/ee", "@formbricks/ui", "@formbricks/lib"],
|
transpilePackages: ["@formbricks/database", "@formbricks/ee", "@formbricks/ui", "@formbricks/lib"],
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@formbricks/web",
|
"name": "@formbricks/web",
|
||||||
"version": "1.1.0",
|
"version": "1.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf .turbo node_modules .next",
|
"clean": "rimraf .turbo node_modules .next",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"lru-cache": "^10.0.1",
|
"lru-cache": "^10.0.1",
|
||||||
"lucide-react": "^0.288.0",
|
"lucide-react": "^0.288.0",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"next": "13.5.6",
|
"next": "14.0.0",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"posthog-js": "^1.85.1",
|
"posthog-js": "^1.85.1",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Prisma } from "@prisma/client";
|
|||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { revalidateTag, unstable_cache } from "next/cache";
|
||||||
import { actionClassCache } from "../actionClass/cache";
|
import { actionClassCache } from "../actionClass/cache";
|
||||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { getSessionCached } from "../session/service";
|
import { getSession } from "../session/service";
|
||||||
import { createActionClass, getActionClassByEnvironmentIdAndName } from "../actionClass/service";
|
import { createActionClass, getActionClassByEnvironmentIdAndName } from "../actionClass/service";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { actionCache } from "./cache";
|
import { actionCache } from "./cache";
|
||||||
@@ -136,7 +136,7 @@ export const createAction = async (data: TActionInput): Promise<TAction> => {
|
|||||||
eventType = "automatic";
|
eventType = "automatic";
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await getSessionCached(sessionId);
|
const session = await getSession(sessionId);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
throw new ResourceNotFoundError("Session", sessionId);
|
throw new ResourceNotFoundError("Session", sessionId);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { hasUserEnvironmentAccess } from "../environment/auth";
|
|||||||
import { getApiKey } from "./service";
|
import { getApiKey } from "./service";
|
||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { apiKeyCache } from "./cache";
|
||||||
|
|
||||||
export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise<boolean> =>
|
export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise<boolean> =>
|
||||||
await unstable_cache(
|
await unstable_cache(
|
||||||
@@ -21,6 +22,6 @@ export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Pro
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
[`users-${userId}-apiKeys-${apiKeyId}`],
|
[`canUserAccessApiKey-${userId}-${apiKeyId}`],
|
||||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [`apiKeys-${apiKeyId}`] }
|
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [apiKeyCache.tag.byId(apiKeyId)] }
|
||||||
)();
|
)();
|
||||||
|
|||||||
34
packages/lib/apiKey/cache.ts
Normal file
34
packages/lib/apiKey/cache.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
hashedKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const apiKeyCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `apiKeys-${id}`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-apiKeys`;
|
||||||
|
},
|
||||||
|
byHashedKey(hashedKey: string) {
|
||||||
|
return `apiKeys-${hashedKey}-apiKey`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, environmentId, hashedKey }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashedKey) {
|
||||||
|
revalidateTag(this.tag.byHashedKey(hashedKey));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -9,55 +9,74 @@ import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbr
|
|||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
||||||
import { ITEMS_PER_PAGE } from "../constants";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
import { apiKeyCache } from "./cache";
|
||||||
|
|
||||||
export const getApiKey = async (apiKeyId: string): Promise<TApiKey | null> => {
|
export const getApiKey = async (apiKeyId: string): Promise<TApiKey | null> =>
|
||||||
validateInputs([apiKeyId, ZString]);
|
unstable_cache(
|
||||||
if (!apiKeyId) {
|
async () => {
|
||||||
throw new InvalidInputError("API key cannot be null or undefined.");
|
validateInputs([apiKeyId, ZString]);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!apiKeyId) {
|
||||||
const apiKeyData = await prisma.apiKey.findUnique({
|
throw new InvalidInputError("API key cannot be null or undefined.");
|
||||||
where: {
|
}
|
||||||
id: apiKeyId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiKeyData) {
|
try {
|
||||||
throw new ResourceNotFoundError("API Key from ID", apiKeyId);
|
const apiKeyData = await prisma.apiKey.findUnique({
|
||||||
|
where: {
|
||||||
|
id: apiKeyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKeyData) {
|
||||||
|
throw new ResourceNotFoundError("API Key from ID", apiKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiKeyData;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getApiKey-${apiKeyId}`],
|
||||||
|
{
|
||||||
|
tags: [apiKeyCache.tag.byId(apiKeyId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
return apiKeyData;
|
export const getApiKeys = async (environmentId: string, page?: number): Promise<TApiKey[]> =>
|
||||||
} catch (error) {
|
unstable_cache(
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
async () => {
|
||||||
throw new DatabaseError(error.message);
|
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const apiKeys = await prisma.apiKey.findMany({
|
||||||
|
where: {
|
||||||
|
environmentId,
|
||||||
|
},
|
||||||
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
return apiKeys;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getApiKeys-${environmentId}-${page}`],
|
||||||
|
{
|
||||||
|
tags: [apiKeyCache.tag.byEnvironmentId(environmentId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
|
)();
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getApiKeys = async (environmentId: string, page?: number): Promise<TApiKey[]> => {
|
|
||||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const apiKeys = await prisma.apiKey.findMany({
|
|
||||||
where: {
|
|
||||||
environmentId,
|
|
||||||
},
|
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
return apiKeys;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hashApiKey = (key: string): string => createHash("sha256").update(key).digest("hex");
|
export const hashApiKey = (key: string): string => createHash("sha256").update(key).digest("hex");
|
||||||
|
|
||||||
@@ -75,6 +94,12 @@ export async function createApiKey(environmentId: string, apiKeyData: TApiKeyCre
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
apiKeyCache.revalidate({
|
||||||
|
id: result.id,
|
||||||
|
hashedKey: result.hashedKey,
|
||||||
|
environmentId: result.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
return { ...result, apiKey: key };
|
return { ...result, apiKey: key };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
@@ -85,30 +110,43 @@ export async function createApiKey(environmentId: string, apiKeyData: TApiKeyCre
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getApiKeyFromKey = async (apiKey: string): Promise<TApiKey | null> => {
|
export const getApiKeyFromKey = async (apiKey: string): Promise<TApiKey | null> => {
|
||||||
validateInputs([apiKey, ZString]);
|
const hashedKey = getHash(apiKey);
|
||||||
if (!apiKey) {
|
|
||||||
throw new InvalidInputError("API key cannot be null or undefined.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
return unstable_cache(
|
||||||
const apiKeyData = await prisma.apiKey.findUnique({
|
async () => {
|
||||||
where: {
|
validateInputs([apiKey, ZString]);
|
||||||
hashedKey: getHash(apiKey),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return apiKeyData;
|
if (!apiKey) {
|
||||||
} catch (error) {
|
throw new InvalidInputError("API key cannot be null or undefined.");
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
}
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
|
try {
|
||||||
|
const apiKeyData = await prisma.apiKey.findUnique({
|
||||||
|
where: {
|
||||||
|
hashedKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return apiKeyData;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getApiKeyFromKey-${apiKey}`],
|
||||||
|
{
|
||||||
|
tags: [apiKeyCache.tag.byHashedKey(hashedKey)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
|
)();
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteApiKey = async (id: string): Promise<TApiKey | null> => {
|
export const deleteApiKey = async (id: string): Promise<TApiKey | null> => {
|
||||||
validateInputs([id, ZId]);
|
validateInputs([id, ZId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const deletedApiKeyData = await prisma.apiKey.delete({
|
const deletedApiKeyData = await prisma.apiKey.delete({
|
||||||
where: {
|
where: {
|
||||||
@@ -116,6 +154,12 @@ export const deleteApiKey = async (id: string): Promise<TApiKey | null> => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
apiKeyCache.revalidate({
|
||||||
|
id: deletedApiKeyData.id,
|
||||||
|
hashedKey: deletedApiKeyData.hashedKey,
|
||||||
|
environmentId: deletedApiKeyData.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
return deletedApiKeyData;
|
return deletedApiKeyData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
|||||||
34
packages/lib/attributeClass/cache.ts
Normal file
34
packages/lib/attributeClass/cache.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const attributeClassCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `attributeClass-${id}`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-attributeClasses`;
|
||||||
|
},
|
||||||
|
byEnvironmentIdAndName(environmentId: string, name: string) {
|
||||||
|
return `environments-${environmentId}-name-${name}-attributeClasses`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, environmentId, name }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId && name) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentIdAndName(environmentId, name));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -7,57 +7,81 @@ import {
|
|||||||
TAttributeClassUpdateInput,
|
TAttributeClassUpdateInput,
|
||||||
ZAttributeClassUpdateInput,
|
ZAttributeClassUpdateInput,
|
||||||
TAttributeClassType,
|
TAttributeClassType,
|
||||||
|
ZAttributeClassType,
|
||||||
} from "@formbricks/types/attributeClasses";
|
} from "@formbricks/types/attributeClasses";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { DatabaseError } from "@formbricks/types/errors";
|
import { DatabaseError } from "@formbricks/types/errors";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL, ITEMS_PER_PAGE } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL, ITEMS_PER_PAGE } from "../constants";
|
||||||
import { ZOptionalNumber } from "@formbricks/types/common";
|
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
|
||||||
|
import { attributeClassCache } from "./cache";
|
||||||
const attributeClassesCacheTag = (environmentId: string): string =>
|
import { formatAttributeClassDateFields } from "./util";
|
||||||
`environments-${environmentId}-attributeClasses`;
|
|
||||||
|
|
||||||
const getAttributeClassesCacheKey = (environmentId: string): string[] => [
|
|
||||||
attributeClassesCacheTag(environmentId),
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getAttributeClass = async (attributeClassId: string): Promise<TAttributeClass | null> => {
|
export const getAttributeClass = async (attributeClassId: string): Promise<TAttributeClass | null> => {
|
||||||
validateInputs([attributeClassId, ZId]);
|
const attributeClass = await unstable_cache(
|
||||||
try {
|
async () => {
|
||||||
const attributeClass = await prisma.attributeClass.findFirst({
|
validateInputs([attributeClassId, ZId]);
|
||||||
where: {
|
|
||||||
id: attributeClassId,
|
try {
|
||||||
},
|
return await prisma.attributeClass.findFirst({
|
||||||
});
|
where: {
|
||||||
return attributeClass;
|
id: attributeClassId,
|
||||||
} catch (error) {
|
},
|
||||||
throw new DatabaseError(`Database error when fetching attributeClass with id ${attributeClassId}`);
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError(`Database error when fetching attributeClass with id ${attributeClassId}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getAttributeClass-${attributeClassId}`],
|
||||||
|
{
|
||||||
|
tags: [attributeClassCache.tag.byId(attributeClassId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
|
if (!attributeClass) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return formatAttributeClassDateFields(attributeClass);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAttributeClasses = async (
|
export const getAttributeClasses = async (
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
page?: number
|
page?: number
|
||||||
): Promise<TAttributeClass[]> => {
|
): Promise<TAttributeClass[]> => {
|
||||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
const attributeClasses = await unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const attributeClasses = await prisma.attributeClass.findMany({
|
const attributeClasses = await prisma.attributeClass.findMany({
|
||||||
where: {
|
where: {
|
||||||
environmentId: environmentId,
|
environmentId: environmentId,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: "asc",
|
createdAt: "asc",
|
||||||
},
|
},
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return attributeClasses;
|
return attributeClasses;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError(`Database error when fetching attributeClasses for environment ${environmentId}`);
|
throw new DatabaseError(
|
||||||
}
|
`Database error when fetching attributeClasses for environment ${environmentId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getAttributeClasses-${environmentId}-${page}`],
|
||||||
|
{
|
||||||
|
tags: [attributeClassCache.tag.byEnvironmentId(environmentId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
|
return attributeClasses.map(formatAttributeClassDateFields);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updatetAttributeClass = async (
|
export const updatetAttributeClass = async (
|
||||||
@@ -65,6 +89,7 @@ export const updatetAttributeClass = async (
|
|||||||
data: Partial<TAttributeClassUpdateInput>
|
data: Partial<TAttributeClassUpdateInput>
|
||||||
): Promise<TAttributeClass | null> => {
|
): Promise<TAttributeClass | null> => {
|
||||||
validateInputs([attributeClassId, ZId], [data, ZAttributeClassUpdateInput.partial()]);
|
validateInputs([attributeClassId, ZId], [data, ZAttributeClassUpdateInput.partial()]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const attributeClass = await prisma.attributeClass.update({
|
const attributeClass = await prisma.attributeClass.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -76,7 +101,11 @@ export const updatetAttributeClass = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(attributeClassesCacheTag(attributeClass.environmentId));
|
attributeClassCache.revalidate({
|
||||||
|
id: attributeClass.id,
|
||||||
|
environmentId: attributeClass.environmentId,
|
||||||
|
name: attributeClass.name,
|
||||||
|
});
|
||||||
|
|
||||||
return attributeClass;
|
return attributeClass;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -84,36 +113,32 @@ export const updatetAttributeClass = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAttributeClassByNameCached = async (environmentId: string, name: string) =>
|
export const getAttributeClassByName = async (environmentId: string, name: string) =>
|
||||||
await unstable_cache(
|
await unstable_cache(
|
||||||
async (): Promise<TAttributeClass | null> => {
|
async (): Promise<TAttributeClass | null> => {
|
||||||
return await getAttributeClassByName(environmentId, name);
|
validateInputs([environmentId, ZId], [name, ZString]);
|
||||||
|
|
||||||
|
return await prisma.attributeClass.findFirst({
|
||||||
|
where: {
|
||||||
|
environmentId,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[`environments-${environmentId}-attributeClass-${name}`],
|
[`getAttributeClassByName-${environmentId}-${name}`],
|
||||||
{
|
{
|
||||||
tags: getAttributeClassesCacheKey(environmentId),
|
tags: [attributeClassCache.tag.byEnvironmentIdAndName(environmentId, name)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
export const getAttributeClassByName = async (
|
|
||||||
environmentId: string,
|
|
||||||
name: string
|
|
||||||
): Promise<TAttributeClass | null> => {
|
|
||||||
const attributeClass = await prisma.attributeClass.findFirst({
|
|
||||||
where: {
|
|
||||||
environmentId,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return attributeClass;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createAttributeClass = async (
|
export const createAttributeClass = async (
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
name: string,
|
name: string,
|
||||||
type: TAttributeClassType
|
type: TAttributeClassType
|
||||||
): Promise<TAttributeClass | null> => {
|
): Promise<TAttributeClass | null> => {
|
||||||
|
validateInputs([environmentId, ZId], [name, ZString], [type, ZAttributeClassType]);
|
||||||
|
|
||||||
const attributeClass = await prisma.attributeClass.create({
|
const attributeClass = await prisma.attributeClass.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -125,12 +150,19 @@ export const createAttributeClass = async (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
revalidateTag(attributeClassesCacheTag(environmentId));
|
|
||||||
|
attributeClassCache.revalidate({
|
||||||
|
id: attributeClass.id,
|
||||||
|
environmentId: attributeClass.environmentId,
|
||||||
|
name: attributeClass.name,
|
||||||
|
});
|
||||||
|
|
||||||
return attributeClass;
|
return attributeClass;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAttributeClass = async (attributeClassId: string): Promise<TAttributeClass> => {
|
export const deleteAttributeClass = async (attributeClassId: string): Promise<TAttributeClass> => {
|
||||||
validateInputs([attributeClassId, ZId]);
|
validateInputs([attributeClassId, ZId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const deletedAttributeClass = await prisma.attributeClass.delete({
|
const deletedAttributeClass = await prisma.attributeClass.delete({
|
||||||
where: {
|
where: {
|
||||||
@@ -138,6 +170,12 @@ export const deleteAttributeClass = async (attributeClassId: string): Promise<TA
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
attributeClassCache.revalidate({
|
||||||
|
id: deletedAttributeClass.id,
|
||||||
|
environmentId: deletedAttributeClass.environmentId,
|
||||||
|
name: deletedAttributeClass.name,
|
||||||
|
});
|
||||||
|
|
||||||
return deletedAttributeClass;
|
return deletedAttributeClass;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError(`Database error when deleting webhook with ID ${attributeClassId}`);
|
throw new DatabaseError(`Database error when deleting webhook with ID ${attributeClassId}`);
|
||||||
|
|||||||
14
packages/lib/attributeClass/util.ts
Normal file
14
packages/lib/attributeClass/util.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import "server-only";
|
||||||
|
|
||||||
|
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||||
|
|
||||||
|
export const formatAttributeClassDateFields = (attributeClass: TAttributeClass): TAttributeClass => {
|
||||||
|
if (typeof attributeClass.createdAt === "string") {
|
||||||
|
attributeClass.createdAt = new Date(attributeClass.createdAt);
|
||||||
|
}
|
||||||
|
if (typeof attributeClass.updatedAt === "string") {
|
||||||
|
attributeClass.updatedAt = new Date(attributeClass.updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributeClass;
|
||||||
|
};
|
||||||
@@ -5,8 +5,7 @@ import { prisma } from "@formbricks/database";
|
|||||||
import { symmetricDecrypt, symmetricEncrypt } from "../crypto";
|
import { symmetricDecrypt, symmetricEncrypt } from "../crypto";
|
||||||
import { verifyPassword } from "../auth";
|
import { verifyPassword } from "../auth";
|
||||||
import { totpAuthenticatorCheck } from "../totp";
|
import { totpAuthenticatorCheck } from "../totp";
|
||||||
import { revalidateTag } from "next/cache";
|
import { profileCache } from "../profile/cache";
|
||||||
import { getProfileCacheTag } from "../profile/service";
|
|
||||||
import { ENCRYPTION_KEY } from "../constants";
|
import { ENCRYPTION_KEY } from "../constants";
|
||||||
|
|
||||||
export const setupTwoFactorAuth = async (
|
export const setupTwoFactorAuth = async (
|
||||||
@@ -71,10 +70,10 @@ export const setupTwoFactorAuth = async (
|
|||||||
return { secret, keyUri, dataUri, backupCodes };
|
return { secret, keyUri, dataUri, backupCodes };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enableTwoFactorAuth = async (userId: string, code: string) => {
|
export const enableTwoFactorAuth = async (id: string, code: string) => {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,14 +113,16 @@ export const enableTwoFactorAuth = async (userId: string, code: string) => {
|
|||||||
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
twoFactorEnabled: true,
|
twoFactorEnabled: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getProfileCacheTag(userId));
|
profileCache.revalidate({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Two factor authentication enabled",
|
message: "Two factor authentication enabled",
|
||||||
@@ -134,10 +135,10 @@ type TDisableTwoFactorAuthParams = {
|
|||||||
backupCode?: string;
|
backupCode?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const disableTwoFactorAuth = async (userId: string, params: TDisableTwoFactorAuthParams) => {
|
export const disableTwoFactorAuth = async (id: string, params: TDisableTwoFactorAuthParams) => {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -211,7 +212,7 @@ export const disableTwoFactorAuth = async (userId: string, params: TDisableTwoFa
|
|||||||
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
backupCodes: null,
|
backupCodes: null,
|
||||||
@@ -220,7 +221,9 @@ export const disableTwoFactorAuth = async (userId: string, params: TDisableTwoFa
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getProfileCacheTag(userId));
|
profileCache.revalidate({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Two factor authentication disabled",
|
message: "Two factor authentication disabled",
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { ZId } from "@formbricks/types/environment";
|
|||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { getTeamsByUserIdCacheTag } from "../team/service";
|
import { teamCache } from "../team/cache";
|
||||||
import { revalidateTag } from "next/cache";
|
|
||||||
|
|
||||||
export const hasUserEnvironmentAccess = async (userId: string, environmentId: string) => {
|
export const hasUserEnvironmentAccess = async (userId: string, environmentId: string) => {
|
||||||
return await unstable_cache(
|
return await unstable_cache(
|
||||||
async (): Promise<boolean> => {
|
async (): Promise<boolean> => {
|
||||||
validateInputs([userId, ZId], [environmentId, ZId]);
|
validateInputs([userId, ZId], [environmentId, ZId]);
|
||||||
|
|
||||||
const environment = await prisma.environment.findUnique({
|
const environment = await prisma.environment.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: environmentId,
|
id: environmentId,
|
||||||
@@ -30,12 +30,14 @@ export const hasUserEnvironmentAccess = async (userId: string, environmentId: st
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(userId));
|
|
||||||
|
|
||||||
const environmentUsers = environment?.product.team.memberships.map((member) => member.userId) || [];
|
const environmentUsers = environment?.product.team.memberships.map((member) => member.userId) || [];
|
||||||
return environmentUsers.includes(userId);
|
return environmentUsers.includes(userId);
|
||||||
},
|
},
|
||||||
[`users-${userId}-environments-${environmentId}`],
|
[`hasUserEnvironmentAccess-${userId}-${environmentId}`],
|
||||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [`environments-${environmentId}`] }
|
{
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
tags: [teamCache.tag.byEnvironmentId(environmentId), teamCache.tag.byUserId(userId)],
|
||||||
|
}
|
||||||
)();
|
)();
|
||||||
};
|
};
|
||||||
|
|||||||
34
packages/lib/environment/cache.ts
Normal file
34
packages/lib/environment/cache.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
productId?: string;
|
||||||
|
userId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const environmentCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `environments-${id}`;
|
||||||
|
},
|
||||||
|
byProductId(productId: string) {
|
||||||
|
return `products-${productId}-environments`;
|
||||||
|
},
|
||||||
|
byUserId(userId: string) {
|
||||||
|
return `users-${userId}-environments`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, productId, userId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productId) {
|
||||||
|
revalidateTag(this.tag.byProductId(productId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
revalidateTag(this.tag.byUserId(userId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -14,14 +14,13 @@ import {
|
|||||||
} from "@formbricks/types/environment";
|
} from "@formbricks/types/environment";
|
||||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
|
import { environmentCache } from "./cache";
|
||||||
export const getEnvironmentCacheTag = (environmentId: string) => `environments-${environmentId}`;
|
import { formatEnvironmentDateFields } from "./util";
|
||||||
export const getEnvironmentsCacheTag = (productId: string) => `products-${productId}-environments`;
|
|
||||||
|
|
||||||
export const getEnvironment = (environmentId: string) =>
|
export const getEnvironment = (environmentId: string) =>
|
||||||
unstable_cache(
|
unstable_cache(
|
||||||
@@ -54,9 +53,9 @@ export const getEnvironment = (environmentId: string) =>
|
|||||||
throw new ValidationError("Data validation of environment failed");
|
throw new ValidationError("Data validation of environment failed");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`environments-${environmentId}`],
|
[`getEnvironment-${environmentId}`],
|
||||||
{
|
{
|
||||||
tags: [getEnvironmentCacheTag(environmentId)],
|
tags: [environmentCache.tag.byId(environmentId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -101,9 +100,9 @@ export const getEnvironments = async (productId: string): Promise<TEnvironment[]
|
|||||||
throw new ValidationError("Data validation of environments array failed");
|
throw new ValidationError("Data validation of environments array failed");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`products-${productId}-environments`],
|
[`getEnvironments-${productId}`],
|
||||||
{
|
{
|
||||||
tags: [getEnvironmentsCacheTag(productId)],
|
tags: [environmentCache.tag.byProductId(productId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -123,8 +122,10 @@ export const updateEnvironment = async (
|
|||||||
data: newData,
|
data: newData,
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getEnvironmentsCacheTag(updatedEnvironment.productId));
|
environmentCache.revalidate({
|
||||||
revalidateTag(getEnvironmentCacheTag(environmentId));
|
id: environmentId,
|
||||||
|
productId: updatedEnvironment.productId,
|
||||||
|
});
|
||||||
|
|
||||||
return updatedEnvironment;
|
return updatedEnvironment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -136,29 +137,40 @@ export const updateEnvironment = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getFirstEnvironmentByUserId = async (userId: string): Promise<TEnvironment | null> => {
|
export const getFirstEnvironmentByUserId = async (userId: string): Promise<TEnvironment | null> => {
|
||||||
validateInputs([userId, ZId]);
|
const environment = await unstable_cache(
|
||||||
try {
|
async () => {
|
||||||
return await prisma.environment.findFirst({
|
validateInputs([userId, ZId]);
|
||||||
where: {
|
try {
|
||||||
type: "production",
|
return await prisma.environment.findFirst({
|
||||||
product: {
|
where: {
|
||||||
team: {
|
type: "production",
|
||||||
memberships: {
|
product: {
|
||||||
some: {
|
team: {
|
||||||
userId,
|
memberships: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
},
|
} catch (error) {
|
||||||
});
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
} catch (error) {
|
throw new DatabaseError(error.message);
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
}
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[`getFirstEnvironmentByUserId-${userId}`],
|
||||||
|
{
|
||||||
|
tags: [environmentCache.tag.byUserId(userId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
|
return environment ? formatEnvironmentDateFields(environment) : environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createEnvironment = async (
|
export const createEnvironment = async (
|
||||||
@@ -167,7 +179,7 @@ export const createEnvironment = async (
|
|||||||
): Promise<TEnvironment> => {
|
): Promise<TEnvironment> => {
|
||||||
validateInputs([productId, ZId], [environmentInput, ZEnvironmentCreateInput]);
|
validateInputs([productId, ZId], [environmentInput, ZEnvironmentCreateInput]);
|
||||||
|
|
||||||
return await prisma.environment.create({
|
const environment = await prisma.environment.create({
|
||||||
data: {
|
data: {
|
||||||
type: environmentInput.type || "development",
|
type: environmentInput.type || "development",
|
||||||
product: { connect: { id: productId } },
|
product: { connect: { id: productId } },
|
||||||
@@ -199,4 +211,11 @@ export const createEnvironment = async (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
environmentCache.revalidate({
|
||||||
|
id: environment.id,
|
||||||
|
productId: environment.productId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return environment;
|
||||||
};
|
};
|
||||||
|
|||||||
14
packages/lib/environment/util.ts
Normal file
14
packages/lib/environment/util.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import "server-only";
|
||||||
|
|
||||||
|
import { TEnvironment } from "@formbricks/types/environment";
|
||||||
|
|
||||||
|
export const formatEnvironmentDateFields = (environemt: TEnvironment): TEnvironment => {
|
||||||
|
if (typeof environemt.createdAt === "string") {
|
||||||
|
environemt.createdAt = new Date(environemt.createdAt);
|
||||||
|
}
|
||||||
|
if (typeof environemt.updatedAt === "string") {
|
||||||
|
environemt.updatedAt = new Date(environemt.updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return environemt;
|
||||||
|
};
|
||||||
34
packages/lib/integration/cache.ts
Normal file
34
packages/lib/integration/cache.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const integrationCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `integrations-${id}`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-integrations`;
|
||||||
|
},
|
||||||
|
byEnvironmentIdAndType(environmentId: string, type: string) {
|
||||||
|
return `environments-${environmentId}-type-${type}-integrations`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, environmentId, type }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId && type) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentIdAndType(environmentId, type));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -7,7 +7,9 @@ import { ZId } from "@formbricks/types/environment";
|
|||||||
import { TIntegration, TIntegrationInput, ZIntegrationType } from "@formbricks/types/integration";
|
import { TIntegration, TIntegrationInput, ZIntegrationType } from "@formbricks/types/integration";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
||||||
import { ITEMS_PER_PAGE } from "../constants";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { integrationCache } from "./cache";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
|
||||||
export async function createOrUpdateIntegration(
|
export async function createOrUpdateIntegration(
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
@@ -32,6 +34,10 @@ export async function createOrUpdateIntegration(
|
|||||||
environment: { connect: { id: environmentId } },
|
environment: { connect: { id: environmentId } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
integrationCache.revalidate({
|
||||||
|
environmentId,
|
||||||
|
});
|
||||||
return integration;
|
return integration;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
@@ -42,66 +48,87 @@ export async function createOrUpdateIntegration(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIntegrations = async (environmentId: string, page?: number): Promise<TIntegration[]> => {
|
export const getIntegrations = async (environmentId: string, page?: number): Promise<TIntegration[]> =>
|
||||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await prisma.integration.findMany({
|
const result = await prisma.integration.findMany({
|
||||||
where: {
|
where: {
|
||||||
environmentId,
|
environmentId,
|
||||||
},
|
},
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseError(error.message);
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getIntegrations-${environmentId}-${page}`],
|
||||||
|
{
|
||||||
|
tags: [integrationCache.tag.byEnvironmentId(environmentId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
throw error;
|
)();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getIntegration = async (integrationId: string): Promise<TIntegration | null> => {
|
export const getIntegration = async (integrationId: string): Promise<TIntegration | null> =>
|
||||||
try {
|
unstable_cache(
|
||||||
const result = await prisma.integration.findUnique({
|
async () => {
|
||||||
where: {
|
try {
|
||||||
id: integrationId,
|
const result = await prisma.integration.findUnique({
|
||||||
},
|
where: {
|
||||||
});
|
id: integrationId,
|
||||||
return result;
|
},
|
||||||
} catch (error) {
|
});
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
return result;
|
||||||
throw new DatabaseError(error.message);
|
} catch (error) {
|
||||||
}
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw error;
|
throw new DatabaseError(error.message);
|
||||||
}
|
}
|
||||||
};
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getIntegration-${integrationId}`],
|
||||||
|
{ tags: [integrationCache.tag.byId(integrationId)], revalidate: SERVICES_REVALIDATION_INTERVAL }
|
||||||
|
)();
|
||||||
|
|
||||||
export const getIntegrationByType = async (
|
export const getIntegrationByType = async (
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
type: TIntegrationInput["type"]
|
type: TIntegrationInput["type"]
|
||||||
): Promise<TIntegration | null> => {
|
): Promise<TIntegration | null> =>
|
||||||
validateInputs([environmentId, ZId], [type, ZIntegrationType]);
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([environmentId, ZId], [type, ZIntegrationType]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await prisma.integration.findUnique({
|
const result = await prisma.integration.findUnique({
|
||||||
where: {
|
where: {
|
||||||
type_environmentId: {
|
type_environmentId: {
|
||||||
environmentId,
|
environmentId,
|
||||||
type,
|
type,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseError(error.message);
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getIntegrationByType-${environmentId}-${type}`],
|
||||||
|
{
|
||||||
|
tags: [integrationCache.tag.byEnvironmentIdAndType(environmentId, type)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
throw error;
|
)();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteIntegration = async (integrationId: string): Promise<TIntegration> => {
|
export const deleteIntegration = async (integrationId: string): Promise<TIntegration> => {
|
||||||
validateInputs([integrationId, ZString]);
|
validateInputs([integrationId, ZString]);
|
||||||
@@ -113,6 +140,12 @@ export const deleteIntegration = async (integrationId: string): Promise<TIntegra
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
integrationCache.revalidate({
|
||||||
|
id: integrationData.id,
|
||||||
|
environmentId: integrationData.environmentId,
|
||||||
|
type: integrationData.type,
|
||||||
|
});
|
||||||
|
|
||||||
return integrationData;
|
return integrationData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
|||||||
26
packages/lib/invite/cache.ts
Normal file
26
packages/lib/invite/cache.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
teamId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const inviteCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `invites-${id}`;
|
||||||
|
},
|
||||||
|
byTeamId(teamId: string) {
|
||||||
|
return `teams-${teamId}-invites`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, teamId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
revalidateTag(this.tag.byTeamId(teamId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -15,7 +15,11 @@ import { ResourceNotFoundError, ValidationError, DatabaseError } from "@formbric
|
|||||||
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
||||||
import { sendInviteMemberEmail } from "../emails/emails";
|
import { sendInviteMemberEmail } from "../emails/emails";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ITEMS_PER_PAGE } from "../constants";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
import { inviteCache } from "./cache";
|
||||||
|
import { formatInviteDateFields } from "./util";
|
||||||
|
import { getMembershipByUserIdTeamId } from "../membership/service";
|
||||||
|
|
||||||
const inviteSelect = {
|
const inviteSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -31,16 +35,25 @@ const inviteSelect = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getInvitesByTeamId = async (teamId: string, page?: number): Promise<TInvite[] | null> => {
|
export const getInvitesByTeamId = async (teamId: string, page?: number): Promise<TInvite[] | null> => {
|
||||||
validateInputs([teamId, ZString], [page, ZOptionalNumber]);
|
const invites = await unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([teamId, ZString], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
const invites = await prisma.invite.findMany({
|
return prisma.invite.findMany({
|
||||||
where: { teamId },
|
where: { teamId },
|
||||||
select: inviteSelect,
|
select: inviteSelect,
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
[`getInvitesByTeamId-${teamId}-${page}`],
|
||||||
|
{
|
||||||
|
tags: [inviteCache.tag.byTeamId(teamId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
return invites;
|
return invites.map(formatInviteDateFields);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateInvite = async (inviteId: string, data: TInviteUpdateInput): Promise<TInvite | null> => {
|
export const updateInvite = async (inviteId: string, data: TInviteUpdateInput): Promise<TInvite | null> => {
|
||||||
@@ -53,6 +66,15 @@ export const updateInvite = async (inviteId: string, data: TInviteUpdateInput):
|
|||||||
select: inviteSelect,
|
select: inviteSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (invite === null) {
|
||||||
|
throw new ResourceNotFoundError("Invite", inviteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteCache.revalidate({
|
||||||
|
id: invite.id,
|
||||||
|
teamId: invite.teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return invite;
|
return invite;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2016") {
|
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2016") {
|
||||||
@@ -77,6 +99,11 @@ export const deleteInvite = async (inviteId: string): Promise<TInvite> => {
|
|||||||
throw new ResourceNotFoundError("Invite", inviteId);
|
throw new ResourceNotFoundError("Invite", inviteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inviteCache.revalidate({
|
||||||
|
id: invite.id,
|
||||||
|
teamId: invite.teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return invite;
|
return invite;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
@@ -87,27 +114,32 @@ export const deleteInvite = async (inviteId: string): Promise<TInvite> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInvite = async (inviteId: string): Promise<{ inviteId: string; email: string }> => {
|
export const getInvite = async (inviteId: string): Promise<{ inviteId: string; email: string }> =>
|
||||||
validateInputs([inviteId, ZString]);
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([inviteId, ZString]);
|
||||||
|
|
||||||
const invite = await prisma.invite.findUnique({
|
const invite = await prisma.invite.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: inviteId,
|
id: inviteId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!invite) {
|
||||||
|
throw new ResourceNotFoundError("Invite", inviteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
inviteId,
|
||||||
|
email: invite.email,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
select: {
|
[`getInvite-${inviteId}`],
|
||||||
email: true,
|
{ tags: [inviteCache.tag.byId(inviteId)], revalidate: SERVICES_REVALIDATION_INTERVAL }
|
||||||
},
|
)();
|
||||||
});
|
|
||||||
|
|
||||||
if (!invite) {
|
|
||||||
throw new ResourceNotFoundError("Invite", inviteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
inviteId,
|
|
||||||
email: invite.email,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resendInvite = async (inviteId: string): Promise<TInvite> => {
|
export const resendInvite = async (inviteId: string): Promise<TInvite> => {
|
||||||
validateInputs([inviteId, ZString]);
|
validateInputs([inviteId, ZString]);
|
||||||
@@ -137,6 +169,11 @@ export const resendInvite = async (inviteId: string): Promise<TInvite> => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
inviteCache.revalidate({
|
||||||
|
id: updatedInvite.id,
|
||||||
|
teamId: updatedInvite.teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return updatedInvite;
|
return updatedInvite;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -162,11 +199,8 @@ export const inviteUser = async ({
|
|||||||
const user = await prisma.user.findUnique({ where: { email } });
|
const user = await prisma.user.findUnique({ where: { email } });
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const member = await prisma.membership.findUnique({
|
const member = await getMembershipByUserIdTeamId(user.id, teamId);
|
||||||
where: {
|
|
||||||
userId_teamId: { teamId, userId: user.id },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (member) {
|
if (member) {
|
||||||
throw new ValidationError("User is already a member of this team");
|
throw new ValidationError("User is already a member of this team");
|
||||||
}
|
}
|
||||||
@@ -189,5 +223,10 @@ export const inviteUser = async ({
|
|||||||
|
|
||||||
await sendInviteMemberEmail(invite.id, email, currentUserName, name);
|
await sendInviteMemberEmail(invite.id, email, currentUserName, name);
|
||||||
|
|
||||||
|
inviteCache.revalidate({
|
||||||
|
id: invite.id,
|
||||||
|
teamId: invite.teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return invite;
|
return invite;
|
||||||
};
|
};
|
||||||
|
|||||||
14
packages/lib/invite/util.ts
Normal file
14
packages/lib/invite/util.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import "server-only";
|
||||||
|
|
||||||
|
import { TInvite } from "@formbricks/types/invites";
|
||||||
|
|
||||||
|
export const formatInviteDateFields = (invite: TInvite): TInvite => {
|
||||||
|
if (typeof invite.createdAt === "string") {
|
||||||
|
invite.createdAt = new Date(invite.createdAt);
|
||||||
|
}
|
||||||
|
if (typeof invite.expiresAt === "string") {
|
||||||
|
invite.expiresAt = new Date(invite.expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return invite;
|
||||||
|
};
|
||||||
26
packages/lib/membership/cache.ts
Normal file
26
packages/lib/membership/cache.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const membershipCache = {
|
||||||
|
tag: {
|
||||||
|
byTeamId(teamId: string) {
|
||||||
|
return `teams-${teamId}-memberships`;
|
||||||
|
},
|
||||||
|
byUserId(userId: string) {
|
||||||
|
return `users-${userId}-memberships`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ teamId, userId }: RevalidateProps): void {
|
||||||
|
if (teamId) {
|
||||||
|
revalidateTag(this.tag.byTeamId(teamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
revalidateTag(this.tag.byUserId(userId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -12,76 +12,101 @@ import {
|
|||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
||||||
import { getTeamsByUserIdCacheTag } from "../team/service";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { revalidateTag } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { ITEMS_PER_PAGE } from "../constants";
|
import { membershipCache } from "./cache";
|
||||||
|
import { teamCache } from "../team/cache";
|
||||||
|
|
||||||
export const getMembersByTeamId = async (teamId: string, page?: number): Promise<TMember[]> => {
|
export const getMembersByTeamId = async (teamId: string, page?: number): Promise<TMember[]> =>
|
||||||
validateInputs([teamId, ZString], [page, ZOptionalNumber]);
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([teamId, ZString], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
const membersData = await prisma.membership.findMany({
|
const membersData = await prisma.membership.findMany({
|
||||||
where: { teamId },
|
where: { teamId },
|
||||||
select: {
|
|
||||||
user: {
|
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
user: {
|
||||||
email: true,
|
select: {
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
userId: true,
|
||||||
|
accepted: true,
|
||||||
|
role: true,
|
||||||
},
|
},
|
||||||
},
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
userId: true,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
accepted: true,
|
});
|
||||||
role: true,
|
|
||||||
|
const members = membersData.map((member) => {
|
||||||
|
return {
|
||||||
|
name: member.user?.name || "",
|
||||||
|
email: member.user?.email || "",
|
||||||
|
userId: member.userId,
|
||||||
|
accepted: member.accepted,
|
||||||
|
role: member.role,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return members;
|
||||||
},
|
},
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
[`getMembersByTeamId-${teamId}-${page}`],
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
{
|
||||||
});
|
tags: [membershipCache.tag.byTeamId(teamId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
const members = membersData.map((member) => {
|
}
|
||||||
return {
|
)();
|
||||||
name: member.user?.name || "",
|
|
||||||
email: member.user?.email || "",
|
|
||||||
userId: member.userId,
|
|
||||||
accepted: member.accepted,
|
|
||||||
role: member.role,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return members;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMembershipByUserIdTeamId = async (
|
export const getMembershipByUserIdTeamId = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
teamId: string
|
teamId: string
|
||||||
): Promise<TMembership | null> => {
|
): Promise<TMembership | null> =>
|
||||||
validateInputs([userId, ZString], [teamId, ZString]);
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([userId, ZString], [teamId, ZString]);
|
||||||
|
|
||||||
const membership = await prisma.membership.findUnique({
|
const membership = await prisma.membership.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId_teamId: {
|
userId_teamId: {
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!membership) return null;
|
||||||
|
|
||||||
|
return membership;
|
||||||
},
|
},
|
||||||
});
|
[`getMembershipByUserIdTeamId-${userId}-${teamId}`],
|
||||||
|
{
|
||||||
|
tags: [membershipCache.tag.byUserId(userId), membershipCache.tag.byTeamId(teamId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
if (!membership) return null;
|
export const getMembershipsByUserId = async (userId: string, page?: number): Promise<TMembership[]> =>
|
||||||
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([userId, ZString], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
return membership;
|
const memberships = await prisma.membership.findMany({
|
||||||
};
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
export const getMembershipsByUserId = async (userId: string, page?: number): Promise<TMembership[]> => {
|
return memberships;
|
||||||
validateInputs([userId, ZString], [page, ZOptionalNumber]);
|
|
||||||
|
|
||||||
const memberships = await prisma.membership.findMany({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
},
|
},
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
[`getMembershipsByUserId-${userId}-${page}`],
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
{
|
||||||
});
|
tags: [membershipCache.tag.byUserId(userId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
return memberships;
|
}
|
||||||
};
|
)();
|
||||||
|
|
||||||
export const createMembership = async (
|
export const createMembership = async (
|
||||||
teamId: string,
|
teamId: string,
|
||||||
@@ -89,6 +114,7 @@ export const createMembership = async (
|
|||||||
data: Partial<TMembership>
|
data: Partial<TMembership>
|
||||||
): Promise<TMembership> => {
|
): Promise<TMembership> => {
|
||||||
validateInputs([teamId, ZString], [userId, ZString], [data, ZMembership.partial()]);
|
validateInputs([teamId, ZString], [userId, ZString], [data, ZMembership.partial()]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const membership = await prisma.membership.create({
|
const membership = await prisma.membership.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -98,13 +124,21 @@ export const createMembership = async (
|
|||||||
role: data.role as TMembership["role"],
|
role: data.role as TMembership["role"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(userId));
|
teamCache.revalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
membershipCache.revalidate({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateMembership = async (
|
export const updateMembership = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
teamId: string,
|
teamId: string,
|
||||||
@@ -122,7 +156,15 @@ export const updateMembership = async (
|
|||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(userId));
|
|
||||||
|
teamCache.revalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
membershipCache.revalidate({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -145,7 +187,15 @@ export const deleteMembership = async (userId: string, teamId: string): Promise<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(userId));
|
|
||||||
|
teamCache.revalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
membershipCache.revalidate({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
};
|
};
|
||||||
@@ -182,7 +232,17 @@ export const transferOwnership = async (
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(teamId));
|
|
||||||
|
memberships.forEach((membership) => {
|
||||||
|
teamCache.revalidate({
|
||||||
|
userId: membership.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
membershipCache.revalidate({
|
||||||
|
userId: membership.userId,
|
||||||
|
teamId: membership.teamId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return memberships;
|
return memberships;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { getProduct, getProductCacheTag } from "./service";
|
import { getProduct } from "./service";
|
||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { getTeamsByUserId } from "../team/service";
|
import { getTeamsByUserId } from "../team/service";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { productCache } from "./cache";
|
||||||
|
|
||||||
export const canUserAccessProduct = async (userId: string, productId: string): Promise<boolean> =>
|
export const canUserAccessProduct = async (userId: string, productId: string): Promise<boolean> =>
|
||||||
await unstable_cache(
|
await unstable_cache(
|
||||||
@@ -18,6 +19,9 @@ export const canUserAccessProduct = async (userId: string, productId: string): P
|
|||||||
const teamIds = (await getTeamsByUserId(userId)).map((team) => team.id);
|
const teamIds = (await getTeamsByUserId(userId)).map((team) => team.id);
|
||||||
return teamIds.includes(product.teamId);
|
return teamIds.includes(product.teamId);
|
||||||
},
|
},
|
||||||
[`users-${userId}-products-${productId}`],
|
[`canUserAccessProduct-${userId}-${productId}`],
|
||||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [getProductCacheTag(productId)] }
|
{
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
tags: [productCache.tag.byId(productId), productCache.tag.byUserId(userId)],
|
||||||
|
}
|
||||||
)();
|
)();
|
||||||
|
|||||||
42
packages/lib/product/cache.ts
Normal file
42
packages/lib/product/cache.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
userId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const productCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `product-${id}`;
|
||||||
|
},
|
||||||
|
byUserId(userId: string) {
|
||||||
|
return `users-${userId}-products`;
|
||||||
|
},
|
||||||
|
byTeamId(teamId: string) {
|
||||||
|
return `teams-${teamId}-products`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-products`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, userId, teamId, environmentId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
revalidateTag(this.tag.byTeamId(teamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
revalidateTag(this.tag.byUserId(userId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -6,17 +6,15 @@ import { DatabaseError, ValidationError } from "@formbricks/types/errors";
|
|||||||
import type { TProduct, TProductUpdateInput } from "@formbricks/types/product";
|
import type { TProduct, TProductUpdateInput } from "@formbricks/types/product";
|
||||||
import { ZProduct, ZProductUpdateInput } from "@formbricks/types/product";
|
import { ZProduct, ZProductUpdateInput } from "@formbricks/types/product";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL, ITEMS_PER_PAGE, IS_S3_CONFIGURED } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL, ITEMS_PER_PAGE, IS_S3_CONFIGURED } from "../constants";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { createEnvironment, getEnvironmentCacheTag, getEnvironmentsCacheTag } from "../environment/service";
|
import { createEnvironment } from "../environment/service";
|
||||||
import { ZOptionalNumber } from "@formbricks/types/common";
|
import { environmentCache } from "../environment/cache";
|
||||||
|
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
|
||||||
import { deleteLocalFilesByEnvironmentId, deleteS3FilesByEnvironmentId } from "../storage/service";
|
import { deleteLocalFilesByEnvironmentId, deleteS3FilesByEnvironmentId } from "../storage/service";
|
||||||
|
import { productCache } from "./cache";
|
||||||
export const getProductsCacheTag = (teamId: string): string => `teams-${teamId}-products`;
|
|
||||||
export const getProductCacheTag = (environmentId: string): string => `environments-${environmentId}-product`;
|
|
||||||
const getProductCacheKey = (environmentId: string): string[] => [getProductCacheTag(environmentId)];
|
|
||||||
|
|
||||||
const selectProduct = {
|
const selectProduct = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -58,49 +56,44 @@ export const getProducts = async (teamId: string, page?: number): Promise<TProdu
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`teams-${teamId}-products`],
|
[`getProducts-${teamId}-${page}`],
|
||||||
{
|
{
|
||||||
tags: [getProductsCacheTag(teamId)],
|
tags: [productCache.tag.byTeamId(teamId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
export const getProductByEnvironmentId = async (environmentId: string): Promise<TProduct | null> => {
|
export const getProductByEnvironmentId = async (environmentId: string): Promise<TProduct | null> =>
|
||||||
if (!environmentId) {
|
|
||||||
throw new ValidationError("EnvironmentId is required");
|
|
||||||
}
|
|
||||||
let productPrisma;
|
|
||||||
|
|
||||||
try {
|
|
||||||
productPrisma = await prisma.product.findFirst({
|
|
||||||
where: {
|
|
||||||
environments: {
|
|
||||||
some: {
|
|
||||||
id: environmentId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: selectProduct,
|
|
||||||
});
|
|
||||||
|
|
||||||
return productPrisma;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
||||||
console.error(error);
|
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getProductByEnvironmentIdCached = (environmentId: string): Promise<TProduct | null> =>
|
|
||||||
unstable_cache(
|
unstable_cache(
|
||||||
async () => {
|
async () => {
|
||||||
return await getProductByEnvironmentId(environmentId);
|
validateInputs([environmentId, ZId]);
|
||||||
|
|
||||||
|
let productPrisma;
|
||||||
|
|
||||||
|
try {
|
||||||
|
productPrisma = await prisma.product.findFirst({
|
||||||
|
where: {
|
||||||
|
environments: {
|
||||||
|
some: {
|
||||||
|
id: environmentId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: selectProduct,
|
||||||
|
});
|
||||||
|
|
||||||
|
return productPrisma;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
console.error(error);
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getProductCacheKey(environmentId),
|
[`getProductByEnvironmentId-${environmentId}`],
|
||||||
{
|
{
|
||||||
tags: getProductCacheKey(environmentId),
|
tags: [productCache.tag.byEnvironmentId(environmentId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -110,6 +103,7 @@ export const updateProduct = async (
|
|||||||
inputProduct: Partial<TProductUpdateInput>
|
inputProduct: Partial<TProductUpdateInput>
|
||||||
): Promise<TProduct> => {
|
): Promise<TProduct> => {
|
||||||
validateInputs([productId, ZId], [inputProduct, ZProductUpdateInput.partial()]);
|
validateInputs([productId, ZId], [inputProduct, ZProductUpdateInput.partial()]);
|
||||||
|
|
||||||
const { environments, ...data } = inputProduct;
|
const { environments, ...data } = inputProduct;
|
||||||
let updatedProduct;
|
let updatedProduct;
|
||||||
try {
|
try {
|
||||||
@@ -134,10 +128,16 @@ export const updateProduct = async (
|
|||||||
try {
|
try {
|
||||||
const product = ZProduct.parse(updatedProduct);
|
const product = ZProduct.parse(updatedProduct);
|
||||||
|
|
||||||
revalidateTag(getProductsCacheTag(product.teamId));
|
productCache.revalidate({
|
||||||
|
id: product.id,
|
||||||
|
teamId: product.teamId,
|
||||||
|
});
|
||||||
|
|
||||||
product.environments.forEach((environment) => {
|
product.environments.forEach((environment) => {
|
||||||
// revalidate environment cache
|
// revalidate environment cache
|
||||||
revalidateTag(getProductCacheTag(environment.id));
|
productCache.revalidate({
|
||||||
|
environmentId: environment.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return product;
|
return product;
|
||||||
@@ -149,24 +149,32 @@ export const updateProduct = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProduct = async (productId: string): Promise<TProduct | null> => {
|
export const getProduct = async (productId: string): Promise<TProduct | null> =>
|
||||||
let productPrisma;
|
unstable_cache(
|
||||||
try {
|
async () => {
|
||||||
productPrisma = await prisma.product.findUnique({
|
let productPrisma;
|
||||||
where: {
|
try {
|
||||||
id: productId,
|
productPrisma = await prisma.product.findUnique({
|
||||||
},
|
where: {
|
||||||
select: selectProduct,
|
id: productId,
|
||||||
});
|
},
|
||||||
|
select: selectProduct,
|
||||||
|
});
|
||||||
|
|
||||||
return productPrisma;
|
return productPrisma;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseError(error.message);
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getProduct-${productId}`],
|
||||||
|
{
|
||||||
|
tags: [productCache.tag.byId(productId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
throw error;
|
)();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteProduct = async (productId: string): Promise<TProduct> => {
|
export const deleteProduct = async (productId: string): Promise<TProduct> => {
|
||||||
const product = await prisma.product.delete({
|
const product = await prisma.product.delete({
|
||||||
@@ -203,12 +211,23 @@ export const deleteProduct = async (productId: string): Promise<TProduct> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidateTag(getProductsCacheTag(product.teamId));
|
productCache.revalidate({
|
||||||
revalidateTag(getEnvironmentsCacheTag(product.id));
|
id: product.id,
|
||||||
|
teamId: product.teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
environmentCache.revalidate({
|
||||||
|
productId: product.id,
|
||||||
|
});
|
||||||
|
|
||||||
product.environments.forEach((environment) => {
|
product.environments.forEach((environment) => {
|
||||||
// revalidate product cache
|
// revalidate product cache
|
||||||
revalidateTag(getProductCacheTag(environment.id));
|
productCache.revalidate({
|
||||||
revalidateTag(getEnvironmentCacheTag(environment.id));
|
environmentId: environment.id,
|
||||||
|
});
|
||||||
|
environmentCache.revalidate({
|
||||||
|
id: environment.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +238,8 @@ export const createProduct = async (
|
|||||||
teamId: string,
|
teamId: string,
|
||||||
productInput: Partial<TProductUpdateInput>
|
productInput: Partial<TProductUpdateInput>
|
||||||
): Promise<TProduct> => {
|
): Promise<TProduct> => {
|
||||||
|
validateInputs([teamId, ZString], [productInput, ZProductUpdateInput.partial()]);
|
||||||
|
|
||||||
if (!productInput.name) {
|
if (!productInput.name) {
|
||||||
throw new ValidationError("Product Name is required");
|
throw new ValidationError("Product Name is required");
|
||||||
}
|
}
|
||||||
|
|||||||
26
packages/lib/profile/cache.ts
Normal file
26
packages/lib/profile/cache.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const profileCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `profiles-${id}`;
|
||||||
|
},
|
||||||
|
byEmail(email: string) {
|
||||||
|
return `profiles-${email}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, email }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
revalidateTag(this.tag.byEmail(email));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import "server-only";
|
|||||||
import { prisma } from "@formbricks/database";
|
import { prisma } from "@formbricks/database";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||||
import { TMembership, TMembershipRole, ZMembershipRole } from "@formbricks/types/memberships";
|
import { TMembership } from "@formbricks/types/memberships";
|
||||||
import {
|
import {
|
||||||
TProfile,
|
TProfile,
|
||||||
TProfileCreateInput,
|
TProfileCreateInput,
|
||||||
@@ -11,11 +11,13 @@ import {
|
|||||||
ZProfileUpdateInput,
|
ZProfileUpdateInput,
|
||||||
} from "@formbricks/types/profile";
|
} from "@formbricks/types/profile";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { deleteTeam } from "../team/service";
|
import { deleteTeam } from "../team/service";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
|
import { profileCache } from "./cache";
|
||||||
|
import { updateMembership } from "../membership/service";
|
||||||
|
|
||||||
const responseSelection = {
|
const responseSelection = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -29,18 +31,16 @@ const responseSelection = {
|
|||||||
objective: true,
|
objective: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProfileCacheTag = (userId: string): string => `profiles-${userId}`;
|
|
||||||
export const getProfileByEmailCacheTag = (email: string): string => `profiles-${email}`;
|
|
||||||
|
|
||||||
// function to retrive basic information about a user's profile
|
// function to retrive basic information about a user's profile
|
||||||
export const getProfile = async (userId: string): Promise<TProfile | null> =>
|
export const getProfile = async (id: string): Promise<TProfile | null> =>
|
||||||
unstable_cache(
|
unstable_cache(
|
||||||
async () => {
|
async () => {
|
||||||
validateInputs([userId, ZId]);
|
validateInputs([id, ZId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const profile = await prisma.user.findUnique({
|
const profile = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id,
|
||||||
},
|
},
|
||||||
select: responseSelection,
|
select: responseSelection,
|
||||||
});
|
});
|
||||||
@@ -58,9 +58,9 @@ export const getProfile = async (userId: string): Promise<TProfile | null> =>
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`profiles-${userId}`],
|
[`getProfile-${id}`],
|
||||||
{
|
{
|
||||||
tags: [getProfileByEmailCacheTag(userId)],
|
tags: [profileCache.tag.byId(id)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -69,6 +69,7 @@ export const getProfileByEmail = async (email: string): Promise<TProfile | null>
|
|||||||
unstable_cache(
|
unstable_cache(
|
||||||
async () => {
|
async () => {
|
||||||
validateInputs([email, z.string().email()]);
|
validateInputs([email, z.string().email()]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const profile = await prisma.user.findFirst({
|
const profile = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -90,28 +91,13 @@ export const getProfileByEmail = async (email: string): Promise<TProfile | null>
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`profiles-${email}`],
|
[`getProfileByEmail-${email}`],
|
||||||
{
|
{
|
||||||
tags: [getProfileCacheTag(email)],
|
tags: [profileCache.tag.byEmail(email)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
const updateUserMembership = async (teamId: string, userId: string, role: TMembershipRole) => {
|
|
||||||
validateInputs([teamId, ZId], [userId, ZId], [role, ZMembershipRole]);
|
|
||||||
await prisma.membership.update({
|
|
||||||
where: {
|
|
||||||
userId_teamId: {
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
role,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAdminMemberships = (memberships: TMembership[]): TMembership[] =>
|
const getAdminMemberships = (memberships: TMembership[]): TMembership[] =>
|
||||||
memberships.filter((membership) => membership.role === "admin");
|
memberships.filter((membership) => membership.role === "admin");
|
||||||
|
|
||||||
@@ -121,6 +107,7 @@ export const updateProfile = async (
|
|||||||
data: Partial<TProfileUpdateInput>
|
data: Partial<TProfileUpdateInput>
|
||||||
): Promise<TProfile> => {
|
): Promise<TProfile> => {
|
||||||
validateInputs([personId, ZId], [data, ZProfileUpdateInput.partial()]);
|
validateInputs([personId, ZId], [data, ZProfileUpdateInput.partial()]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedProfile = await prisma.user.update({
|
const updatedProfile = await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -130,8 +117,10 @@ export const updateProfile = async (
|
|||||||
select: responseSelection,
|
select: responseSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getProfileByEmailCacheTag(updatedProfile.email));
|
profileCache.revalidate({
|
||||||
revalidateTag(getProfileCacheTag(personId));
|
email: updatedProfile.email,
|
||||||
|
id: updatedProfile.id,
|
||||||
|
});
|
||||||
|
|
||||||
return updatedProfile;
|
return updatedProfile;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -143,40 +132,48 @@ export const updateProfile = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUser = async (userId: string): Promise<TProfile> => {
|
const deleteUser = async (id: string): Promise<TProfile> => {
|
||||||
validateInputs([userId, ZId]);
|
validateInputs([id, ZId]);
|
||||||
|
|
||||||
const profile = await prisma.user.delete({
|
const profile = await prisma.user.delete({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id,
|
||||||
},
|
},
|
||||||
select: responseSelection,
|
select: responseSelection,
|
||||||
});
|
});
|
||||||
revalidateTag(getProfileByEmailCacheTag(profile.email));
|
|
||||||
revalidateTag(getProfileCacheTag(userId));
|
profileCache.revalidate({
|
||||||
|
email: profile.email,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createProfile = async (data: TProfileCreateInput): Promise<TProfile> => {
|
export const createProfile = async (data: TProfileCreateInput): Promise<TProfile> => {
|
||||||
validateInputs([data, ZProfileUpdateInput]);
|
validateInputs([data, ZProfileUpdateInput]);
|
||||||
|
|
||||||
const profile = await prisma.user.create({
|
const profile = await prisma.user.create({
|
||||||
data: data,
|
data: data,
|
||||||
select: responseSelection,
|
select: responseSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getProfileByEmailCacheTag(profile.email));
|
profileCache.revalidate({
|
||||||
revalidateTag(getProfileCacheTag(profile.id));
|
email: profile.email,
|
||||||
|
id: profile.id,
|
||||||
|
});
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
// function to delete a user's profile including teams
|
// function to delete a user's profile including teams
|
||||||
export const deleteProfile = async (userId: string): Promise<TProfile> => {
|
export const deleteProfile = async (id: string): Promise<TProfile> => {
|
||||||
validateInputs([userId, ZId]);
|
validateInputs([id, ZId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentUserMemberships = await prisma.membership.findMany({
|
const currentUserMemberships = await prisma.membership.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: userId,
|
userId: id,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
team: {
|
team: {
|
||||||
@@ -203,15 +200,13 @@ export const deleteProfile = async (userId: string): Promise<TProfile> => {
|
|||||||
await deleteTeam(teamId);
|
await deleteTeam(teamId);
|
||||||
} else if (currentUserIsTeamOwner && teamHasAtLeastOneAdmin) {
|
} else if (currentUserIsTeamOwner && teamHasAtLeastOneAdmin) {
|
||||||
const firstAdmin = teamAdminMemberships[0];
|
const firstAdmin = teamAdminMemberships[0];
|
||||||
await updateUserMembership(teamId, firstAdmin.userId, "owner");
|
await updateMembership(firstAdmin.userId, teamId, { role: "owner" });
|
||||||
} else if (currentUserIsTeamOwner) {
|
} else if (currentUserIsTeamOwner) {
|
||||||
await deleteTeam(teamId);
|
await deleteTeam(teamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidateTag(getProfileCacheTag(userId));
|
const deletedProfile = await deleteUser(id);
|
||||||
|
|
||||||
const deletedProfile = await deleteUser(userId);
|
|
||||||
|
|
||||||
return deletedProfile;
|
return deletedProfile;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { revalidateTag } from "next/cache";
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
interface RevalidateProps {
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
environmentId?: string;
|
environmentId?: string;
|
||||||
personId?: string;
|
personId?: string;
|
||||||
id?: string;
|
|
||||||
singleUseId?: string;
|
singleUseId?: string;
|
||||||
surveyId?: string;
|
surveyId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const responseCache = {
|
export const responseCache = {
|
||||||
tag: {
|
tag: {
|
||||||
byEnvironmentId(environmentId: string) {
|
|
||||||
return `environments-${environmentId}-responses`;
|
|
||||||
},
|
|
||||||
byId(responseId: string) {
|
byId(responseId: string) {
|
||||||
return `responses-${responseId}`;
|
return `responses-${responseId}`;
|
||||||
},
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-responses`;
|
||||||
|
},
|
||||||
byPersonId(personId: string) {
|
byPersonId(personId: string) {
|
||||||
return `people-${personId}-responses`;
|
return `people-${personId}-responses`;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import "server-only";
|
import "server-only";
|
||||||
|
|
||||||
import { prisma } from "@formbricks/database";
|
import { prisma } from "@formbricks/database";
|
||||||
|
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
|
||||||
|
import { ZId } from "@formbricks/types/environment";
|
||||||
|
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||||
|
import { TPerson } from "@formbricks/types/people";
|
||||||
import {
|
import {
|
||||||
TResponse,
|
TResponse,
|
||||||
TResponseInput,
|
TResponseInput,
|
||||||
@@ -8,20 +12,18 @@ import {
|
|||||||
ZResponseInput,
|
ZResponseInput,
|
||||||
ZResponseUpdateInput,
|
ZResponseUpdateInput,
|
||||||
} from "@formbricks/types/responses";
|
} from "@formbricks/types/responses";
|
||||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
|
||||||
import { TPerson } from "@formbricks/types/people";
|
|
||||||
import { TTag } from "@formbricks/types/tags";
|
import { TTag } from "@formbricks/types/tags";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { deleteDisplayByResponseId } from "../display/service";
|
||||||
import { getPerson, transformPrismaPerson } from "../person/service";
|
import { getPerson, transformPrismaPerson } from "../person/service";
|
||||||
|
import { formatResponseDateFields } from "../response/util";
|
||||||
|
import { responseNoteCache } from "../responseNote/cache";
|
||||||
|
import { getResponseNotes } from "../responseNote/service";
|
||||||
import { captureTelemetry } from "../telemetry";
|
import { captureTelemetry } from "../telemetry";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
|
||||||
import { unstable_cache } from "next/cache";
|
|
||||||
import { deleteDisplayByResponseId } from "../display/service";
|
|
||||||
import { ZString, ZOptionalNumber } from "@formbricks/types/common";
|
|
||||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
|
||||||
import { responseCache } from "./cache";
|
import { responseCache } from "./cache";
|
||||||
import { formatResponseDateFields } from "../response/util";
|
|
||||||
|
|
||||||
const responseSelection = {
|
const responseSelection = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -51,6 +53,19 @@ const responseSelection = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
tags: {
|
||||||
|
select: {
|
||||||
|
tag: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
name: true,
|
||||||
|
environmentId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
notes: {
|
notes: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -67,19 +82,6 @@ const responseSelection = {
|
|||||||
isEdited: true,
|
isEdited: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tags: {
|
|
||||||
select: {
|
|
||||||
tag: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
name: true,
|
|
||||||
environmentId: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getResponsesByPersonId = async (
|
export const getResponsesByPersonId = async (
|
||||||
@@ -229,11 +231,15 @@ export const createResponse = async (responseInput: TResponseInput): Promise<TRe
|
|||||||
};
|
};
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
personId: response.person?.id,
|
|
||||||
id: response.id,
|
id: response.id,
|
||||||
|
personId: response.person?.id,
|
||||||
surveyId: response.surveyId,
|
surveyId: response.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responseNoteCache.revalidate({
|
||||||
|
responseId: response.id,
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
@@ -277,7 +283,10 @@ export const getResponse = async (responseId: string): Promise<TResponse | null>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`getResponse-${responseId}`],
|
[`getResponse-${responseId}`],
|
||||||
{ tags: [responseCache.tag.byId(responseId)], revalidate: SERVICES_REVALIDATION_INTERVAL }
|
{
|
||||||
|
tags: [responseCache.tag.byId(responseId), responseNoteCache.tag.byResponseId(responseId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
@@ -310,11 +319,15 @@ export const getResponses = async (surveyId: string, page?: number): Promise<TRe
|
|||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const transformedResponses: TResponse[] = responses.map((responsePrisma) => ({
|
const transformedResponses: TResponse[] = await Promise.all(
|
||||||
...responsePrisma,
|
responses.map(async (responsePrisma) => {
|
||||||
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
return {
|
||||||
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
...responsePrisma,
|
||||||
}));
|
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
||||||
|
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return transformedResponses;
|
return transformedResponses;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -363,11 +376,15 @@ export const getResponsesByEnvironmentId = async (
|
|||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const transformedResponses: TResponse[] = responses.map((responsePrisma) => ({
|
const transformedResponses: TResponse[] = await Promise.all(
|
||||||
...responsePrisma,
|
responses.map(async (responsePrisma) => {
|
||||||
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
return {
|
||||||
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
...responsePrisma,
|
||||||
}));
|
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
||||||
|
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return transformedResponses;
|
return transformedResponses;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -435,11 +452,15 @@ export const updateResponse = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
personId: response.person?.id,
|
|
||||||
id: response.id,
|
id: response.id,
|
||||||
|
personId: response.person?.id,
|
||||||
surveyId: response.surveyId,
|
surveyId: response.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responseNoteCache.revalidate({
|
||||||
|
responseId: response.id,
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
@@ -460,19 +481,26 @@ export const deleteResponse = async (responseId: string): Promise<TResponse> =>
|
|||||||
select: responseSelection,
|
select: responseSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const responseNotes = await getResponseNotes(responsePrisma.id);
|
||||||
const response: TResponse = {
|
const response: TResponse = {
|
||||||
...responsePrisma,
|
...responsePrisma,
|
||||||
|
notes: responseNotes,
|
||||||
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
||||||
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteDisplayByResponseId(responseId, response.surveyId);
|
deleteDisplayByResponseId(responseId, response.surveyId);
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
personId: response.person?.id,
|
|
||||||
id: response.id,
|
id: response.id,
|
||||||
|
personId: response.person?.id,
|
||||||
surveyId: response.surveyId,
|
surveyId: response.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responseNoteCache.revalidate({
|
||||||
|
responseId: response.id,
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
|||||||
26
packages/lib/responseNote/cache.ts
Normal file
26
packages/lib/responseNote/cache.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
responseId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const responseNoteCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `responseNotes-${id}`;
|
||||||
|
},
|
||||||
|
byResponseId(responseId: string) {
|
||||||
|
return `responses-${responseId}-responseNote`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, responseId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseId) {
|
||||||
|
revalidateTag(this.tag.byResponseId(responseId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -6,6 +6,12 @@ import { DatabaseError } from "@formbricks/types/errors";
|
|||||||
import { TResponseNote } from "@formbricks/types/responses";
|
import { TResponseNote } from "@formbricks/types/responses";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { responseCache } from "../response/cache";
|
import { responseCache } from "../response/cache";
|
||||||
|
import { validateInputs } from "../utils/validate";
|
||||||
|
import { ZId } from "@formbricks/types/environment";
|
||||||
|
import { ZString } from "@formbricks/types/common";
|
||||||
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
import { responseNoteCache } from "./cache";
|
||||||
|
|
||||||
const select = {
|
const select = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -33,6 +39,8 @@ export const createResponseNote = async (
|
|||||||
userId: string,
|
userId: string,
|
||||||
text: string
|
text: string
|
||||||
): Promise<TResponseNote> => {
|
): Promise<TResponseNote> => {
|
||||||
|
validateInputs([responseId, ZId], [userId, ZId], [text, ZString]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseNote = await prisma.responseNote.create({
|
const responseNote = await prisma.responseNote.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -44,30 +52,18 @@ export const createResponseNote = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
id: responseId,
|
id: responseNote.response.id,
|
||||||
surveyId: responseNote.response.surveyId,
|
surveyId: responseNote.response.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return responseNote;
|
responseNoteCache.revalidate({
|
||||||
} catch (error) {
|
id: responseNote.id,
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
responseId: responseNote.response.id,
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getResponseNote = async (responseNoteId: string): Promise<TResponseNote | null> => {
|
|
||||||
try {
|
|
||||||
const responseNote = await prisma.responseNote.findUnique({
|
|
||||||
where: {
|
|
||||||
id: responseNoteId,
|
|
||||||
},
|
|
||||||
select,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return responseNote;
|
return responseNote;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseError(error.message);
|
throw new DatabaseError(error.message);
|
||||||
}
|
}
|
||||||
@@ -76,7 +72,59 @@ export const getResponseNote = async (responseNoteId: string): Promise<TResponse
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getResponseNote = async (responseNoteId: string): Promise<TResponseNote | null> =>
|
||||||
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const responseNote = await prisma.responseNote.findUnique({
|
||||||
|
where: {
|
||||||
|
id: responseNoteId,
|
||||||
|
},
|
||||||
|
select,
|
||||||
|
});
|
||||||
|
return responseNote;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getResponseNote-${responseNoteId}`],
|
||||||
|
{ tags: [responseNoteCache.tag.byId(responseNoteId)], revalidate: SERVICES_REVALIDATION_INTERVAL }
|
||||||
|
)();
|
||||||
|
|
||||||
|
export const getResponseNotes = async (responseId: string): Promise<TResponseNote[]> =>
|
||||||
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
validateInputs([responseId, ZId]);
|
||||||
|
|
||||||
|
const responseNotes = await prisma.responseNote.findMany({
|
||||||
|
where: {
|
||||||
|
responseId,
|
||||||
|
},
|
||||||
|
select,
|
||||||
|
});
|
||||||
|
return responseNotes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getResponseNotes-${responseId}`],
|
||||||
|
{ tags: [responseNoteCache.tag.byResponseId(responseId)], revalidate: SERVICES_REVALIDATION_INTERVAL }
|
||||||
|
)();
|
||||||
|
|
||||||
export const updateResponseNote = async (responseNoteId: string, text: string): Promise<TResponseNote> => {
|
export const updateResponseNote = async (responseNoteId: string, text: string): Promise<TResponseNote> => {
|
||||||
|
validateInputs([responseNoteId, ZString], [text, ZString]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedResponseNote = await prisma.responseNote.update({
|
const updatedResponseNote = await prisma.responseNote.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -95,8 +143,14 @@ export const updateResponseNote = async (responseNoteId: string, text: string):
|
|||||||
surveyId: updatedResponseNote.response.surveyId,
|
surveyId: updatedResponseNote.response.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responseNoteCache.revalidate({
|
||||||
|
id: updatedResponseNote.id,
|
||||||
|
responseId: updatedResponseNote.response.id,
|
||||||
|
});
|
||||||
|
|
||||||
return updatedResponseNote;
|
return updatedResponseNote;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseError(error.message);
|
throw new DatabaseError(error.message);
|
||||||
}
|
}
|
||||||
@@ -106,6 +160,8 @@ export const updateResponseNote = async (responseNoteId: string, text: string):
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const resolveResponseNote = async (responseNoteId: string): Promise<TResponseNote> => {
|
export const resolveResponseNote = async (responseNoteId: string): Promise<TResponseNote> => {
|
||||||
|
validateInputs([responseNoteId, ZString]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseNote = await prisma.responseNote.update({
|
const responseNote = await prisma.responseNote.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -123,8 +179,14 @@ export const resolveResponseNote = async (responseNoteId: string): Promise<TResp
|
|||||||
surveyId: responseNote.response.surveyId,
|
surveyId: responseNote.response.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responseNoteCache.revalidate({
|
||||||
|
id: responseNote.id,
|
||||||
|
responseId: responseNote.response.id,
|
||||||
|
});
|
||||||
|
|
||||||
return responseNote;
|
return responseNote;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
throw new DatabaseError(error.message);
|
throw new DatabaseError(error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
26
packages/lib/session/cache.ts
Normal file
26
packages/lib/session/cache.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
personId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sessionCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `sessions-${id}`;
|
||||||
|
},
|
||||||
|
byPersonId(personId: string) {
|
||||||
|
return `people-${personId}-sessions`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, personId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (personId) {
|
||||||
|
revalidateTag(this.tag.byPersonId(personId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -4,14 +4,13 @@ import "server-only";
|
|||||||
import { prisma } from "@formbricks/database";
|
import { prisma } from "@formbricks/database";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { DatabaseError } from "@formbricks/types/errors";
|
import { DatabaseError } from "@formbricks/types/errors";
|
||||||
import { TSession, TSessionWithActions } from "@formbricks/types/sessions";
|
import { TSession } from "@formbricks/types/sessions";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ZOptionalNumber } from "@formbricks/types/common";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { sessionCache } from "./cache";
|
||||||
|
import { formatSessionDateFields } from "./util";
|
||||||
const getSessionCacheKey = (sessionId: string): string[] => [sessionId];
|
|
||||||
|
|
||||||
const select = {
|
const select = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -24,93 +23,64 @@ const select = {
|
|||||||
const oneHour = 1000 * 60 * 60;
|
const oneHour = 1000 * 60 * 60;
|
||||||
|
|
||||||
export const getSession = async (sessionId: string): Promise<TSession | null> => {
|
export const getSession = async (sessionId: string): Promise<TSession | null> => {
|
||||||
validateInputs([sessionId, ZId]);
|
const session = await unstable_cache(
|
||||||
try {
|
|
||||||
const session = await prisma.session.findUnique({
|
|
||||||
where: {
|
|
||||||
id: sessionId,
|
|
||||||
},
|
|
||||||
select,
|
|
||||||
});
|
|
||||||
|
|
||||||
return session;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSessionCached = (sessionId: string) =>
|
|
||||||
unstable_cache(
|
|
||||||
async () => {
|
async () => {
|
||||||
return await getSession(sessionId);
|
validateInputs([sessionId, ZId]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session = await prisma.session.findUnique({
|
||||||
|
where: {
|
||||||
|
id: sessionId,
|
||||||
|
},
|
||||||
|
select,
|
||||||
|
});
|
||||||
|
|
||||||
|
return session;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getSessionCacheKey(sessionId),
|
[`getSession-${sessionId}`],
|
||||||
{
|
{
|
||||||
tags: getSessionCacheKey(sessionId),
|
tags: [sessionCache.tag.byId(sessionId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
export const getSessionWithActionsOfPerson = async (
|
if (!session) return null;
|
||||||
personId: string,
|
|
||||||
page?: number
|
return formatSessionDateFields(session);
|
||||||
): Promise<TSessionWithActions[] | null> => {
|
};
|
||||||
validateInputs([personId, ZId], [page, ZOptionalNumber]);
|
|
||||||
try {
|
export const getSessionCount = async (personId: string): Promise<number> =>
|
||||||
const sessionsWithActionsForPerson = await prisma.session.findMany({
|
unstable_cache(
|
||||||
where: {
|
async () => {
|
||||||
personId,
|
validateInputs([personId, ZId]);
|
||||||
},
|
|
||||||
select: {
|
try {
|
||||||
id: true,
|
const sessionCount = await prisma.session.count({
|
||||||
events: {
|
where: {
|
||||||
select: {
|
personId,
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
eventClass: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
description: true,
|
|
||||||
type: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
},
|
return sessionCount;
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
} catch (error) {
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
});
|
throw new DatabaseError(error.message);
|
||||||
if (!sessionsWithActionsForPerson) return null;
|
}
|
||||||
|
throw error;
|
||||||
return sessionsWithActionsForPerson;
|
}
|
||||||
} catch (error) {
|
},
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
[`getSessionCount-${personId}`],
|
||||||
throw new DatabaseError(error.message);
|
{
|
||||||
|
tags: [sessionCache.tag.byPersonId(personId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
throw error;
|
)();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSessionCount = async (personId: string): Promise<number> => {
|
|
||||||
validateInputs([personId, ZId]);
|
|
||||||
try {
|
|
||||||
const sessionCount = await prisma.session.count({
|
|
||||||
where: {
|
|
||||||
personId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return sessionCount;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createSession = async (personId: string): Promise<TSession> => {
|
export const createSession = async (personId: string): Promise<TSession> => {
|
||||||
validateInputs([personId, ZId]);
|
validateInputs([personId, ZId]);
|
||||||
@@ -128,8 +98,10 @@ export const createSession = async (personId: string): Promise<TSession> => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
// revalidate session cache
|
sessionCache.revalidate({
|
||||||
revalidateTag(session.id);
|
id: session.id,
|
||||||
|
personId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
@@ -144,6 +116,7 @@ export const createSession = async (personId: string): Promise<TSession> => {
|
|||||||
|
|
||||||
export const extendSession = async (sessionId: string): Promise<TSession> => {
|
export const extendSession = async (sessionId: string): Promise<TSession> => {
|
||||||
validateInputs([sessionId, ZId]);
|
validateInputs([sessionId, ZId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const session = await prisma.session.update({
|
const session = await prisma.session.update({
|
||||||
where: {
|
where: {
|
||||||
@@ -156,7 +129,10 @@ export const extendSession = async (sessionId: string): Promise<TSession> => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// revalidate session cache
|
// revalidate session cache
|
||||||
revalidateTag(sessionId);
|
sessionCache.revalidate({
|
||||||
|
id: sessionId,
|
||||||
|
personId: session.personId,
|
||||||
|
});
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
17
packages/lib/session/util.ts
Normal file
17
packages/lib/session/util.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import "server-only";
|
||||||
|
|
||||||
|
import { TSession } from "@formbricks/types/sessions";
|
||||||
|
|
||||||
|
export const formatSessionDateFields = (session: TSession): TSession => {
|
||||||
|
if (typeof session.createdAt === "string") {
|
||||||
|
session.createdAt = new Date(session.createdAt);
|
||||||
|
}
|
||||||
|
if (typeof session.updatedAt === "string") {
|
||||||
|
session.updatedAt = new Date(session.updatedAt);
|
||||||
|
}
|
||||||
|
if (typeof session.expiresAt === "string") {
|
||||||
|
session.expiresAt = new Date(session.expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||||
import { getSurvey, getSurveyCacheTag } from "./service";
|
import { getSurvey } from "./service";
|
||||||
|
import { surveyCache } from "./cache";
|
||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
|
||||||
@@ -20,6 +21,6 @@ export const canUserAccessSurvey = async (userId: string, surveyId: string): Pro
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[`users-${userId}-surveys-${surveyId}`],
|
[`canUserAccessSurvey-${userId}-${surveyId}`],
|
||||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [getSurveyCacheTag(surveyId)] }
|
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [surveyCache.tag.byId(surveyId)] }
|
||||||
)();
|
)();
|
||||||
|
|||||||
42
packages/lib/survey/cache.ts
Normal file
42
packages/lib/survey/cache.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
attributeClassId?: string;
|
||||||
|
actionClassId?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const surveyCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `surveys-${id}`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string): string {
|
||||||
|
return `environments-${environmentId}-surveys`;
|
||||||
|
},
|
||||||
|
byAttributeClassId(attributeClassId: string) {
|
||||||
|
return `attributeFilters-${attributeClassId}-surveys`;
|
||||||
|
},
|
||||||
|
byActionClassId(actionClassId: string) {
|
||||||
|
return `actionClasses-${actionClassId}-surveys`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, attributeClassId, actionClassId, environmentId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributeClassId) {
|
||||||
|
revalidateTag(this.tag.byAttributeClassId(attributeClassId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionClassId) {
|
||||||
|
revalidateTag(this.tag.byActionClassId(actionClassId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -5,20 +5,16 @@ import { ZOptionalNumber } from "@formbricks/types/common";
|
|||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||||
import { TSurvey, TSurveyAttributeFilter, TSurveyInput, ZSurvey } from "@formbricks/types/surveys";
|
import { TSurvey, TSurveyAttributeFilter, TSurveyInput, ZSurvey } from "@formbricks/types/surveys";
|
||||||
|
import { TActionClass } from "@formbricks/types/actionClasses";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { getActionClasses } from "../actionClass/service";
|
import { getActionClasses } from "../actionClass/service";
|
||||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
import { responseCache } from "../response/cache";
|
import { responseCache } from "../response/cache";
|
||||||
import { captureTelemetry } from "../telemetry";
|
import { captureTelemetry } from "../telemetry";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { formatSurveyDateFields } from "./util";
|
import { formatSurveyDateFields } from "./util";
|
||||||
|
import { surveyCache } from "./cache";
|
||||||
// surveys cache key and tags
|
|
||||||
const getSurveysCacheTag = (environmentId: string): string => `environments-${environmentId}-surveys`;
|
|
||||||
|
|
||||||
// survey cache key and tags
|
|
||||||
export const getSurveyCacheTag = (surveyId: string): string => `surveys-${surveyId}`;
|
|
||||||
|
|
||||||
export const selectSurvey = {
|
export const selectSurvey = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -70,10 +66,32 @@ export const selectSurvey = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getActionClassIdFromName = (actionClasses: TActionClass[], actionClassName: string): string => {
|
||||||
|
return actionClasses.find((actionClass) => actionClass.name === actionClassName)!.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const revalidateSurveyByActionClassId = (actionClasses: TActionClass[], actionClassNames: string[]): void => {
|
||||||
|
for (const actionClassName of actionClassNames) {
|
||||||
|
const actionClassId: string = getActionClassIdFromName(actionClasses, actionClassName);
|
||||||
|
surveyCache.revalidate({
|
||||||
|
actionClassId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const revalidateSurveyByAttributeClassId = (attributeFilters: TSurveyAttributeFilter[]): void => {
|
||||||
|
for (const attributeFilter of attributeFilters) {
|
||||||
|
surveyCache.revalidate({
|
||||||
|
attributeClassId: attributeFilter.attributeClassId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getSurvey = async (surveyId: string): Promise<TSurvey | null> => {
|
export const getSurvey = async (surveyId: string): Promise<TSurvey | null> => {
|
||||||
const survey = await unstable_cache(
|
const survey = await unstable_cache(
|
||||||
async () => {
|
async () => {
|
||||||
validateInputs([surveyId, ZId]);
|
validateInputs([surveyId, ZId]);
|
||||||
|
|
||||||
let surveyPrisma;
|
let surveyPrisma;
|
||||||
try {
|
try {
|
||||||
surveyPrisma = await prisma.survey.findUnique({
|
surveyPrisma = await prisma.survey.findUnique({
|
||||||
@@ -102,9 +120,9 @@ export const getSurvey = async (surveyId: string): Promise<TSurvey | null> => {
|
|||||||
|
|
||||||
return transformedSurvey;
|
return transformedSurvey;
|
||||||
},
|
},
|
||||||
[`surveys-${surveyId}`],
|
[`getSurvey-${surveyId}`],
|
||||||
{
|
{
|
||||||
tags: [getSurveyCacheTag(surveyId)],
|
tags: [surveyCache.tag.byId(surveyId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -125,61 +143,91 @@ export const getSurveysByAttributeClassId = async (
|
|||||||
attributeClassId: string,
|
attributeClassId: string,
|
||||||
page?: number
|
page?: number
|
||||||
): Promise<TSurvey[]> => {
|
): Promise<TSurvey[]> => {
|
||||||
validateInputs([attributeClassId, ZId], [page, ZOptionalNumber]);
|
const surveys = await unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([attributeClassId, ZId], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
const surveysPrisma = await prisma.survey.findMany({
|
const surveysPrisma = await prisma.survey.findMany({
|
||||||
where: {
|
where: {
|
||||||
attributeFilters: {
|
attributeFilters: {
|
||||||
some: {
|
some: {
|
||||||
attributeClassId,
|
attributeClassId,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
select: selectSurvey,
|
||||||
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const surveys: TSurvey[] = [];
|
||||||
|
|
||||||
|
for (const surveyPrisma of surveysPrisma) {
|
||||||
|
const transformedSurvey = {
|
||||||
|
...surveyPrisma,
|
||||||
|
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass.name),
|
||||||
|
};
|
||||||
|
surveys.push(transformedSurvey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return surveys;
|
||||||
},
|
},
|
||||||
select: selectSurvey,
|
[`getSurveysByAttributeClassId-${attributeClassId}-${page}`],
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
{
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
tags: [surveyCache.tag.byAttributeClassId(attributeClassId)],
|
||||||
});
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
const surveys: TSurvey[] = [];
|
return surveys.map((survey) => ({
|
||||||
|
...survey,
|
||||||
for (const surveyPrisma of surveysPrisma) {
|
...formatSurveyDateFields(survey),
|
||||||
const transformedSurvey = {
|
}));
|
||||||
...surveyPrisma,
|
|
||||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass.name),
|
|
||||||
};
|
|
||||||
surveys.push(transformedSurvey);
|
|
||||||
}
|
|
||||||
return surveys;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSurveysByActionClassId = async (actionClassId: string, page?: number): Promise<TSurvey[]> => {
|
export const getSurveysByActionClassId = async (actionClassId: string, page?: number): Promise<TSurvey[]> => {
|
||||||
validateInputs([actionClassId, ZId], [page, ZOptionalNumber]);
|
const surveys = await unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([actionClassId, ZId], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
const surveysPrisma = await prisma.survey.findMany({
|
const surveysPrisma = await prisma.survey.findMany({
|
||||||
where: {
|
where: {
|
||||||
triggers: {
|
triggers: {
|
||||||
some: {
|
some: {
|
||||||
eventClass: {
|
eventClass: {
|
||||||
id: actionClassId,
|
id: actionClassId,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
select: selectSurvey,
|
||||||
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const surveys: TSurvey[] = [];
|
||||||
|
|
||||||
|
for (const surveyPrisma of surveysPrisma) {
|
||||||
|
const transformedSurvey = {
|
||||||
|
...surveyPrisma,
|
||||||
|
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass.name),
|
||||||
|
};
|
||||||
|
surveys.push(transformedSurvey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return surveys;
|
||||||
},
|
},
|
||||||
select: selectSurvey,
|
[`getSurveysByActionClassId-${actionClassId}-${page}`],
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
{
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
tags: [surveyCache.tag.byActionClassId(actionClassId)],
|
||||||
});
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
const surveys: TSurvey[] = [];
|
return surveys.map((survey) => ({
|
||||||
|
...survey,
|
||||||
for (const surveyPrisma of surveysPrisma) {
|
...formatSurveyDateFields(survey),
|
||||||
const transformedSurvey = {
|
}));
|
||||||
...surveyPrisma,
|
|
||||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass.name),
|
|
||||||
};
|
|
||||||
surveys.push(transformedSurvey);
|
|
||||||
}
|
|
||||||
return surveys;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSurveys = async (environmentId: string, page?: number): Promise<TSurvey[]> => {
|
export const getSurveys = async (environmentId: string, page?: number): Promise<TSurvey[]> => {
|
||||||
@@ -216,9 +264,9 @@ export const getSurveys = async (environmentId: string, page?: number): Promise<
|
|||||||
}
|
}
|
||||||
return surveys;
|
return surveys;
|
||||||
},
|
},
|
||||||
[`environments-${environmentId}-surveys`],
|
[`getSurveys-${environmentId}-${page}`],
|
||||||
{
|
{
|
||||||
tags: [getSurveysCacheTag(environmentId)],
|
tags: [surveyCache.tag.byEnvironmentId(environmentId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -249,6 +297,7 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
if (triggers) {
|
if (triggers) {
|
||||||
const newTriggers: string[] = [];
|
const newTriggers: string[] = [];
|
||||||
const removedTriggers: string[] = [];
|
const removedTriggers: string[] = [];
|
||||||
|
|
||||||
// find added triggers
|
// find added triggers
|
||||||
for (const trigger of triggers) {
|
for (const trigger of triggers) {
|
||||||
if (!trigger) {
|
if (!trigger) {
|
||||||
@@ -260,6 +309,7 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
newTriggers.push(trigger);
|
newTriggers.push(trigger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find removed triggers
|
// find removed triggers
|
||||||
for (const trigger of currentSurvey.triggers) {
|
for (const trigger of currentSurvey.triggers) {
|
||||||
if (triggers.find((t: any) => t === trigger)) {
|
if (triggers.find((t: any) => t === trigger)) {
|
||||||
@@ -273,7 +323,7 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
data.triggers = {
|
data.triggers = {
|
||||||
...(data.triggers || []),
|
...(data.triggers || []),
|
||||||
create: newTriggers.map((trigger) => ({
|
create: newTriggers.map((trigger) => ({
|
||||||
eventClassId: actionClasses.find((actionClass) => actionClass.name === trigger)!.id,
|
eventClassId: getActionClassIdFromName(actionClasses, trigger),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -283,23 +333,26 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
...(data.triggers || []),
|
...(data.triggers || []),
|
||||||
deleteMany: {
|
deleteMany: {
|
||||||
eventClassId: {
|
eventClassId: {
|
||||||
in: removedTriggers.map(
|
in: removedTriggers.map((trigger) => getActionClassIdFromName(actionClasses, trigger)),
|
||||||
(trigger) => actionClasses.find((actionClass) => actionClass.name === trigger)!.id
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Revalidation for newly added/removed actionClassId
|
||||||
|
revalidateSurveyByActionClassId(actionClasses, [...newTriggers, ...removedTriggers]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributeFilters) {
|
if (attributeFilters) {
|
||||||
const newFilters: TSurveyAttributeFilter[] = [];
|
const newFilters: TSurveyAttributeFilter[] = [];
|
||||||
const removedFilterIds: string[] = [];
|
const removedFilters: TSurveyAttributeFilter[] = [];
|
||||||
|
|
||||||
// find added attribute filters
|
// find added attribute filters
|
||||||
for (const attributeFilter of attributeFilters) {
|
for (const attributeFilter of attributeFilters) {
|
||||||
if (!attributeFilter.attributeClassId || !attributeFilter.condition || !attributeFilter.value) {
|
if (!attributeFilter.attributeClassId || !attributeFilter.condition || !attributeFilter.value) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentSurvey.attributeFilters.find(
|
currentSurvey.attributeFilters.find(
|
||||||
(f) =>
|
(f) =>
|
||||||
@@ -329,9 +382,14 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
removedFilterIds.push(attributeFilter.attributeClassId);
|
removedFilters.push({
|
||||||
|
attributeClassId: attributeFilter.attributeClassId,
|
||||||
|
condition: attributeFilter.condition,
|
||||||
|
value: attributeFilter.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new attribute filters
|
// create new attribute filters
|
||||||
if (newFilters.length > 0) {
|
if (newFilters.length > 0) {
|
||||||
data.attributeFilters = {
|
data.attributeFilters = {
|
||||||
@@ -343,19 +401,21 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// delete removed triggers
|
// delete removed attribute filter
|
||||||
if (removedFilterIds.length > 0) {
|
if (removedFilters.length > 0) {
|
||||||
// delete all attribute filters that match the removed attribute classes
|
// delete all attribute filters that match the removed attribute classes
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
removedFilterIds.map(async (attributeClassId) => {
|
removedFilters.map(async (attributeFilter) => {
|
||||||
await prisma.surveyAttributeFilter.deleteMany({
|
await prisma.surveyAttributeFilter.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
attributeClassId,
|
attributeClassId: attributeFilter.attributeClassId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
revalidateSurveyByAttributeClassId([...newFilters, ...removedFilters]);
|
||||||
}
|
}
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@@ -375,8 +435,10 @@ export async function updateSurvey(updatedSurvey: TSurvey): Promise<TSurvey> {
|
|||||||
attributeFilters: updatedSurvey.attributeFilters ? updatedSurvey.attributeFilters : [], // Include attributeFilters from updatedSurvey
|
attributeFilters: updatedSurvey.attributeFilters ? updatedSurvey.attributeFilters : [], // Include attributeFilters from updatedSurvey
|
||||||
};
|
};
|
||||||
|
|
||||||
revalidateTag(getSurveysCacheTag(modifiedSurvey.environmentId));
|
surveyCache.revalidate({
|
||||||
revalidateTag(getSurveyCacheTag(modifiedSurvey.id));
|
id: modifiedSurvey.id,
|
||||||
|
environmentId: modifiedSurvey.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
return modifiedSurvey;
|
return modifiedSurvey;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -399,13 +461,27 @@ export async function deleteSurvey(surveyId: string) {
|
|||||||
select: selectSurvey,
|
select: selectSurvey,
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getSurveysCacheTag(deletedSurvey.environmentId));
|
|
||||||
revalidateTag(getSurveyCacheTag(surveyId));
|
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
surveyId,
|
surveyId,
|
||||||
environmentId: deletedSurvey.environmentId,
|
environmentId: deletedSurvey.environmentId,
|
||||||
});
|
});
|
||||||
|
surveyCache.revalidate({
|
||||||
|
id: deletedSurvey.id,
|
||||||
|
environmentId: deletedSurvey.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Revalidate triggers by actionClassId
|
||||||
|
deletedSurvey.triggers.forEach((trigger) => {
|
||||||
|
surveyCache.revalidate({
|
||||||
|
actionClassId: trigger.eventClass.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Revalidate surveys by attributeClassId
|
||||||
|
deletedSurvey.attributeFilters.forEach((attributeFilter) => {
|
||||||
|
surveyCache.revalidate({
|
||||||
|
attributeClassId: attributeFilter.attributeClassId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return deletedSurvey;
|
return deletedSurvey;
|
||||||
}
|
}
|
||||||
@@ -413,6 +489,15 @@ export async function deleteSurvey(surveyId: string) {
|
|||||||
export async function createSurvey(environmentId: string, surveyBody: TSurveyInput): Promise<TSurvey> {
|
export async function createSurvey(environmentId: string, surveyBody: TSurveyInput): Promise<TSurvey> {
|
||||||
validateInputs([environmentId, ZId]);
|
validateInputs([environmentId, ZId]);
|
||||||
|
|
||||||
|
if (surveyBody.attributeFilters) {
|
||||||
|
revalidateSurveyByAttributeClassId(surveyBody.attributeFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surveyBody.triggers) {
|
||||||
|
const actionClasses = await getActionClasses(environmentId);
|
||||||
|
revalidateSurveyByActionClassId(actionClasses, surveyBody.triggers);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Create with triggers & attributeFilters
|
// TODO: Create with triggers & attributeFilters
|
||||||
delete surveyBody.triggers;
|
delete surveyBody.triggers;
|
||||||
delete surveyBody.attributeFilters;
|
delete surveyBody.attributeFilters;
|
||||||
@@ -439,8 +524,10 @@ export async function createSurvey(environmentId: string, surveyBody: TSurveyInp
|
|||||||
|
|
||||||
captureTelemetry("survey created");
|
captureTelemetry("survey created");
|
||||||
|
|
||||||
revalidateTag(getSurveysCacheTag(environmentId));
|
surveyCache.revalidate({
|
||||||
revalidateTag(getSurveyCacheTag(survey.id));
|
id: survey.id,
|
||||||
|
environmentId: survey.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
return transformedSurvey;
|
return transformedSurvey;
|
||||||
}
|
}
|
||||||
@@ -453,6 +540,11 @@ export async function duplicateSurvey(environmentId: string, surveyId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actionClasses = await getActionClasses(environmentId);
|
const actionClasses = await getActionClasses(environmentId);
|
||||||
|
const newAttributeFilters = existingSurvey.attributeFilters.map((attributeFilter) => ({
|
||||||
|
attributeClassId: attributeFilter.attributeClassId,
|
||||||
|
condition: attributeFilter.condition,
|
||||||
|
value: attributeFilter.value,
|
||||||
|
}));
|
||||||
|
|
||||||
// create new survey with the data of the existing survey
|
// create new survey with the data of the existing survey
|
||||||
const newSurvey = await prisma.survey.create({
|
const newSurvey = await prisma.survey.create({
|
||||||
@@ -466,15 +558,11 @@ export async function duplicateSurvey(environmentId: string, surveyId: string) {
|
|||||||
thankYouCard: JSON.parse(JSON.stringify(existingSurvey.thankYouCard)),
|
thankYouCard: JSON.parse(JSON.stringify(existingSurvey.thankYouCard)),
|
||||||
triggers: {
|
triggers: {
|
||||||
create: existingSurvey.triggers.map((trigger) => ({
|
create: existingSurvey.triggers.map((trigger) => ({
|
||||||
eventClassId: actionClasses.find((actionClass) => actionClass.name === trigger)!.id,
|
eventClassId: getActionClassIdFromName(actionClasses, trigger),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
attributeFilters: {
|
attributeFilters: {
|
||||||
create: existingSurvey.attributeFilters.map((attributeFilter) => ({
|
create: newAttributeFilters,
|
||||||
attributeClassId: attributeFilter.attributeClassId,
|
|
||||||
condition: attributeFilter.condition,
|
|
||||||
value: attributeFilter.value,
|
|
||||||
})),
|
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
connect: {
|
connect: {
|
||||||
@@ -496,8 +584,16 @@ export async function duplicateSurvey(environmentId: string, surveyId: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidateTag(getSurveysCacheTag(environmentId));
|
surveyCache.revalidate({
|
||||||
revalidateTag(getSurveyCacheTag(surveyId));
|
id: newSurvey.id,
|
||||||
|
environmentId: newSurvey.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Revalidate surveys by actionClassId
|
||||||
|
revalidateSurveyByActionClassId(actionClasses, existingSurvey.triggers);
|
||||||
|
|
||||||
|
// Revalidate surveys by attributeClassId
|
||||||
|
revalidateSurveyByAttributeClassId(newAttributeFilters);
|
||||||
|
|
||||||
return newSurvey;
|
return newSurvey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { unstable_cache } from "next/cache";
|
|||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { canUserAccessResponse } from "../response/auth";
|
import { canUserAccessResponse } from "../response/auth";
|
||||||
import { canUserAccessTag } from "../tag/auth";
|
import { canUserAccessTag } from "../tag/auth";
|
||||||
import { getTagOnResponseCacheTag } from "./service";
|
import { tagOnResponseCache } from "./cache";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
|
||||||
export const canUserAccessTagOnResponse = async (
|
export const canUserAccessTagOnResponse = async (
|
||||||
@@ -23,5 +23,8 @@ export const canUserAccessTagOnResponse = async (
|
|||||||
return isAuthorizedForTag && isAuthorizedForResponse;
|
return isAuthorizedForTag && isAuthorizedForResponse;
|
||||||
},
|
},
|
||||||
[`users-${userId}-tagOnResponse-${tagId}-${responseId}`],
|
[`users-${userId}-tagOnResponse-${tagId}-${responseId}`],
|
||||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [getTagOnResponseCacheTag(tagId, responseId)] }
|
{
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
tags: [tagOnResponseCache.tag.byResponseIdAndTagId(responseId, tagId)],
|
||||||
|
}
|
||||||
)();
|
)();
|
||||||
|
|||||||
27
packages/lib/tagOnResponse/cache.ts
Normal file
27
packages/lib/tagOnResponse/cache.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
tagId?: string;
|
||||||
|
responseId?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tagOnResponseCache = {
|
||||||
|
tag: {
|
||||||
|
byResponseIdAndTagId(responseId: string, tagId: string) {
|
||||||
|
return `responses-${responseId}-tagOnResponses-${tagId}`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-tagOnResponses`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ tagId, responseId, environmentId }: RevalidateProps): void {
|
||||||
|
if (responseId && tagId) {
|
||||||
|
revalidateTag(this.tag.byResponseIdAndTagId(responseId, tagId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -3,9 +3,19 @@ import "server-only";
|
|||||||
import { prisma } from "@formbricks/database";
|
import { prisma } from "@formbricks/database";
|
||||||
import { TTagsCount, TTagsOnResponses } from "@formbricks/types/tags";
|
import { TTagsCount, TTagsOnResponses } from "@formbricks/types/tags";
|
||||||
import { responseCache } from "../response/cache";
|
import { responseCache } from "../response/cache";
|
||||||
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
import { tagOnResponseCache } from "./cache";
|
||||||
|
import { validateInputs } from "../utils/validate";
|
||||||
|
import { ZId } from "@formbricks/types/environment";
|
||||||
|
|
||||||
export const getTagOnResponseCacheTag = (tagId: string, responseId: string) =>
|
const selectTagsOnResponse = {
|
||||||
`tagsOnResponse-${tagId}-${responseId}`;
|
tag: {
|
||||||
|
select: {
|
||||||
|
environmentId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const addTagToRespone = async (responseId: string, tagId: string): Promise<TTagsOnResponses> => {
|
export const addTagToRespone = async (responseId: string, tagId: string): Promise<TTagsOnResponses> => {
|
||||||
try {
|
try {
|
||||||
@@ -14,12 +24,23 @@ export const addTagToRespone = async (responseId: string, tagId: string): Promis
|
|||||||
responseId,
|
responseId,
|
||||||
tagId,
|
tagId,
|
||||||
},
|
},
|
||||||
|
select: selectTagsOnResponse,
|
||||||
});
|
});
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
id: responseId,
|
id: responseId,
|
||||||
});
|
});
|
||||||
return tagOnResponse;
|
|
||||||
|
tagOnResponseCache.revalidate({
|
||||||
|
tagId,
|
||||||
|
responseId,
|
||||||
|
environmentId: tagOnResponse.tag.environmentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
responseId,
|
||||||
|
tagId,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -34,28 +55,58 @@ export const deleteTagOnResponse = async (responseId: string, tagId: string): Pr
|
|||||||
tagId,
|
tagId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
select: selectTagsOnResponse,
|
||||||
});
|
});
|
||||||
|
|
||||||
responseCache.revalidate({
|
responseCache.revalidate({
|
||||||
id: responseId,
|
id: responseId,
|
||||||
});
|
});
|
||||||
return deletedTag;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTagsOnResponsesCount = async (): Promise<TTagsCount> => {
|
tagOnResponseCache.revalidate({
|
||||||
try {
|
tagId,
|
||||||
const tagsCount = await prisma.tagsOnResponses.groupBy({
|
responseId,
|
||||||
by: ["tagId"],
|
environmentId: deletedTag.tag.environmentId,
|
||||||
_count: {
|
|
||||||
_all: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return tagsCount.map((tagCount) => ({ tagId: tagCount.tagId, count: tagCount._count._all }));
|
return {
|
||||||
|
tagId,
|
||||||
|
responseId,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTagsOnResponsesCount = async (environmentId: string): Promise<TTagsCount> =>
|
||||||
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([environmentId, ZId]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tagsCount = await prisma.tagsOnResponses.groupBy({
|
||||||
|
by: ["tagId"],
|
||||||
|
where: {
|
||||||
|
response: {
|
||||||
|
survey: {
|
||||||
|
environment: {
|
||||||
|
id: environmentId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return tagsCount.map((tagCount) => ({ tagId: tagCount.tagId, count: tagCount._count._all }));
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getTagsOnResponsesCount-${environmentId}`],
|
||||||
|
{
|
||||||
|
tags: [tagOnResponseCache.tag.byEnvironmentId(environmentId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|||||||
34
packages/lib/team/cache.ts
Normal file
34
packages/lib/team/cache.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
userId?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const teamCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `teams-${id}`;
|
||||||
|
},
|
||||||
|
byUserId(userId: string) {
|
||||||
|
return `users-${userId}-teams`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-teams`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, userId, environmentId }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
revalidateTag(this.tag.byUserId(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -5,11 +5,12 @@ import { ZId } from "@formbricks/types/environment";
|
|||||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||||
import { TTeam, TTeamUpdateInput, ZTeamUpdateInput } from "@formbricks/types/teams";
|
import { TTeam, TTeamUpdateInput, ZTeamUpdateInput } from "@formbricks/types/teams";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL, ITEMS_PER_PAGE } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL, ITEMS_PER_PAGE } from "../constants";
|
||||||
import { getEnvironmentCacheTag } from "../environment/service";
|
|
||||||
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
|
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
|
||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
|
import { environmentCache } from "../environment/cache";
|
||||||
|
import { teamCache } from "./cache";
|
||||||
|
|
||||||
export const select = {
|
export const select = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -20,9 +21,6 @@ export const select = {
|
|||||||
stripeCustomerId: true,
|
stripeCustomerId: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTeamsByUserIdCacheTag = (userId: string) => `users-${userId}-teams`;
|
|
||||||
export const getTeamByEnvironmentIdCacheTag = (environmentId: string) => `environments-${environmentId}-team`;
|
|
||||||
|
|
||||||
export const getTeamsByUserId = async (userId: string, page?: number): Promise<TTeam[]> =>
|
export const getTeamsByUserId = async (userId: string, page?: number): Promise<TTeam[]> =>
|
||||||
unstable_cache(
|
unstable_cache(
|
||||||
async () => {
|
async () => {
|
||||||
@@ -41,7 +39,6 @@ export const getTeamsByUserId = async (userId: string, page?: number): Promise<T
|
|||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(userId));
|
|
||||||
|
|
||||||
return teams;
|
return teams;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -52,9 +49,9 @@ export const getTeamsByUserId = async (userId: string, page?: number): Promise<T
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`users-${userId}-teams`],
|
[`getTeamsByUserId-${userId}-${page}`],
|
||||||
{
|
{
|
||||||
tags: [getTeamsByUserIdCacheTag(userId)],
|
tags: [teamCache.tag.byUserId(userId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -79,7 +76,6 @@ export const getTeamByEnvironmentId = async (environmentId: string): Promise<TTe
|
|||||||
},
|
},
|
||||||
select: { ...select, memberships: true }, // include memberships
|
select: { ...select, memberships: true }, // include memberships
|
||||||
});
|
});
|
||||||
revalidateTag(getTeamByEnvironmentIdCacheTag(environmentId));
|
|
||||||
|
|
||||||
return team;
|
return team;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -91,9 +87,9 @@ export const getTeamByEnvironmentId = async (environmentId: string): Promise<TTe
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`environments-${environmentId}-team`],
|
[`getTeamByEnvironmentId-${environmentId}`],
|
||||||
{
|
{
|
||||||
tags: [getTeamByEnvironmentIdCacheTag(environmentId)],
|
tags: [teamCache.tag.byEnvironmentId(environmentId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -107,6 +103,10 @@ export const createTeam = async (teamInput: TTeamUpdateInput): Promise<TTeam> =>
|
|||||||
select,
|
select,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
teamCache.revalidate({
|
||||||
|
id: team.id,
|
||||||
|
});
|
||||||
|
|
||||||
return team;
|
return team;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -125,13 +125,17 @@ export const updateTeam = async (teamId: string, data: Partial<TTeamUpdateInput>
|
|||||||
|
|
||||||
// revalidate cache for members
|
// revalidate cache for members
|
||||||
updatedTeam?.memberships.forEach((membership) => {
|
updatedTeam?.memberships.forEach((membership) => {
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(membership.userId));
|
teamCache.revalidate({
|
||||||
|
userId: membership.userId,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// revalidate cache for environments
|
// revalidate cache for environments
|
||||||
updatedTeam?.products.forEach((product) => {
|
updatedTeam?.products.forEach((product) => {
|
||||||
product.environments.forEach((environment) => {
|
product.environments.forEach((environment) => {
|
||||||
revalidateTag(getTeamByEnvironmentIdCacheTag(environment.id));
|
teamCache.revalidate({
|
||||||
|
environmentId: environment.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,6 +145,10 @@ export const updateTeam = async (teamId: string, data: Partial<TTeamUpdateInput>
|
|||||||
products: undefined,
|
products: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
teamCache.revalidate({
|
||||||
|
id: team.id,
|
||||||
|
});
|
||||||
|
|
||||||
return team;
|
return team;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2016") {
|
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2016") {
|
||||||
@@ -163,14 +171,21 @@ export const deleteTeam = async (teamId: string): Promise<TTeam> => {
|
|||||||
|
|
||||||
// revalidate cache for members
|
// revalidate cache for members
|
||||||
deletedTeam?.memberships.forEach((membership) => {
|
deletedTeam?.memberships.forEach((membership) => {
|
||||||
revalidateTag(getTeamsByUserIdCacheTag(membership.userId));
|
teamCache.revalidate({
|
||||||
|
userId: membership.userId,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// revalidate cache for environments
|
// revalidate cache for environments
|
||||||
deletedTeam?.products.forEach((product) => {
|
deletedTeam?.products.forEach((product) => {
|
||||||
product.environments.forEach((environment) => {
|
product.environments.forEach((environment) => {
|
||||||
revalidateTag(getTeamByEnvironmentIdCacheTag(environment.id));
|
environmentCache.revalidate({
|
||||||
revalidateTag(getEnvironmentCacheTag(environment.id));
|
id: environment.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
teamCache.revalidate({
|
||||||
|
environmentId: environment.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -180,6 +195,10 @@ export const deleteTeam = async (teamId: string): Promise<TTeam> => {
|
|||||||
products: undefined,
|
products: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
teamCache.revalidate({
|
||||||
|
id: team.id,
|
||||||
|
});
|
||||||
|
|
||||||
return team;
|
return team;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
|||||||
@@ -5,53 +5,65 @@ import { Prisma } from "@prisma/client";
|
|||||||
import { validateInputs } from "../utils/validate";
|
import { validateInputs } from "../utils/validate";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||||
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
import { teamCache } from "../team/cache";
|
||||||
|
|
||||||
export const getTeamDetails = async (
|
export const getTeamDetails = async (
|
||||||
environmentId: string
|
environmentId: string
|
||||||
): Promise<{ teamId: string; teamOwnerId: string | undefined }> => {
|
): Promise<{ teamId: string; teamOwnerId: string | undefined }> =>
|
||||||
validateInputs([environmentId, ZId]);
|
unstable_cache(
|
||||||
try {
|
async () => {
|
||||||
const environment = await prisma.environment.findUnique({
|
validateInputs([environmentId, ZId]);
|
||||||
where: {
|
|
||||||
id: environmentId,
|
try {
|
||||||
},
|
const environment = await prisma.environment.findUnique({
|
||||||
select: {
|
where: {
|
||||||
product: {
|
id: environmentId,
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
team: {
|
product: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
team: {
|
||||||
memberships: {
|
|
||||||
select: {
|
select: {
|
||||||
userId: true,
|
id: true,
|
||||||
role: true,
|
memberships: {
|
||||||
|
select: {
|
||||||
|
userId: true,
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!environment) {
|
if (!environment) {
|
||||||
throw new ResourceNotFoundError("Environment", environmentId);
|
throw new ResourceNotFoundError("Environment", environmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const teamId: string = environment.product.team.id;
|
||||||
|
// find team owner
|
||||||
|
const teamOwnerId: string | undefined = environment.product.team.memberships.find(
|
||||||
|
(m) => m.role === "owner"
|
||||||
|
)?.userId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
teamId: teamId,
|
||||||
|
teamOwnerId: teamOwnerId,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
throw new DatabaseError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getTeamDetails-${environmentId}`],
|
||||||
|
{
|
||||||
|
tags: [teamCache.tag.byEnvironmentId(environmentId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
|
)();
|
||||||
const teamId: string = environment.product.team.id;
|
|
||||||
// find team owner
|
|
||||||
const teamOwnerId: string | undefined = environment.product.team.memberships.find(
|
|
||||||
(m) => m.role === "owner"
|
|
||||||
)?.userId;
|
|
||||||
|
|
||||||
return {
|
|
||||||
teamId: teamId,
|
|
||||||
teamOwnerId: teamOwnerId,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
||||||
throw new DatabaseError(error.message);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getWebhook } from "./service";
|
|||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { webhookCache } from "./cache";
|
||||||
|
|
||||||
export const canUserAccessWebhook = async (userId: string, webhookId: string): Promise<boolean> =>
|
export const canUserAccessWebhook = async (userId: string, webhookId: string): Promise<boolean> =>
|
||||||
await unstable_cache(
|
await unstable_cache(
|
||||||
@@ -18,8 +19,9 @@ export const canUserAccessWebhook = async (userId: string, webhookId: string): P
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[`${userId}-${webhookId}`],
|
[`canUserAccessWebhook-${userId}-${webhookId}`],
|
||||||
{
|
{
|
||||||
|
tags: [webhookCache.tag.byId(webhookId)],
|
||||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|||||||
35
packages/lib/webhook/cache.ts
Normal file
35
packages/lib/webhook/cache.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
import { TWebhookInput } from "@formbricks/types/webhooks";
|
||||||
|
|
||||||
|
interface RevalidateProps {
|
||||||
|
id?: string;
|
||||||
|
environmentId?: string;
|
||||||
|
source?: TWebhookInput["source"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webhookCache = {
|
||||||
|
tag: {
|
||||||
|
byId(id: string) {
|
||||||
|
return `webhooks-${id}`;
|
||||||
|
},
|
||||||
|
byEnvironmentId(environmentId: string) {
|
||||||
|
return `environments-${environmentId}-webhooks`;
|
||||||
|
},
|
||||||
|
byEnvironmentIdAndSource(environmentId: string, source: TWebhookInput["source"]) {
|
||||||
|
return `environments-${environmentId}-sources-${source}-webhooks`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
revalidate({ id, environmentId, source }: RevalidateProps): void {
|
||||||
|
if (id) {
|
||||||
|
revalidateTag(this.tag.byId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentId && source) {
|
||||||
|
revalidateTag(this.tag.byEnvironmentIdAndSource(environmentId, source));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -7,60 +7,89 @@ import { validateInputs } from "../utils/validate";
|
|||||||
import { ZId } from "@formbricks/types/environment";
|
import { ZId } from "@formbricks/types/environment";
|
||||||
import { ResourceNotFoundError, DatabaseError, InvalidInputError } from "@formbricks/types/errors";
|
import { ResourceNotFoundError, DatabaseError, InvalidInputError } from "@formbricks/types/errors";
|
||||||
import { ZOptionalNumber } from "@formbricks/types/common";
|
import { ZOptionalNumber } from "@formbricks/types/common";
|
||||||
import { ITEMS_PER_PAGE } from "../constants";
|
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||||
|
import { webhookCache } from "./cache";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
|
||||||
export const getWebhooks = async (environmentId: string, page?: number): Promise<TWebhook[]> => {
|
export const getWebhooks = async (environmentId: string, page?: number): Promise<TWebhook[]> =>
|
||||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const webhooks = await prisma.webhook.findMany({
|
const webhooks = await prisma.webhook.findMany({
|
||||||
where: {
|
where: {
|
||||||
environmentId: environmentId,
|
environmentId: environmentId,
|
||||||
},
|
},
|
||||||
take: page ? ITEMS_PER_PAGE : undefined,
|
take: page ? ITEMS_PER_PAGE : undefined,
|
||||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||||
});
|
});
|
||||||
return webhooks;
|
return webhooks;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
|
throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[`getWebhooks-${environmentId}-${page}`],
|
||||||
|
{
|
||||||
|
tags: [webhookCache.tag.byEnvironmentId(environmentId)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
export const getCountOfWebhooksBasedOnSource = async (
|
export const getCountOfWebhooksBasedOnSource = async (
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
source: TWebhookInput["source"]
|
source: TWebhookInput["source"]
|
||||||
): Promise<number> => {
|
): Promise<number> =>
|
||||||
validateInputs([environmentId, ZId], [source, ZId]);
|
unstable_cache(
|
||||||
try {
|
async () => {
|
||||||
const count = await prisma.webhook.count({
|
validateInputs([environmentId, ZId], [source, ZId]);
|
||||||
where: {
|
|
||||||
environmentId,
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return count;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getWebhook = async (id: string): Promise<TWebhook | null> => {
|
try {
|
||||||
validateInputs([id, ZId]);
|
const count = await prisma.webhook.count({
|
||||||
const webhook = await prisma.webhook.findUnique({
|
where: {
|
||||||
where: {
|
environmentId,
|
||||||
id,
|
source,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
[`getCountOfWebhooksBasedOnSource-${environmentId}-${source}`],
|
||||||
return webhook;
|
{
|
||||||
};
|
tags: [webhookCache.tag.byEnvironmentIdAndSource(environmentId, source)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
|
export const getWebhook = async (id: string): Promise<TWebhook | null> =>
|
||||||
|
unstable_cache(
|
||||||
|
async () => {
|
||||||
|
validateInputs([id, ZId]);
|
||||||
|
|
||||||
|
const webhook = await prisma.webhook.findUnique({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return webhook;
|
||||||
|
},
|
||||||
|
[`getWebhook-${id}`],
|
||||||
|
{
|
||||||
|
tags: [webhookCache.tag.byId(id)],
|
||||||
|
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
export const createWebhook = async (
|
export const createWebhook = async (
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
webhookInput: TWebhookInput
|
webhookInput: TWebhookInput
|
||||||
): Promise<TWebhook> => {
|
): Promise<TWebhook> => {
|
||||||
validateInputs([environmentId, ZId], [webhookInput, ZWebhookInput]);
|
validateInputs([environmentId, ZId], [webhookInput, ZWebhookInput]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let createdWebhook = await prisma.webhook.create({
|
const createdWebhook = await prisma.webhook.create({
|
||||||
data: {
|
data: {
|
||||||
...webhookInput,
|
...webhookInput,
|
||||||
surveyIds: webhookInput.surveyIds || [],
|
surveyIds: webhookInput.surveyIds || [],
|
||||||
@@ -71,6 +100,13 @@ export const createWebhook = async (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
webhookCache.revalidate({
|
||||||
|
id: createdWebhook.id,
|
||||||
|
environmentId: createdWebhook.environmentId,
|
||||||
|
source: createdWebhook.source,
|
||||||
|
});
|
||||||
|
|
||||||
return createdWebhook;
|
return createdWebhook;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof InvalidInputError)) {
|
if (!(error instanceof InvalidInputError)) {
|
||||||
@@ -87,7 +123,7 @@ export const updateWebhook = async (
|
|||||||
): Promise<TWebhook> => {
|
): Promise<TWebhook> => {
|
||||||
validateInputs([environmentId, ZId], [webhookId, ZId], [webhookInput, ZWebhookInput]);
|
validateInputs([environmentId, ZId], [webhookId, ZId], [webhookInput, ZWebhookInput]);
|
||||||
try {
|
try {
|
||||||
const webhook = await prisma.webhook.update({
|
const updatedWebhook = await prisma.webhook.update({
|
||||||
where: {
|
where: {
|
||||||
id: webhookId,
|
id: webhookId,
|
||||||
},
|
},
|
||||||
@@ -98,7 +134,14 @@ export const updateWebhook = async (
|
|||||||
surveyIds: webhookInput.surveyIds || [],
|
surveyIds: webhookInput.surveyIds || [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return webhook;
|
|
||||||
|
webhookCache.revalidate({
|
||||||
|
id: updatedWebhook.id,
|
||||||
|
environmentId: updatedWebhook.environmentId,
|
||||||
|
source: updatedWebhook.source,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedWebhook;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError(
|
throw new DatabaseError(
|
||||||
`Database error when updating webhook with ID ${webhookId} for environment ${environmentId}`
|
`Database error when updating webhook with ID ${webhookId} for environment ${environmentId}`
|
||||||
@@ -108,12 +151,20 @@ export const updateWebhook = async (
|
|||||||
|
|
||||||
export const deleteWebhook = async (id: string): Promise<TWebhook> => {
|
export const deleteWebhook = async (id: string): Promise<TWebhook> => {
|
||||||
validateInputs([id, ZId]);
|
validateInputs([id, ZId]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let deletedWebhook = await prisma.webhook.delete({
|
let deletedWebhook = await prisma.webhook.delete({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
webhookCache.revalidate({
|
||||||
|
id: deletedWebhook.id,
|
||||||
|
environmentId: deletedWebhook.environmentId,
|
||||||
|
source: deletedWebhook.source,
|
||||||
|
});
|
||||||
|
|
||||||
return deletedWebhook;
|
return deletedWebhook;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025") {
|
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025") {
|
||||||
|
|||||||
99
pnpm-lock.yaml
generated
99
pnpm-lock.yaml
generated
@@ -39,8 +39,8 @@ importers:
|
|||||||
specifier: ^2.0.18
|
specifier: ^2.0.18
|
||||||
version: 2.0.18(react@18.2.0)
|
version: 2.0.18(react@18.2.0)
|
||||||
next:
|
next:
|
||||||
specifier: 13.5.5
|
specifier: 14.0.0
|
||||||
version: 13.5.5(react-dom@18.2.0)(react@18.2.0)
|
version: 14.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
@@ -331,7 +331,7 @@ importers:
|
|||||||
version: 0.0.7
|
version: 0.0.7
|
||||||
'@sentry/nextjs':
|
'@sentry/nextjs':
|
||||||
specifier: ^7.75.0
|
specifier: ^7.75.0
|
||||||
version: 7.75.0(encoding@0.1.13)(next@13.5.6)(react@18.2.0)
|
version: 7.75.0(encoding@0.1.13)(next@14.0.0)(react@18.2.0)
|
||||||
'@t3-oss/env-nextjs':
|
'@t3-oss/env-nextjs':
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1(zod@3.22.4)
|
version: 0.7.1(zod@3.22.4)
|
||||||
@@ -366,8 +366,8 @@ importers:
|
|||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
next:
|
next:
|
||||||
specifier: 13.5.6
|
specifier: 14.0.0
|
||||||
version: 13.5.6(react-dom@18.2.0)(react@18.2.0)
|
version: 14.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: ^6.9.7
|
specifier: ^6.9.7
|
||||||
version: 6.9.7
|
version: 6.9.7
|
||||||
@@ -520,7 +520,7 @@ importers:
|
|||||||
version: 9.0.0(eslint@8.51.0)
|
version: 9.0.0(eslint@8.51.0)
|
||||||
eslint-config-turbo:
|
eslint-config-turbo:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 1.10.14(eslint@8.51.0)
|
version: 1.8.8(eslint@8.51.0)
|
||||||
eslint-plugin-react:
|
eslint-plugin-react:
|
||||||
specifier: 7.33.2
|
specifier: 7.33.2
|
||||||
version: 7.33.2(eslint@8.51.0)
|
version: 7.33.2(eslint@8.51.0)
|
||||||
@@ -640,7 +640,7 @@ importers:
|
|||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: ^4.23.2
|
specifier: ^4.23.2
|
||||||
version: 4.23.2(next@13.5.6)(nodemailer@6.9.6)(react-dom@18.2.0)(react@18.2.0)
|
version: 4.23.2(next@13.5.5)(nodemailer@6.9.6)(react-dom@18.2.0)(react@18.2.0)
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: ^6.9.6
|
specifier: ^6.9.6
|
||||||
version: 6.9.6
|
version: 6.9.6
|
||||||
@@ -5114,6 +5114,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==}
|
resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@next/env@14.0.0:
|
||||||
|
resolution: {integrity: sha512-cIKhxkfVELB6hFjYsbtEeTus2mwrTC+JissfZYM0n+8Fv+g8ucUfOlm3VEDtwtwydZ0Nuauv3bl0qF82nnCAqA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next@13.5.5:
|
/@next/eslint-plugin-next@13.5.5:
|
||||||
resolution: {integrity: sha512-S/32s4S+SCOpW58lHKdmILAAPRdnsSei7Y3L1oZSoe5Eh0QSlzbG1nYyIpnpwWgz3T7qe3imdq7cJ6Hf29epRA==}
|
resolution: {integrity: sha512-S/32s4S+SCOpW58lHKdmILAAPRdnsSei7Y3L1oZSoe5Eh0QSlzbG1nYyIpnpwWgz3T7qe3imdq7cJ6Hf29epRA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5145,8 +5149,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-arm64@13.5.6:
|
/@next/swc-darwin-arm64@14.0.0:
|
||||||
resolution: {integrity: sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==}
|
resolution: {integrity: sha512-HQKi159jCz4SRsPesVCiNN6tPSAFUkOuSkpJsqYTIlbHLKr1mD6be/J0TvWV6fwJekj81bZV9V/Tgx3C2HO9lA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
@@ -5163,8 +5167,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64@13.5.6:
|
/@next/swc-darwin-x64@14.0.0:
|
||||||
resolution: {integrity: sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==}
|
resolution: {integrity: sha512-4YyQLMSaCgX/kgC1jjF3s3xSoBnwHuDhnF6WA1DWNEYRsbOOPWjcYhv8TKhRe2ApdOam+VfQSffC4ZD+X4u1Cg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
@@ -5181,8 +5185,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu@13.5.6:
|
/@next/swc-linux-arm64-gnu@14.0.0:
|
||||||
resolution: {integrity: sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==}
|
resolution: {integrity: sha512-io7fMkJ28Glj7SH8yvnlD6naIhRDnDxeE55CmpQkj3+uaA2Hko6WGY2pT5SzpQLTnGGnviK85cy8EJ2qsETj/g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -5199,8 +5203,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl@13.5.6:
|
/@next/swc-linux-arm64-musl@14.0.0:
|
||||||
resolution: {integrity: sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==}
|
resolution: {integrity: sha512-nC2h0l1Jt8LEzyQeSs/BKpXAMe0mnHIMykYALWaeddTqCv5UEN8nGO3BG8JAqW/Y8iutqJsaMe2A9itS0d/r8w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -5217,8 +5221,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu@13.5.6:
|
/@next/swc-linux-x64-gnu@14.0.0:
|
||||||
resolution: {integrity: sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==}
|
resolution: {integrity: sha512-Wf+WjXibJQ7hHXOdNOmSMW5bxeJHVf46Pwb3eLSD2L76NrytQlif9NH7JpHuFlYKCQGfKfgSYYre5rIfmnSwQw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -5235,8 +5239,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl@13.5.6:
|
/@next/swc-linux-x64-musl@14.0.0:
|
||||||
resolution: {integrity: sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==}
|
resolution: {integrity: sha512-WTZb2G7B+CTsdigcJVkRxfcAIQj7Lf0ipPNRJ3vlSadU8f0CFGv/ST+sJwF5eSwIe6dxKoX0DG6OljDBaad+rg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -5253,8 +5257,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc@13.5.6:
|
/@next/swc-win32-arm64-msvc@14.0.0:
|
||||||
resolution: {integrity: sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==}
|
resolution: {integrity: sha512-7R8/x6oQODmNpnWVW00rlWX90sIlwluJwcvMT6GXNIBOvEf01t3fBg0AGURNKdTJg2xNuP7TyLchCL7Lh2DTiw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -5271,8 +5275,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc@13.5.6:
|
/@next/swc-win32-ia32-msvc@14.0.0:
|
||||||
resolution: {integrity: sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==}
|
resolution: {integrity: sha512-RLK1nELvhCnxaWPF07jGU4x3tjbyx2319q43loZELqF0+iJtKutZ+Lk8SVmf/KiJkYBc7Cragadz7hb3uQvz4g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -5289,8 +5293,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc@13.5.6:
|
/@next/swc-win32-x64-msvc@14.0.0:
|
||||||
resolution: {integrity: sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==}
|
resolution: {integrity: sha512-g6hLf1SUko+hnnaywQQZzzb3BRecQsoKkF3o/C+F+dOA4w/noVAJngUVkfwF0+2/8FzNznM7ofM6TGZO9svn7w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -7293,7 +7297,7 @@ packages:
|
|||||||
localforage: 1.10.0
|
localforage: 1.10.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/nextjs@7.75.0(encoding@0.1.13)(next@13.5.6)(react@18.2.0):
|
/@sentry/nextjs@7.75.0(encoding@0.1.13)(next@14.0.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-EKdTUe5Q48qRgFM7T9s9sXwOEMvaouepHF5m343jSuTugTQ7CCJIR9jLGgUuRPgaUdE0F+PyJWopgVAZpaVFSg==}
|
resolution: {integrity: sha512-EKdTUe5Q48qRgFM7T9s9sXwOEMvaouepHF5m343jSuTugTQ7CCJIR9jLGgUuRPgaUdE0F+PyJWopgVAZpaVFSg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -7314,7 +7318,7 @@ packages:
|
|||||||
'@sentry/vercel-edge': 7.75.0
|
'@sentry/vercel-edge': 7.75.0
|
||||||
'@sentry/webpack-plugin': 1.20.0(encoding@0.1.13)
|
'@sentry/webpack-plugin': 1.20.0(encoding@0.1.13)
|
||||||
chalk: 3.0.0
|
chalk: 3.0.0
|
||||||
next: 13.5.6(react-dom@18.2.0)(react@18.2.0)
|
next: 14.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
rollup: 2.78.0
|
rollup: 2.78.0
|
||||||
@@ -13035,13 +13039,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==}
|
resolution: {integrity: sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-config-turbo@1.10.14(eslint@8.51.0):
|
/eslint-config-turbo@1.8.8(eslint@8.51.0):
|
||||||
resolution: {integrity: sha512-ZeB+IcuFXy1OICkLuAplVa0euoYbhK+bMEQd0nH9+Lns18lgZRm33mVz/iSoH9VdUzl/1ZmFmoK+RpZc+8R80A==}
|
resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>6.6.0'
|
eslint: '>6.6.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 8.51.0
|
eslint: 8.51.0
|
||||||
eslint-plugin-turbo: 1.10.14(eslint@8.51.0)
|
eslint-plugin-turbo: 1.8.8(eslint@8.51.0)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-import-resolver-node@0.3.9:
|
/eslint-import-resolver-node@0.3.9:
|
||||||
@@ -13243,12 +13247,11 @@ packages:
|
|||||||
- typescript
|
- typescript
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-turbo@1.10.14(eslint@8.51.0):
|
/eslint-plugin-turbo@1.8.8(eslint@8.51.0):
|
||||||
resolution: {integrity: sha512-sBdBDnYr9AjT1g4lR3PBkZDonTrMnR4TvuGv5W0OiF7z9az1rI68yj2UHJZvjkwwcGu5mazWA1AfB0oaagpmfg==}
|
resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>6.6.0'
|
eslint: '>6.6.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
dotenv: 16.0.3
|
|
||||||
eslint: 8.51.0
|
eslint: 8.51.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -18220,7 +18223,7 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next-auth@4.23.2(next@13.5.6)(nodemailer@6.9.6)(react-dom@18.2.0)(react@18.2.0):
|
/next-auth@4.23.2(next@13.5.5)(nodemailer@6.9.6)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-VRmInu0r/yZNFQheDFeOKtiugu3bt90Po3owAQDnFQ3YLQFmUKgFjcE2+3L0ny5jsJpBXaKbm7j7W2QTc6Ye2A==}
|
resolution: {integrity: sha512-VRmInu0r/yZNFQheDFeOKtiugu3bt90Po3owAQDnFQ3YLQFmUKgFjcE2+3L0ny5jsJpBXaKbm7j7W2QTc6Ye2A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: ^12.2.5 || ^13
|
next: ^12.2.5 || ^13
|
||||||
@@ -18235,7 +18238,7 @@ packages:
|
|||||||
'@panva/hkdf': 1.1.1
|
'@panva/hkdf': 1.1.1
|
||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
jose: 4.15.4
|
jose: 4.15.4
|
||||||
next: 13.5.6(react-dom@18.2.0)(react@18.2.0)
|
next: 13.5.5(react-dom@18.2.0)(react@18.2.0)
|
||||||
nodemailer: 6.9.6
|
nodemailer: 6.9.6
|
||||||
oauth: 0.9.15
|
oauth: 0.9.15
|
||||||
openid-client: 5.6.1
|
openid-client: 5.6.1
|
||||||
@@ -18339,9 +18342,9 @@ packages:
|
|||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/next@13.5.6(react-dom@18.2.0)(react@18.2.0):
|
/next@14.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==}
|
resolution: {integrity: sha512-J0jHKBJpB9zd4+c153sair0sz44mbaCHxggs8ryVXSFBuBqJ8XdE9/ozoV85xGh2VnSjahwntBZZgsihL9QznA==}
|
||||||
engines: {node: '>=16.14.0'}
|
engines: {node: '>=18.17.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@opentelemetry/api': ^1.1.0
|
'@opentelemetry/api': ^1.1.0
|
||||||
@@ -18354,7 +18357,7 @@ packages:
|
|||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 13.5.6
|
'@next/env': 14.0.0
|
||||||
'@swc/helpers': 0.5.2
|
'@swc/helpers': 0.5.2
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
caniuse-lite: 1.0.30001551
|
caniuse-lite: 1.0.30001551
|
||||||
@@ -18364,15 +18367,15 @@ packages:
|
|||||||
styled-jsx: 5.1.1(react@18.2.0)
|
styled-jsx: 5.1.1(react@18.2.0)
|
||||||
watchpack: 2.4.0
|
watchpack: 2.4.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 13.5.6
|
'@next/swc-darwin-arm64': 14.0.0
|
||||||
'@next/swc-darwin-x64': 13.5.6
|
'@next/swc-darwin-x64': 14.0.0
|
||||||
'@next/swc-linux-arm64-gnu': 13.5.6
|
'@next/swc-linux-arm64-gnu': 14.0.0
|
||||||
'@next/swc-linux-arm64-musl': 13.5.6
|
'@next/swc-linux-arm64-musl': 14.0.0
|
||||||
'@next/swc-linux-x64-gnu': 13.5.6
|
'@next/swc-linux-x64-gnu': 14.0.0
|
||||||
'@next/swc-linux-x64-musl': 13.5.6
|
'@next/swc-linux-x64-musl': 14.0.0
|
||||||
'@next/swc-win32-arm64-msvc': 13.5.6
|
'@next/swc-win32-arm64-msvc': 14.0.0
|
||||||
'@next/swc-win32-ia32-msvc': 13.5.6
|
'@next/swc-win32-ia32-msvc': 14.0.0
|
||||||
'@next/swc-win32-x64-msvc': 13.5.6
|
'@next/swc-win32-x64-msvc': 14.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
|||||||
Reference in New Issue
Block a user