diff --git a/.gitignore b/.gitignore index e10e251..77956c8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,8 @@ next-env.d.ts # generated files and folders /data +.idea +prisma/dev.db # tests /tests diff --git a/lib/api/controllers/links/getLinks.ts b/lib/api/controllers/links/getLinks.ts index d5ed6df..22df9fb 100644 --- a/lib/api/controllers/links/getLinks.ts +++ b/lib/api/controllers/links/getLinks.ts @@ -66,7 +66,6 @@ export default async function getLink(userId: number, body: string) { query.searchQuery && query.searchFilter?.name ? query.searchQuery : undefined, - mode: "insensitive", }, }, { @@ -75,7 +74,6 @@ export default async function getLink(userId: number, body: string) { query.searchQuery && query.searchFilter?.url ? query.searchQuery : undefined, - mode: "insensitive", }, }, { @@ -84,7 +82,6 @@ export default async function getLink(userId: number, body: string) { query.searchQuery && query.searchFilter?.description ? query.searchQuery : undefined, - mode: "insensitive", }, }, { @@ -100,7 +97,6 @@ export default async function getLink(userId: number, body: string) { query.searchQuery && query.searchFilter?.tags ? { contains: query.searchQuery, - mode: "insensitive", } : undefined, OR: [ @@ -114,7 +110,6 @@ export default async function getLink(userId: number, body: string) { query.searchFilter?.tags ? query.searchQuery : undefined, - mode: "insensitive", }, collection: { members: { diff --git a/lib/api/controllers/links/postLink.ts b/lib/api/controllers/links/postLink.ts index 3a5bfbd..3653eb9 100644 --- a/lib/api/controllers/links/postLink.ts +++ b/lib/api/controllers/links/postLink.ts @@ -20,12 +20,15 @@ export default async function postLink( }; } - link.collection.name = link.collection.name.trim(); - + // This has to move above we assign link.collection.name + // Because if the link is null (write then delete text on collection) + // It will try to do trim on empty string and will throw and error, this prevents it. if (!link.collection.name) { link.collection.name = "Unnamed Collection"; } + link.collection.name = link.collection.name.trim(); + if (link.collection.id) { const collectionIsAccessible = (await getPermission( userId, diff --git a/lib/api/controllers/users/getUsers.ts b/lib/api/controllers/users/getUsers.ts index 381a713..9d752a6 100644 --- a/lib/api/controllers/users/getUsers.ts +++ b/lib/api/controllers/users/getUsers.ts @@ -17,14 +17,23 @@ export default async function getUser({ id: params.lookupId, username: params.lookupUsername?.toLowerCase(), }, + include: { + whitelistedUsers: { + select: { + username: true + } + } + } }); if (!user) return { response: "User not found.", status: 404 }; + const whitelistedUsernames = user.whitelistedUsers?.map(usernames => usernames.username); + if ( !isSelf && user?.isPrivate && - !user.whitelistedUsers.includes(username.toLowerCase()) + !whitelistedUsernames.includes(username.toLowerCase()) ) { return { response: "This profile is private.", status: 401 }; } @@ -33,7 +42,7 @@ export default async function getUser({ const data = isSelf ? // If user is requesting its own data - lessSensitiveInfo + {...lessSensitiveInfo, whitelistedUsers: whitelistedUsernames} : { // If user is requesting someone elses data id: lessSensitiveInfo.id, diff --git a/lib/api/controllers/users/updateUser.ts b/lib/api/controllers/users/updateUser.ts index 1f41fb5..91674ba 100644 --- a/lib/api/controllers/users/updateUser.ts +++ b/lib/api/controllers/users/updateUser.ts @@ -106,14 +106,56 @@ export default async function updateUser( username: user.username.toLowerCase(), email: user.email?.toLowerCase(), isPrivate: user.isPrivate, - whitelistedUsers: user.whitelistedUsers, password: user.newPassword && user.newPassword !== "" ? newHashedPassword : undefined, }, + include: { + whitelistedUsers: true + } }); + + const { whitelistedUsers, password, ...userInfo } = updatedUser; + + // If user.whitelistedUsers is not provided, we will assume the whitelistedUsers should be removed + const newWhitelistedUsernames: string[] = user.whitelistedUsers || []; + + // Get the current whitelisted usernames + const currentWhitelistedUsernames: string[] = whitelistedUsers.map((user) => user.username); + + // Find the usernames to be deleted (present in current but not in new) + const usernamesToDelete: string[] = currentWhitelistedUsernames.filter( + (username) => !newWhitelistedUsernames.includes(username) + ); + + // Find the usernames to be created (present in new but not in current) + const usernamesToCreate: string[] = newWhitelistedUsernames.filter( + (username) => !currentWhitelistedUsernames.includes(username) && username.trim() !== '' + ); + + // Delete whitelistedUsers that are not present in the new list + await prisma.whitelistedUser.deleteMany({ + where: { + userId: sessionUser.id, + 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, + userId: sessionUser.id, + }, + }); + } + + const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY; const PRICE_ID = process.env.PRICE_ID; @@ -125,10 +167,9 @@ export default async function updateUser( user.email as string ); - const { password, ...userInfo } = updatedUser; - const response: Omit = { ...userInfo, + whitelistedUsers: newWhitelistedUsernames, profilePic: `/api/avatar/${userInfo.id}?${Date.now()}`, }; diff --git a/pages/api/avatar/[id].ts b/pages/api/avatar/[id].ts index 73b155b..ac4f1d3 100644 --- a/pages/api/avatar/[id].ts +++ b/pages/api/avatar/[id].ts @@ -33,11 +33,16 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { where: { id: queryId, }, + include: { + whitelistedUsers: true + } }); + const whitelistedUsernames = targetUser?.whitelistedUsers.map(whitelistedUsername => whitelistedUsername.username); + if ( targetUser?.isPrivate && - !targetUser.whitelistedUsers.includes(username) + !whitelistedUsernames?.includes(username) ) { return res .setHeader("Content-Type", "text/plain") diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 43d8b90..da10e49 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -4,22 +4,22 @@ generator client { datasource db { provider = "postgresql" - url = env("DATABASE_URL") + url = env("DATABASE_URL") } model Account { - id String @id @default(cuid()) - userId Int - type String - provider String - providerAccountId String - refresh_token String? @db.Text - access_token String? @db.Text - expires_at Int? - token_type String? - scope String? - id_token String? @db.Text - session_state String? + id String @id @default(cuid()) + userId Int + type String + provider String + providerAccountId String + refresh_token String? + access_token String? + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -35,29 +35,37 @@ model Session { } model User { - id Int @id @default(autoincrement()) - name String + id Int @id @default(autoincrement()) + name String - username String? @unique + username String? @unique - email String? @unique - emailVerified DateTime? - image String? + email String? @unique + emailVerified DateTime? + image String? - accounts Account[] - sessions Session[] - - password String - collections Collection[] + accounts Account[] + sessions Session[] - tags Tag[] + password String + collections Collection[] - pinnedLinks Link[] - - collectionsJoined UsersAndCollections[] - isPrivate Boolean @default(false) - whitelistedUsers String[] @default([]) - createdAt DateTime @default(now()) + tags Tag[] + + pinnedLinks Link[] + + collectionsJoined UsersAndCollections[] + isPrivate Boolean @default(false) + whitelistedUsers whitelistedUser[] + createdAt DateTime @default(now()) +} + +model whitelistedUser { + id Int @id @default(autoincrement()) + + username String @default("") + User User? @relation(fields: [userId], references: [id]) + userId Int? } model VerificationToken { @@ -69,27 +77,26 @@ model VerificationToken { } model Collection { - id Int @id @default(autoincrement()) - name String - description String @default("") - color String @default("#0ea5e9") - isPublic Boolean @default(false) + id Int @id @default(autoincrement()) + name String + description String @default("") + color String @default("#0ea5e9") + isPublic Boolean @default(false) - - owner User @relation(fields: [ownerId], references: [id]) - ownerId Int - members UsersAndCollections[] - links Link[] - createdAt DateTime @default(now()) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + members UsersAndCollections[] + links Link[] + createdAt DateTime @default(now()) @@unique([name, ownerId]) } model UsersAndCollections { - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id]) userId Int - collection Collection @relation(fields: [collectionId], references: [id]) + collection Collection @relation(fields: [collectionId], references: [id]) collectionId Int canCreate Boolean @@ -100,24 +107,24 @@ model UsersAndCollections { } model Link { - id Int @id @default(autoincrement()) - name String - url String + id Int @id @default(autoincrement()) + name String + url String description String @default("") pinnedBy User[] - collection Collection @relation(fields: [collectionId], references: [id]) + collection Collection @relation(fields: [collectionId], references: [id]) collectionId Int - tags Tag[] - createdAt DateTime @default(now()) + tags Tag[] + createdAt DateTime @default(now()) } model Tag { - id Int @id @default(autoincrement()) - name String - links Link[] - owner User @relation(fields: [ownerId], references: [id]) + id Int @id @default(autoincrement()) + name String + links Link[] + owner User @relation(fields: [ownerId], references: [id]) ownerId Int @@unique([name, ownerId]) diff --git a/types/global.ts b/types/global.ts index 95b2640..04b68e0 100644 --- a/types/global.ts +++ b/types/global.ts @@ -36,6 +36,7 @@ export interface CollectionIncludingMembersAndLinkCount export interface AccountSettings extends User { profilePic: string; newPassword?: string; + whitelistedUsers: string[] } interface LinksIncludingTags extends Link {