el.xwx.moe/lib/api/controllers/users/userId/updateUserById.ts

294 lines
7.7 KiB
TypeScript
Raw Normal View History

2023-05-20 14:25:00 -05:00
import { prisma } from "@/lib/api/db";
import { AccountSettings } from "@/types/global";
import bcrypt from "bcrypt";
import removeFile from "@/lib/api/storage/removeFile";
import createFile from "@/lib/api/storage/createFile";
2023-08-03 16:54:04 -05:00
import createFolder from "@/lib/api/storage/createFolder";
2024-05-16 14:02:22 -05:00
import sendChangeEmailVerificationRequest from "@/lib/api/sendChangeEmailVerificationRequest";
import { i18n } from "next-i18next.config";
2024-09-17 13:03:05 -05:00
import { UpdateUserSchema } from "@/lib/shared/schemaValidation";
2023-08-03 16:54:04 -05:00
const emailEnabled =
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
2023-05-20 14:25:00 -05:00
2023-10-23 14:24:22 -05:00
export default async function updateUserById(
2023-11-02 13:59:31 -05:00
userId: number,
2024-09-17 13:03:05 -05:00
body: AccountSettings
) {
2024-09-17 13:03:05 -05:00
const dataValidation = UpdateUserSchema().safeParse(body);
2024-05-16 14:02:22 -05:00
2024-09-17 13:03:05 -05:00
if (!dataValidation.success) {
return {
2024-09-17 13:03:05 -05:00
response: `Error: ${
dataValidation.error.issues[0].message
} [${dataValidation.error.issues[0].path.join(", ")}]`,
status: 400,
};
2024-09-17 13:03:05 -05:00
}
2024-09-17 13:03:05 -05:00
const data = dataValidation.data;
const userIsTaken = await prisma.user.findFirst({
2023-12-03 14:01:28 -06:00
where: {
id: { not: userId },
OR: emailEnabled
? [
{
username: data.username.toLowerCase(),
},
{
email: data.email?.toLowerCase(),
},
]
: [
{
username: data.username.toLowerCase(),
},
],
2023-12-03 14:01:28 -06:00
},
});
2023-12-30 08:31:53 -06:00
if (userIsTaken) {
if (data.email?.toLowerCase().trim() === userIsTaken.email?.trim())
2023-12-03 14:01:28 -06:00
return {
response: "Email is taken.",
2023-12-03 14:01:28 -06:00
status: 400,
};
else if (
data.username?.toLowerCase().trim() === userIsTaken.username?.trim()
)
2023-12-03 14:01:28 -06:00
return {
response: "Username is taken.",
2023-12-03 14:01:28 -06:00
status: 400,
};
return {
response: "Username/Email is taken.",
status: 400,
};
}
2023-12-03 14:01:28 -06:00
// Avatar Settings
2023-12-03 14:01:28 -06:00
if (
data.image?.startsWith("data:image/jpeg;base64") &&
data.image.length < 1572864
) {
try {
const base64Data = data.image.replace(/^data:image\/jpeg;base64,/, "");
2023-07-18 11:44:37 -05:00
createFolder({ filePath: `uploads/avatar` });
2023-08-03 16:54:04 -05:00
await createFile({
filePath: `uploads/avatar/${userId}.jpg`,
data: base64Data,
isBase64: true,
});
} catch (err) {
console.log("Error saving image:", err);
2023-12-03 14:01:28 -06:00
}
} else if (data.image?.length && data.image?.length >= 1572864) {
console.log("A file larger than 1.5MB was uploaded.");
return {
response: "A file larger than 1.5MB was uploaded.",
status: 400,
};
} else if (data.image == "") {
removeFile({ filePath: `uploads/avatar/${userId}.jpg` });
2023-05-22 07:20:48 -05:00
}
2023-05-20 14:25:00 -05:00
2024-05-16 14:02:22 -05:00
// Email Settings
const user = await prisma.user.findUnique({
where: { id: userId },
});
if (user && user.email && data.email && data.email !== user.email) {
if (!data.password) {
2023-12-03 14:01:28 -06:00
return {
2024-05-16 14:02:22 -05:00
response: "Invalid password.",
2023-12-03 14:01:28 -06:00
status: 400,
};
}
2024-05-16 14:02:22 -05:00
// Verify password
if (!user.password) {
2023-12-03 14:01:28 -06:00
return {
response:
"User has no password. Please reset your password from the forgot password page.",
2023-12-03 14:01:28 -06:00
status: 400,
};
}
2023-11-02 13:59:31 -05:00
2024-05-16 14:02:22 -05:00
const passwordMatch = bcrypt.compareSync(data.password, user.password);
if (!passwordMatch) {
2023-12-03 14:01:28 -06:00
return {
2024-05-16 14:02:22 -05:00
response: "Password is incorrect.",
2023-12-03 14:01:28 -06:00
status: 400,
};
}
2024-05-16 14:02:22 -05:00
2024-05-16 14:50:43 -05:00
sendChangeEmailVerificationRequest(
user.email,
data.email,
data.name?.trim() || user.name || "Linkwarden User"
2024-05-16 14:50:43 -05:00
);
2024-05-16 14:02:22 -05:00
}
2024-05-21 06:08:08 -05:00
// Password Settings
if (data.newPassword || data.oldPassword) {
if (!data.oldPassword || !data.newPassword)
2023-12-03 14:01:28 -06:00
return {
2024-05-21 06:08:08 -05:00
response: "Please fill out all the fields.",
2023-12-03 14:01:28 -06:00
status: 400,
};
2024-05-21 06:08:08 -05:00
else if (!user?.password)
2023-12-03 14:01:28 -06:00
return {
2024-05-21 06:08:08 -05:00
response:
"User has no password. Please reset your password from the forgot password page.",
2023-12-03 14:01:28 -06:00
status: 400,
};
2024-05-21 06:08:08 -05:00
else if (!bcrypt.compareSync(data.oldPassword, user.password))
2023-12-03 14:01:28 -06:00
return {
2024-05-21 06:08:08 -05:00
response: "Old password is incorrect.",
2023-12-03 14:01:28 -06:00
status: 400,
};
2024-05-21 06:08:08 -05:00
else if (data.newPassword?.length < 8)
2023-12-03 14:01:28 -06:00
return {
2024-05-21 06:08:08 -05:00
response: "Password must be at least 8 characters.",
2023-12-03 14:01:28 -06:00
status: 400,
};
2024-05-21 06:08:08 -05:00
else if (data.newPassword === data.oldPassword)
2023-10-23 00:45:31 -05:00
return {
2024-05-21 06:08:08 -05:00
response: "New password must be different from the old password.",
2023-10-23 00:45:31 -05:00
status: 400,
};
2023-05-22 07:20:48 -05:00
}
2023-05-20 14:25:00 -05:00
2024-05-16 14:02:22 -05:00
// Other settings / Apply changes
2024-10-26 08:42:21 -05:00
const isInvited =
user?.name === null && user.parentSubscriptionId && !user.password;
if (isInvited && data.password === "")
return {
response: "Password is required.",
status: 400,
};
2023-07-12 13:26:34 -05:00
const saltRounds = 10;
2024-10-26 08:42:21 -05:00
const newHashedPassword = bcrypt.hashSync(
data.newPassword || data.password || "",
saltRounds
);
2023-07-12 13:26:34 -05:00
2023-05-20 14:25:00 -05:00
const updatedUser = await prisma.user.update({
where: {
2023-11-02 13:59:31 -05:00
id: userId,
2023-05-20 14:25:00 -05:00
},
data: {
2024-09-17 13:03:05 -05:00
name: data.name,
username: data.username,
isPrivate: data.isPrivate,
image:
data.image && data.image.startsWith("http")
? data.image
: data.image
? `uploads/avatar/${userId}.jpg`
: "",
2024-09-17 13:03:05 -05:00
collectionOrder: data.collectionOrder?.filter(
(value, index, self) => self.indexOf(value) === index
),
2024-09-17 13:03:05 -05:00
locale: i18n.locales.includes(data.locale || "") ? data.locale : "en",
archiveAsScreenshot: data.archiveAsScreenshot,
2024-06-27 20:58:07 -05:00
archiveAsMonolith: data.archiveAsMonolith,
archiveAsPDF: data.archiveAsPDF,
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
linksRouteTo: data.linksRouteTo,
2024-03-05 08:03:04 -06:00
preventDuplicateLinks: data.preventDuplicateLinks,
2024-11-07 10:09:36 -06:00
referredBy:
!user?.referredBy && data.referredBy ? data.referredBy : undefined,
2023-07-12 13:26:34 -05:00
password:
2024-10-26 08:42:21 -05:00
isInvited || (data.newPassword && data.newPassword !== "")
2023-07-12 13:26:34 -05:00
? newHashedPassword
: undefined,
2023-05-20 14:25:00 -05:00
},
include: {
2023-08-04 19:32:13 -05:00
whitelistedUsers: true,
subscriptions: true,
2024-10-26 08:42:21 -05:00
parentSubscription: {
include: {
user: true,
},
},
2023-08-04 19:32:13 -05:00
},
2023-05-20 14:25:00 -05:00
});
2024-10-26 08:42:21 -05:00
const {
whitelistedUsers,
password,
subscriptions,
parentSubscription,
...userInfo
} = updatedUser;
// If user.whitelistedUsers is not provided, we will assume the whitelistedUsers should be removed
const newWhitelistedUsernames: string[] = data.whitelistedUsers || [];
// Get the current whitelisted usernames
2023-08-04 19:32:13 -05:00
const currentWhitelistedUsernames: string[] = whitelistedUsers.map(
(data) => data.username
2023-08-04 19:32:13 -05:00
);
// Find the usernames to be deleted (present in current but not in new)
const usernamesToDelete: string[] = currentWhitelistedUsernames.filter(
2023-08-04 19:32:13 -05:00
(username) => !newWhitelistedUsernames.includes(username)
);
// Find the usernames to be created (present in new but not in current)
const usernamesToCreate: string[] = newWhitelistedUsernames.filter(
2023-08-04 19:32:13 -05:00
(username) =>
!currentWhitelistedUsernames.includes(username) && username.trim() !== ""
);
// Delete whitelistedUsers that are not present in the new list
await prisma.whitelistedUser.deleteMany({
where: {
2023-11-02 13:59:31 -05:00
userId: userId,
username: {
in: usernamesToDelete,
},
},
});
// Create new whitelistedUsers that are not in the current list, no create many ;(
for (const username of usernamesToCreate) {
await prisma.whitelistedUser.create({
data: {
username,
2023-11-02 13:59:31 -05:00
userId: userId,
},
});
}
2024-10-26 08:42:21 -05:00
const response = {
2023-06-08 08:39:22 -05:00
...userInfo,
whitelistedUsers: newWhitelistedUsernames,
image: userInfo.image ? `${userInfo.image}?${Date.now()}` : "",
2024-10-26 08:42:21 -05:00
subscription: {
active: subscriptions?.active,
2024-10-29 17:08:47 -05:00
quantity: subscriptions?.quantity,
2024-10-26 08:42:21 -05:00
},
parentSubscription: {
active: parentSubscription?.active,
user: {
email: parentSubscription?.user.email,
},
},
2023-06-08 08:39:22 -05:00
};
return { response, status: 200 };
2023-05-20 14:25:00 -05:00
}