Accepted incoming changes
This commit is contained in:
@@ -1,46 +1,68 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
|
||||
export default async function getTags(userId: number) {
|
||||
// Remove empty tags
|
||||
await prisma.tag.deleteMany({
|
||||
where: {
|
||||
ownerId: userId,
|
||||
links: {
|
||||
none: {},
|
||||
export default async function getTags({
|
||||
userId,
|
||||
collectionId,
|
||||
}: {
|
||||
userId?: number;
|
||||
collectionId?: number;
|
||||
}) {
|
||||
if (userId) {
|
||||
// Remove empty tags
|
||||
await prisma.tag.deleteMany({
|
||||
where: {
|
||||
ownerId: userId,
|
||||
links: {
|
||||
none: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const tags = await prisma.tag.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ ownerId: userId }, // Tags owned by the user
|
||||
{
|
||||
links: {
|
||||
some: {
|
||||
collection: {
|
||||
members: {
|
||||
some: {
|
||||
userId, // Tags from collections where the user is a member
|
||||
const tags = await prisma.tag.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ ownerId: userId }, // Tags owned by the user
|
||||
{
|
||||
links: {
|
||||
some: {
|
||||
collection: {
|
||||
members: {
|
||||
some: {
|
||||
userId, // Tags from collections where the user is a member
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: { links: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
// orderBy: {
|
||||
// links: {
|
||||
// _count: "desc",
|
||||
// },
|
||||
// },
|
||||
});
|
||||
include: {
|
||||
_count: {
|
||||
select: { links: true },
|
||||
},
|
||||
},
|
||||
// orderBy: {
|
||||
// links: {
|
||||
// _count: "desc",
|
||||
// },
|
||||
// },
|
||||
});
|
||||
|
||||
return { response: tags, status: 200 };
|
||||
return { response: tags, status: 200 };
|
||||
} else if (collectionId) {
|
||||
const tags = await prisma.tag.findMany({
|
||||
where: {
|
||||
links: {
|
||||
some: {
|
||||
collection: {
|
||||
id: collectionId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return { response: tags, status: 200 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import bcrypt from "bcrypt";
|
||||
import isServerAdmin from "../../isServerAdmin";
|
||||
import { PostUserSchema } from "@/lib/shared/schemaValidation";
|
||||
import isAuthenticatedRequest from "../../isAuthenticatedRequest";
|
||||
import { Subscription, User } from "@prisma/client";
|
||||
|
||||
const emailEnabled =
|
||||
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
||||
@@ -12,66 +14,59 @@ interface Data {
|
||||
status: number;
|
||||
}
|
||||
|
||||
interface User {
|
||||
name: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export default async function postUser(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): Promise<Data> {
|
||||
let isAdmin = await isServerAdmin({ req });
|
||||
const parentUser = await isAuthenticatedRequest({ req });
|
||||
const isAdmin =
|
||||
parentUser && parentUser.id === Number(process.env.NEXT_PUBLIC_ADMIN || 1);
|
||||
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === "true" && !isAdmin) {
|
||||
return { response: "Registration is disabled.", status: 400 };
|
||||
}
|
||||
|
||||
const body: User = req.body;
|
||||
const dataValidation = PostUserSchema().safeParse(req.body);
|
||||
|
||||
const checkHasEmptyFields = emailEnabled
|
||||
? !body.password || !body.name || !body.email
|
||||
: !body.username || !body.password || !body.name;
|
||||
if (!dataValidation.success) {
|
||||
return {
|
||||
response: `Error: ${
|
||||
dataValidation.error.issues[0].message
|
||||
} [${dataValidation.error.issues[0].path.join(", ")}]`,
|
||||
status: 400,
|
||||
};
|
||||
}
|
||||
|
||||
if (!body.password || body.password.length < 8)
|
||||
return { response: "Password must be at least 8 characters.", status: 400 };
|
||||
const { name, email, password, invite } = dataValidation.data;
|
||||
let { username } = dataValidation.data;
|
||||
|
||||
if (checkHasEmptyFields)
|
||||
return { response: "Please fill out all the fields.", status: 400 };
|
||||
|
||||
// Check email (if enabled)
|
||||
const checkEmail =
|
||||
/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
|
||||
if (emailEnabled && !checkEmail.test(body.email?.toLowerCase() || ""))
|
||||
return { response: "Please enter a valid email.", status: 400 };
|
||||
|
||||
// Check username (if email was disabled)
|
||||
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
|
||||
if (invite && (!stripeEnabled || !emailEnabled)) {
|
||||
return { response: "You are not authorized to invite users.", status: 401 };
|
||||
} else if (invite && !parentUser) {
|
||||
return { response: "You must be logged in to invite users.", status: 401 };
|
||||
}
|
||||
|
||||
const autoGeneratedUsername = "user" + Math.round(Math.random() * 1000000000);
|
||||
|
||||
if (body.username && !checkUsername.test(body.username?.toLowerCase()))
|
||||
if (!username) {
|
||||
username = autoGeneratedUsername;
|
||||
}
|
||||
|
||||
if (!emailEnabled && !password) {
|
||||
return {
|
||||
response:
|
||||
"Username has to be between 3-30 characters, no spaces and special characters are allowed.",
|
||||
response: "Password is required.",
|
||||
status: 400,
|
||||
};
|
||||
else if (!body.username) {
|
||||
body.username = autoGeneratedUsername;
|
||||
}
|
||||
|
||||
const checkIfUserExists = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
email: body.email ? body.email.toLowerCase().trim() : undefined,
|
||||
email: email ? email.toLowerCase().trim() : undefined,
|
||||
},
|
||||
{
|
||||
username: body.username
|
||||
? body.username.toLowerCase().trim()
|
||||
: undefined,
|
||||
username: username ? username.toLowerCase().trim() : undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -83,65 +78,57 @@ export default async function postUser(
|
||||
|
||||
const saltRounds = 10;
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(body.password, saltRounds);
|
||||
const hashedPassword = bcrypt.hashSync(password || "", saltRounds);
|
||||
|
||||
// Subscription dates
|
||||
const currentPeriodStart = new Date();
|
||||
const currentPeriodEnd = new Date();
|
||||
currentPeriodEnd.setFullYear(currentPeriodEnd.getFullYear() + 1000); // end date is in 1000 years...
|
||||
|
||||
if (isAdmin) {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
username: emailEnabled
|
||||
? (body.username as string).toLowerCase().trim() ||
|
||||
autoGeneratedUsername
|
||||
: (body.username as string).toLowerCase().trim(),
|
||||
email: emailEnabled ? body.email?.toLowerCase().trim() : undefined,
|
||||
password: hashedPassword,
|
||||
emailVerified: new Date(),
|
||||
subscriptions: stripeEnabled
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
name: name,
|
||||
username: emailEnabled ? username || autoGeneratedUsername : username,
|
||||
email: emailEnabled ? email : undefined,
|
||||
emailVerified: isAdmin ? new Date() : undefined,
|
||||
password: password ? hashedPassword : undefined,
|
||||
parentSubscription:
|
||||
parentUser && invite
|
||||
? {
|
||||
connect: {
|
||||
id: (parentUser.subscriptions as Subscription).id,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
subscriptions:
|
||||
stripeEnabled && isAdmin
|
||||
? {
|
||||
create: {
|
||||
stripeSubscriptionId:
|
||||
"fake_sub_" + Math.round(Math.random() * 10000000000000),
|
||||
active: true,
|
||||
currentPeriodStart,
|
||||
currentPeriodEnd,
|
||||
currentPeriodStart: new Date(),
|
||||
currentPeriodEnd: new Date(
|
||||
new Date().setFullYear(new Date().getFullYear() + 1000)
|
||||
), // 1000 years from now
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
emailVerified: true,
|
||||
subscriptions: {
|
||||
select: {
|
||||
active: true,
|
||||
},
|
||||
select: isAdmin
|
||||
? {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
emailVerified: true,
|
||||
password: true,
|
||||
subscriptions: {
|
||||
select: {
|
||||
active: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
createdAt: true,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return { response: user, status: 201 };
|
||||
} else {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
username: emailEnabled
|
||||
? autoGeneratedUsername
|
||||
: (body.username as string).toLowerCase().trim(),
|
||||
email: emailEnabled ? body.email?.toLowerCase().trim() : undefined,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
return { response: "User successfully created.", status: 201 };
|
||||
}
|
||||
const { password: pass, ...userWithoutPassword } = user as User;
|
||||
return { response: userWithoutPassword, status: 201 };
|
||||
} else {
|
||||
return { response: "Email or Username already exists.", status: 400 };
|
||||
}
|
||||
|
||||
@@ -4,15 +4,28 @@ import removeFolder from "@/lib/api/storage/removeFolder";
|
||||
import Stripe from "stripe";
|
||||
import { DeleteUserBody } from "@/types/global";
|
||||
import removeFile from "@/lib/api/storage/removeFile";
|
||||
import updateSeats from "@/lib/api/stripe/updateSeats";
|
||||
|
||||
export default async function deleteUserById(
|
||||
userId: number,
|
||||
body: DeleteUserBody,
|
||||
isServerAdmin?: boolean
|
||||
isServerAdmin: boolean,
|
||||
queryId: number
|
||||
) {
|
||||
// First, we retrieve the user from the database
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
subscriptions: {
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
parentSubscription: {
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -23,24 +36,70 @@ export default async function deleteUserById(
|
||||
}
|
||||
|
||||
if (!isServerAdmin) {
|
||||
if (user.password) {
|
||||
const isPasswordValid = bcrypt.compareSync(
|
||||
body.password,
|
||||
user.password as string
|
||||
);
|
||||
if (queryId === userId) {
|
||||
if (user.password) {
|
||||
const isPasswordValid = bcrypt.compareSync(
|
||||
body.password,
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!isPasswordValid && !isServerAdmin) {
|
||||
if (!isPasswordValid && !isServerAdmin) {
|
||||
return {
|
||||
response: "Invalid credentials.",
|
||||
status: 401,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
response: "Invalid credentials.",
|
||||
status: 401, // Unauthorized
|
||||
response:
|
||||
"User has no password. Please reset your password from the forgot password page.",
|
||||
status: 401,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
response:
|
||||
"User has no password. Please reset your password from the forgot password page.",
|
||||
status: 401, // Unauthorized
|
||||
};
|
||||
if (user.parentSubscriptionId) {
|
||||
return {
|
||||
response: "Permission denied.",
|
||||
status: 401,
|
||||
};
|
||||
} else {
|
||||
if (!user.subscriptions) {
|
||||
return {
|
||||
response: "User has no subscription.",
|
||||
status: 401,
|
||||
};
|
||||
}
|
||||
|
||||
const findChild = await prisma.user.findFirst({
|
||||
where: { id: queryId, parentSubscriptionId: user.subscriptions?.id },
|
||||
});
|
||||
|
||||
if (!findChild)
|
||||
return {
|
||||
response: "Permission denied.",
|
||||
status: 401,
|
||||
};
|
||||
|
||||
const removeUser = await prisma.user.update({
|
||||
where: { id: findChild.id },
|
||||
data: {
|
||||
parentSubscription: {
|
||||
disconnect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (removeUser.emailVerified)
|
||||
await updateSeats(
|
||||
user.subscriptions.stripeSubscriptionId,
|
||||
user.subscriptions.quantity - 1
|
||||
);
|
||||
|
||||
return {
|
||||
response: "Account removed from subscription.",
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,27 +109,27 @@ export default async function deleteUserById(
|
||||
async (prisma) => {
|
||||
// Delete Access Tokens
|
||||
await prisma.accessToken.deleteMany({
|
||||
where: { userId },
|
||||
where: { userId: queryId },
|
||||
});
|
||||
|
||||
// Delete whitelisted users
|
||||
await prisma.whitelistedUser.deleteMany({
|
||||
where: { userId },
|
||||
where: { userId: queryId },
|
||||
});
|
||||
|
||||
// Delete links
|
||||
await prisma.link.deleteMany({
|
||||
where: { collection: { ownerId: userId } },
|
||||
where: { collection: { ownerId: queryId } },
|
||||
});
|
||||
|
||||
// Delete tags
|
||||
await prisma.tag.deleteMany({
|
||||
where: { ownerId: userId },
|
||||
where: { ownerId: queryId },
|
||||
});
|
||||
|
||||
// Find collections that the user owns
|
||||
const collections = await prisma.collection.findMany({
|
||||
where: { ownerId: userId },
|
||||
where: { ownerId: queryId },
|
||||
});
|
||||
|
||||
for (const collection of collections) {
|
||||
@@ -89,29 +148,29 @@ export default async function deleteUserById(
|
||||
|
||||
// Delete collections after cleaning up related data
|
||||
await prisma.collection.deleteMany({
|
||||
where: { ownerId: userId },
|
||||
where: { ownerId: queryId },
|
||||
});
|
||||
|
||||
// Delete subscription
|
||||
if (process.env.STRIPE_SECRET_KEY)
|
||||
await prisma.subscription
|
||||
.delete({
|
||||
where: { userId },
|
||||
where: { userId: queryId },
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
|
||||
await prisma.usersAndCollections.deleteMany({
|
||||
where: {
|
||||
OR: [{ userId: userId }, { collection: { ownerId: userId } }],
|
||||
OR: [{ userId: queryId }, { collection: { ownerId: queryId } }],
|
||||
},
|
||||
});
|
||||
|
||||
// Delete user's avatar
|
||||
await removeFile({ filePath: `uploads/avatar/${userId}.jpg` });
|
||||
await removeFile({ filePath: `uploads/avatar/${queryId}.jpg` });
|
||||
|
||||
// Finally, delete the user
|
||||
await prisma.user.delete({
|
||||
where: { id: userId },
|
||||
where: { id: queryId },
|
||||
});
|
||||
},
|
||||
{ timeout: 20000 }
|
||||
@@ -124,24 +183,36 @@ export default async function deleteUserById(
|
||||
});
|
||||
|
||||
try {
|
||||
const listByEmail = await stripe.customers.list({
|
||||
email: user.email?.toLowerCase(),
|
||||
expand: ["data.subscriptions"],
|
||||
});
|
||||
if (user.subscriptions?.id) {
|
||||
const listByEmail = await stripe.customers.list({
|
||||
email: user.email?.toLowerCase(),
|
||||
expand: ["data.subscriptions"],
|
||||
});
|
||||
|
||||
if (listByEmail.data[0].subscriptions?.data[0].id) {
|
||||
const deleted = await stripe.subscriptions.cancel(
|
||||
listByEmail.data[0].subscriptions?.data[0].id,
|
||||
{
|
||||
cancellation_details: {
|
||||
comment: body.cancellation_details?.comment,
|
||||
feedback: body.cancellation_details?.feedback,
|
||||
},
|
||||
}
|
||||
if (listByEmail.data[0].subscriptions?.data[0].id) {
|
||||
const deleted = await stripe.subscriptions.cancel(
|
||||
listByEmail.data[0].subscriptions?.data[0].id,
|
||||
{
|
||||
cancellation_details: {
|
||||
comment: body.cancellation_details?.comment,
|
||||
feedback: body.cancellation_details?.feedback,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
response: deleted,
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
} else if (user.parentSubscription?.id && user && user.emailVerified) {
|
||||
await updateSeats(
|
||||
user.parentSubscription.stripeSubscriptionId,
|
||||
user.parentSubscription.quantity - 1
|
||||
);
|
||||
|
||||
return {
|
||||
response: deleted,
|
||||
response: "User account and all related data deleted successfully.",
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,6 +212,8 @@ export default async function updateUserById(
|
||||
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
||||
linksRouteTo: data.linksRouteTo,
|
||||
preventDuplicateLinks: data.preventDuplicateLinks,
|
||||
referredBy:
|
||||
!user?.referredBy && data.referredBy ? data.referredBy : undefined,
|
||||
password:
|
||||
data.newPassword && data.newPassword !== ""
|
||||
? newHashedPassword
|
||||
|
||||
@@ -39,7 +39,7 @@ const handleMonolith = async (link: Link, content: string) => {
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("Error running MONOLITH:", err);
|
||||
console.log("Uncaught Monolith error...");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import { ArchivedFormat, TokenExpiry } from "@/types/global";
|
||||
import { LinksRouteTo } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
// const stringField = z.string({
|
||||
// errorMap: (e) => ({
|
||||
// message: `Invalid ${e.path}.`,
|
||||
// }),
|
||||
// });
|
||||
|
||||
export const ForgotPasswordSchema = z.object({
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export const ResetPasswordSchema = z.object({
|
||||
token: z.string(),
|
||||
password: z.string().min(8),
|
||||
});
|
||||
|
||||
export const VerifyEmailSchema = z.object({
|
||||
token: z.string(),
|
||||
});
|
||||
|
||||
export const PostTokenSchema = z.object({
|
||||
name: z.string().max(50),
|
||||
expires: z.nativeEnum(TokenExpiry),
|
||||
});
|
||||
|
||||
export type PostTokenSchemaType = z.infer<typeof PostTokenSchema>;
|
||||
|
||||
export const PostUserSchema = () => {
|
||||
const emailEnabled =
|
||||
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
||||
|
||||
return z.object({
|
||||
name: z.string().trim().min(1).max(50).optional(),
|
||||
password: z.string().min(8).max(2048).optional(),
|
||||
email: emailEnabled
|
||||
? z.string().trim().email().toLowerCase()
|
||||
: z.string().optional(),
|
||||
username: emailEnabled
|
||||
? z.string().optional()
|
||||
: z
|
||||
.string()
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.min(3)
|
||||
.max(50)
|
||||
.regex(/^[a-z0-9_-]{3,50}$/),
|
||||
invite: z.boolean().optional(),
|
||||
});
|
||||
};
|
||||
|
||||
export const UpdateUserSchema = () => {
|
||||
const emailEnabled =
|
||||
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
||||
|
||||
return z.object({
|
||||
name: z.string().trim().min(1).max(50).optional(),
|
||||
email: emailEnabled
|
||||
? z.string().trim().email().toLowerCase()
|
||||
: z.string().optional(),
|
||||
username: z
|
||||
.string()
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.min(3)
|
||||
.max(30)
|
||||
.regex(/^[a-z0-9_-]{3,30}$/),
|
||||
image: z.string().nullish(),
|
||||
password: z.string().min(8).max(2048).optional(),
|
||||
newPassword: z.string().min(8).max(2048).optional(),
|
||||
oldPassword: z.string().min(8).max(2048).optional(),
|
||||
archiveAsScreenshot: z.boolean().optional(),
|
||||
archiveAsPDF: z.boolean().optional(),
|
||||
archiveAsMonolith: z.boolean().optional(),
|
||||
archiveAsWaybackMachine: z.boolean().optional(),
|
||||
locale: z.string().max(20).optional(),
|
||||
isPrivate: z.boolean().optional(),
|
||||
preventDuplicateLinks: z.boolean().optional(),
|
||||
collectionOrder: z.array(z.number()).optional(),
|
||||
linksRouteTo: z.nativeEnum(LinksRouteTo).optional(),
|
||||
whitelistedUsers: z.array(z.string().max(50)).optional(),
|
||||
referredBy: z.string().max(100).nullish(),
|
||||
});
|
||||
};
|
||||
|
||||
export const PostSessionSchema = z.object({
|
||||
username: z.string().min(3).max(50),
|
||||
password: z.string().min(8),
|
||||
sessionName: z.string().trim().max(50).optional(),
|
||||
});
|
||||
|
||||
export const PostLinkSchema = z.object({
|
||||
type: z.enum(["url", "pdf", "image"]),
|
||||
url: z.string().trim().max(2048).url().optional(),
|
||||
name: z.string().trim().max(2048).optional(),
|
||||
description: z.string().trim().max(2048).optional(),
|
||||
collection: z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
name: z.string().trim().max(2048).optional(),
|
||||
})
|
||||
.optional(),
|
||||
tags:
|
||||
z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
name: z.string().trim().max(50),
|
||||
})
|
||||
)
|
||||
.optional() || [],
|
||||
});
|
||||
|
||||
export type PostLinkSchemaType = z.infer<typeof PostLinkSchema>;
|
||||
|
||||
export const UpdateLinkSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string().trim().max(2048).optional(),
|
||||
url: z.string().trim().max(2048).optional(),
|
||||
description: z.string().trim().max(2048).optional(),
|
||||
icon: z.string().trim().max(50).nullish(),
|
||||
iconWeight: z.string().trim().max(50).nullish(),
|
||||
color: z.string().trim().max(10).nullish(),
|
||||
collection: z.object({
|
||||
id: z.number(),
|
||||
ownerId: z.number(),
|
||||
}),
|
||||
tags: z.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
name: z.string().trim().max(50),
|
||||
})
|
||||
),
|
||||
pinnedBy: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
})
|
||||
.optional()
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type UpdateLinkSchemaType = z.infer<typeof UpdateLinkSchema>;
|
||||
|
||||
const ACCEPTED_TYPES = [
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"application/pdf",
|
||||
];
|
||||
const NEXT_PUBLIC_MAX_FILE_BUFFER = Number(
|
||||
process.env.NEXT_PUBLIC_MAX_FILE_BUFFER || 10
|
||||
);
|
||||
const MAX_FILE_SIZE = NEXT_PUBLIC_MAX_FILE_BUFFER * 1024 * 1024;
|
||||
export const UploadFileSchema = z.object({
|
||||
file: z
|
||||
.any()
|
||||
.refine((files) => files?.length == 1, "File is required.")
|
||||
.refine(
|
||||
(files) => files?.[0]?.size <= MAX_FILE_SIZE,
|
||||
`Max file size is ${MAX_FILE_SIZE}MB.`
|
||||
)
|
||||
.refine(
|
||||
(files) => ACCEPTED_TYPES.includes(files?.[0]?.mimetype),
|
||||
`Only ${ACCEPTED_TYPES.join(", ")} files are accepted.`
|
||||
),
|
||||
id: z.number(),
|
||||
format: z.nativeEnum(ArchivedFormat),
|
||||
});
|
||||
|
||||
export const PostCollectionSchema = z.object({
|
||||
name: z.string().trim().max(2048),
|
||||
description: z.string().trim().max(2048).optional(),
|
||||
color: z.string().trim().max(10).optional(),
|
||||
icon: z.string().trim().max(50).optional(),
|
||||
iconWeight: z.string().trim().max(50).optional(),
|
||||
parentId: z.number().optional(),
|
||||
});
|
||||
|
||||
export type PostCollectionSchemaType = z.infer<typeof PostCollectionSchema>;
|
||||
|
||||
export const UpdateCollectionSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string().trim().max(2048),
|
||||
description: z.string().trim().max(2048).optional(),
|
||||
color: z.string().trim().max(10).optional(),
|
||||
isPublic: z.boolean().optional(),
|
||||
icon: z.string().trim().max(50).nullish(),
|
||||
iconWeight: z.string().trim().max(50).nullish(),
|
||||
parentId: z.union([z.number(), z.literal("root")]).nullish(),
|
||||
members: z.array(
|
||||
z.object({
|
||||
userId: z.number(),
|
||||
canCreate: z.boolean(),
|
||||
canUpdate: z.boolean(),
|
||||
canDelete: z.boolean(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type UpdateCollectionSchemaType = z.infer<typeof UpdateCollectionSchema>;
|
||||
|
||||
export const UpdateTagSchema = z.object({
|
||||
name: z.string().trim().max(50),
|
||||
});
|
||||
|
||||
export type UpdateTagSchemaType = z.infer<typeof UpdateTagSchema>;
|
||||
Reference in New Issue
Block a user