Skip to content

Conversation

brymut
Copy link
Contributor

@brymut brymut commented Sep 8, 2025

Description

fix #2480

Adding ability for users to manually order their bookmarks. Order stored in DB, using DND provider from wallets.

Screen.Recording.2025-09-08.at.20.41.09.mov

Screenshots

Screenshot 2025-09-08 at 20 44 29 Screenshot 2025-09-08 at 20 44 50 Screenshot 2025-09-08 at 20 45 16 Screenshot 2025-09-08 at 20 45 30

Additional Context

Was anything unclear during your work on this PR? Anything we should definitely take a closer look at?

Checklist

Are your changes backward compatible? Please answer below:
Y
For example, a change is not backward compatible if you removed a GraphQL field or dropped a database column.

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:
9

For frontend changes: Tested on mobile, light and dark mode? Please answer below:
Y

Did you introduce any new environment variables? If so, call them out explicitly here:
N

@brymut brymut force-pushed the custom-bookmark-reorder branch 5 times, most recently from af21dad to 45b255d Compare September 8, 2025 17:43
@brymut brymut marked this pull request as ready for review September 8, 2025 17:48
@brymut brymut force-pushed the custom-bookmark-reorder branch 2 times, most recently from dbdbe65 to af658c8 Compare September 8, 2025 18:16
Comment on lines 789 to 800
for (let i = 0; i < itemIds.length; i++) {
const itemId = Number(itemIds[i])
await models.bookmark.updateMany({
where: {
userId: me.id,
itemId
},
data: {
customOrder: i + 1
}
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will run itemIds.length update queries one by one, in sequential order. It could be a potential bottleneck with a large number of bookmarks. For a small number, it may be ok.

If you want to use the updateMany, but run the queries in parallel, one option could be

Suggested change
for (let i = 0; i < itemIds.length; i++) {
const itemId = Number(itemIds[i])
await models.bookmark.updateMany({
where: {
userId: me.id,
itemId
},
data: {
customOrder: i + 1
}
})
}
await Promise.all(
itemIds.map((itemId, i) =>
models.bookmark.updateMany({
where: { userId: me.id, itemId: Number(itemId) },
data: { customOrder: i + 1 },
})
)
)

However, this still will fire itemIds.length queries (but in parallel instead of sequentially)

A better option could be dynamically building a SQL query with something like this

Suggested change
for (let i = 0; i < itemIds.length; i++) {
const itemId = Number(itemIds[i])
await models.bookmark.updateMany({
where: {
userId: me.id,
itemId
},
data: {
customOrder: i + 1
}
})
}
const caseStatements = itemIds
.map((id, i) => `WHEN ${id} THEN ${i + 1}`)
.join(' ');
await prisma.$executeRawUnsafe(`
UPDATE "Bookmark"
SET "customOrder" = (CASE "itemId"
${caseStatements}
END)
WHERE "userId" = ${me.id}
AND "itemId" IN (${itemIds.join(',')})
`);

Something like this should update all bookmarks in one sql statement.

I hope it helps!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting solution, hadn't considered this a bottleneck due to the practicality of re-ordering large lists of bookmarks frequently. However, for the custom SQL solution, I think @huumn mentioned avoiding using prisma.$executeRawUnsafe

Copy link
Member

@ekzyis ekzyis Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we definitely do not want to use $executeRawUnsafe.

You should be able to use Prisma client's in operator.

Oh, I see, you are not updating all items with the same value so it's more complicated than just using the in operator.

Still, there should be a way to do this in one query without executing unsafe SQL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a look

Copy link

@gndelia gndelia Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekzyis Sorry, I suggested using executeRawUnsafe because I'm not that familiar with Prisma and I saw queryRawUnsafe was being used already, but I Totally get the point of not using it 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't quite come up with a solution that doesn't bypass the client api, but atomic now & modestly faster due to less trips. But still N updates. Hope this is sufficient for use-case @ekzyis / @gndelia


const { items, cursor } = useMemo(() => {
if (!dat) return { items: [], cursor: null }
return dat?.items || { items: [], cursor: null }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor comment, but dat is always defined as it's checked above, so this ternary operator can be removed

Suggested change
return dat?.items || { items: [], cursor: null }
return dat.items || { items: [], cursor: null }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option could just be constructing the useMemo like this:

const { items, cursor } = useMemo(() => dat?.items ?? { items: [], cursor: null }, [dat])

removing the redundant check and keeping the optional chaining

@brymut brymut force-pushed the custom-bookmark-reorder branch from af658c8 to 78d2114 Compare September 16, 2025 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request] Reorder bookmarks
3 participants