mirror of
https://github.com/ahmadk953/tasko.git
synced 2025-05-04 04:33:10 +00:00
Initial Commit
This commit is contained in:
commit
f3e2f01bd7
150 changed files with 13612 additions and 0 deletions
37
lib/create-audit-log.ts
Normal file
37
lib/create-audit-log.ts
Normal 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
28
lib/create-safe-action.ts
Normal 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
9
lib/db.ts
Normal 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
1
lib/fetcher.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const fetcher = (url: string) => fetch(url).then((res) => res.json())
|
16
lib/generate-log-message.ts
Normal file
16
lib/generate-log-message.ts
Normal 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
86
lib/org-limit.ts
Normal 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
6
lib/stripe.ts
Normal 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
35
lib/subscription.ts
Normal 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
6
lib/unsplash.ts
Normal 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
10
lib/utils.ts
Normal 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}`
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue