Initial Commit

This commit is contained in:
Ahmad 2024-02-14 21:30:10 -05:00
commit f3e2f01bd7
No known key found for this signature in database
GPG key ID: 8FD8A93530D182BF
150 changed files with 13612 additions and 0 deletions

37
lib/create-audit-log.ts Normal file
View file

@ -0,0 +1,37 @@
import { auth, currentUser } from "@clerk/nextjs";
import { ACTION, ENTITY_TYPE } from "@prisma/client";
import { db } from "@/lib/db";
interface Props {
entityId: string;
entityType: ENTITY_TYPE;
entityTitle: string;
action: ACTION;
}
export const createAuditLog = async (props: Props) => {
try {
const { orgId } = auth();
const user = await currentUser();
if (!orgId || !user) throw new Error("User not found");
const { entityId, entityType, entityTitle, action } = props;
await db.auditLog.create({
data: {
orgId,
entityId,
entityType,
entityTitle,
action,
userId: user.id,
userImage: user?.imageUrl,
userName: user?.firstName + " " + user?.lastName,
},
});
} catch (error) {
console.error("[AUDIT_LOG_ERROR]", error);
}
};

28
lib/create-safe-action.ts Normal file
View file

@ -0,0 +1,28 @@
import { z } from "zod";
export type FieldErrors<T> = {
[K in keyof T]?: string[];
};
export type ActionState<TInput, TOutput> = {
fieldErrors?: FieldErrors<TInput>;
error?: string | null;
data?: TOutput;
};
export const createSafeAction = <TInput, TOutput>(
schema: z.Schema<TInput>,
handler: (validatedData: TInput) => Promise<ActionState<TInput, TOutput>>
) => {
return async (data: TInput): Promise<ActionState<TInput, TOutput>> => {
const validationResult = schema.safeParse(data);
if (!validationResult.success) {
return {
fieldErrors: validationResult.error.flatten()
.fieldErrors as FieldErrors<TInput>,
};
}
return handler(validationResult.data);
};
};

9
lib/db.ts Normal file
View file

@ -0,0 +1,9 @@
import { PrismaClient } from "@prisma/client";
declare global {
var prisma: PrismaClient | undefined;
}
export const db = globalThis.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") globalThis.prisma = db;

1
lib/fetcher.ts Normal file
View file

@ -0,0 +1 @@
export const fetcher = (url: string) => fetch(url).then((res) => res.json())

View file

@ -0,0 +1,16 @@
import { ACTION, AuditLog } from "@prisma/client";
export const generateLogMessage = (log: AuditLog) => {
const { action, entityTitle, entityType } = log;
switch (action) {
case ACTION.CREATE:
return `Created ${entityType.toLowerCase()} "${entityTitle}"`;
case ACTION.UPDATE:
return `Updated ${entityType.toLowerCase()} "${entityTitle}"`;
case ACTION.DELETE:
return `Deleted ${entityType.toLowerCase()} "${entityTitle}"`;
default:
return `Unknown action ${entityType.toLowerCase()} "${entityTitle}"`;
}
};

86
lib/org-limit.ts Normal file
View file

@ -0,0 +1,86 @@
import { auth } from "@clerk/nextjs";
import { db } from "@/lib/db";
import { MAX_FREE_BOARDS } from "@/constants/boards";
export const incrementAvailableCount = async () => {
const { orgId } = auth();
if (!orgId) {
throw new Error("Unauthorized");
}
const orgLimit = await db.orgLimit.findUnique({
where: { orgId },
});
if (orgLimit) {
await db.orgLimit.update({
where: { orgId },
data: { count: orgLimit.count + 1 },
});
} else {
await db.orgLimit.create({
data: { orgId, count: 1 },
});
}
};
export const decreaseAvailableCount = async () => {
const { orgId } = auth();
if (!orgId) {
throw new Error("Unauthorized");
}
const orgLimit = await db.orgLimit.findUnique({
where: { orgId },
});
if (orgLimit) {
await db.orgLimit.update({
where: { orgId },
data: { count: orgLimit.count > 0 ? orgLimit.count - 1 : 0 },
});
} else {
await db.orgLimit.create({
data: { orgId, count: 1 },
});
}
};
export const hasAvailableCount = async () => {
const { orgId } = auth();
if (!orgId) {
throw new Error("Unauthorized");
}
const orgLimit = await db.orgLimit.findUnique({
where: { orgId },
});
if (!orgLimit || orgLimit.count < MAX_FREE_BOARDS) {
return true;
} else {
return false;
}
};
export const getAvailableCount = async () => {
const { orgId } = auth();
if (!orgId) {
return 0;
}
const orgLimit = await db.orgLimit.findUnique({
where: { orgId },
});
if (!orgLimit) {
return 0;
}
return orgLimit.count;
};

6
lib/stripe.ts Normal file
View file

@ -0,0 +1,6 @@
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_API_KEY!, {
apiVersion: "2023-10-16",
typescript: true,
});

35
lib/subscription.ts Normal file
View file

@ -0,0 +1,35 @@
import { auth } from "@clerk/nextjs";
import { db } from "@/lib/db";
const DAY_IN_MS = 86_400_000;
export const checkSubscription = async () => {
const { orgId } = auth();
if (!orgId) {
return false;
}
const orgSubscription = await db.orgSubscription.findUnique({
where: {
orgId,
},
select: {
stripeSubscriptionId: true,
stripeCurrentPeriodEnd: true,
stripeCustomerId: true,
stripePriceId: true,
},
});
if (!orgSubscription) {
return false;
}
const isValid =
orgSubscription.stripePriceId &&
orgSubscription.stripeCurrentPeriodEnd?.getTime()! + DAY_IN_MS > Date.now();
return !!isValid;
};

6
lib/unsplash.ts Normal file
View file

@ -0,0 +1,6 @@
import { createApi } from "unsplash-js";
export const unsplash = createApi({
accessKey: process.env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY!,
fetch: fetch,
});

10
lib/utils.ts Normal file
View file

@ -0,0 +1,10 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function absoluteUrl(pathname: string) {
return `${process.env.NEXT_PUBLIC_APP_URL}${pathname}`
}