Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
06d854c
generic cache. local file in dev, redis in prod
fatfingers23 Feb 1, 2026
1b5dfbe
Ideally should be the logic to read/set likes for npmx
fatfingers23 Feb 1, 2026
4dc224c
made the get cache actually work
fatfingers23 Feb 2, 2026
81548b7
Should be proper read of likes on ui
fatfingers23 Feb 2, 2026
20a521b
I think that's liking
fatfingers23 Feb 2, 2026
d209cd1
wip
fatfingers23 Feb 2, 2026
d6f58dd
scope upgrade works
fatfingers23 Feb 2, 2026
b39dc33
feels so close
fatfingers23 Feb 2, 2026
703935b
rebase?
fatfingers23 Feb 2, 2026
798dc71
Hopefully resolves the tests?
fatfingers23 Feb 2, 2026
94b6cb3
Some small changes
fatfingers23 Feb 2, 2026
15a2d7a
missed the server side one
fatfingers23 Feb 2, 2026
21a99a4
uses valibot to check body schema
fatfingers23 Feb 2, 2026
2ee102b
moved useComposables
fatfingers23 Feb 2, 2026
135ca56
moving the api around
fatfingers23 Feb 2, 2026
6045cb7
Divided up the cache and changed keys a bit
fatfingers23 Feb 2, 2026
e401ab5
I think this every thing expect to todos and more testing
fatfingers23 Feb 3, 2026
f6ed29d
tweaked some of the cache logic
fatfingers23 Feb 3, 2026
f913b30
Moved a type to be shared for api endpoints
fatfingers23 Feb 3, 2026
449aed7
handled some more PR feedback
fatfingers23 Feb 3, 2026
40488fd
robot fixes
fatfingers23 Feb 3, 2026
988c5c7
client only
fatfingers23 Feb 3, 2026
2ba4075
once more with feeling
fatfingers23 Feb 3, 2026
0f5887c
chore: rename
danielroe Feb 3, 2026
b8792f9
refactor: unify atproto caches
danielroe Feb 3, 2026
181d044
chore: make shared composable
danielroe Feb 3, 2026
4c70696
chore: use server alias
danielroe Feb 3, 2026
f42a24d
fix: add request lock
danielroe Feb 3, 2026
60211db
rebase cleanup
fatfingers23 Feb 3, 2026
d01eddb
fix: update generic cache
danielroe Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/components/Header/AccountMenu.client.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { useAtproto } from '~/composables/atproto/useAtproto'
import { useModal } from '~/composables/useModal'

const {
Expand Down
27 changes: 6 additions & 21 deletions app/components/Header/AuthModal.client.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
<script setup lang="ts">
import { useAtproto } from '~/composables/atproto/useAtproto'
import { authRedirect } from '~/utils/atproto/helpers'

const handleInput = shallowRef('')

const { user, logout } = useAtproto()

async function handleBlueskySignIn() {
await navigateTo(
{
path: '/api/auth/atproto',
query: { handle: 'https://bsky.social' },
},
{ external: true },
)
await authRedirect('https://bsky.social')
}

async function handleCreateAccount() {
await navigateTo(
{
path: '/api/auth/atproto',
query: { handle: 'https://npmx.social', create: 'true' },
},
{ external: true },
)
await authRedirect('https://npmx.social', true)
}

async function handleLogin() {
if (handleInput.value) {
await navigateTo(
{
path: '/api/auth/atproto',
query: { handle: handleInput.value },
},
{ external: true },
)
await authRedirect(handleInput.value)
}
}
</script>
Expand Down
1 change: 1 addition & 0 deletions app/components/Header/MobileMenu.client.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { useAtproto } from '~/composables/atproto/useAtproto'

const isOpen = defineModel<boolean>('open', { default: false })

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function useAtproto() {
export const useAtproto = createSharedComposable(function useAtproto() {
const {
data: user,
pending,
Expand All @@ -17,4 +17,4 @@ export function useAtproto() {
}

return { user, pending, logout }
}
})
72 changes: 72 additions & 0 deletions app/pages/package/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { areUrlsEquivalent } from '#shared/utils/url'
import { isEditableElement } from '~/utils/input'
import { formatBytes } from '~/utils/formatters'
import { NuxtLink } from '#components'
import { useModal } from '~/composables/useModal'
import { useAtproto } from '~/composables/atproto/useAtproto'
import { togglePackageLike } from '~/utils/atproto/likes'

definePageMeta({
name: 'package',
Expand Down Expand Up @@ -356,6 +359,54 @@ const canonicalUrl = computed(() => {
return requestedVersion.value ? `${base}/v/${requestedVersion.value}` : base
})

//atproto
// TODO: Maybe set this where it's not loaded here every load?
const { user } = useAtproto()

const authModal = useModal('auth-modal')

const { data: likesData } = useFetch(() => `/api/social/likes/${packageName.value}`, {
default: () => ({ totalLikes: 0, userHasLiked: false }),
server: false,
})

const isLikeActionPending = ref(false)

const likeAction = async () => {
if (user.value?.handle == null) {
authModal.open()
return
}

if (isLikeActionPending.value) return

const currentlyLiked = likesData.value?.userHasLiked ?? false
const currentLikes = likesData.value?.totalLikes ?? 0

// Optimistic update
likesData.value = {
totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
userHasLiked: !currentlyLiked,
Comment on lines +386 to +389
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clamp optimistic counts to avoid negatives.

If currentLikes is stale (e.g., 0 while userHasLiked is true), the optimistic decrement can go negative.

🧮 Suggested clamp
-  likesData.value = {
-    totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
-    userHasLiked: !currentlyLiked,
-  }
+  const nextLikes = currentlyLiked ? Math.max(0, currentLikes - 1) : currentLikes + 1
+  likesData.value = {
+    totalLikes: nextLikes,
+    userHasLiked: !currentlyLiked,
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Optimistic update
likesData.value = {
totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
userHasLiked: !currentlyLiked,
// Optimistic update
const nextLikes = currentlyLiked ? Math.max(0, currentLikes - 1) : currentLikes + 1
likesData.value = {
totalLikes: nextLikes,
userHasLiked: !currentlyLiked,
}

}

isLikeActionPending.value = true

const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)

isLikeActionPending.value = false

if (result.success) {
// Update with server response
likesData.value = result.data
} else {
// Revert on error
likesData.value = {
totalLikes: currentLikes,
userHasLiked: currentlyLiked,
}
}
Comment on lines +375 to +407
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure pending state resets even when the request throws.

If togglePackageLike rejects, isLikeActionPending stays true and the optimistic count never reverts. Wrap the call in try/catch/finally.

🛡️ Proposed fix
   // Optimistic update
   likesData.value = {
     totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
     userHasLiked: !currentlyLiked,
   }

   isLikeActionPending.value = true

-  const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)
-
-  isLikeActionPending.value = false
-
-  if (result.success) {
-    // Update with server response
-    likesData.value = result.data
-  } else {
-    // Revert on error
-    likesData.value = {
-      totalLikes: currentLikes,
-      userHasLiked: currentlyLiked,
-    }
-  }
+  try {
+    const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)
+    if (result.success) {
+      // Update with server response
+      likesData.value = result.data
+    } else {
+      // Revert on error
+      likesData.value = {
+        totalLikes: currentLikes,
+        userHasLiked: currentlyLiked,
+      }
+    }
+  } catch {
+    likesData.value = {
+      totalLikes: currentLikes,
+      userHasLiked: currentlyLiked,
+    }
+  } finally {
+    isLikeActionPending.value = false
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const likeAction = async () => {
if (user.value?.handle == null) {
authModal.open()
return
}
if (isLikeActionPending.value) return
const currentlyLiked = likesData.value?.userHasLiked ?? false
const currentLikes = likesData.value?.totalLikes ?? 0
// Optimistic update
likesData.value = {
totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
userHasLiked: !currentlyLiked,
}
isLikeActionPending.value = true
const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)
isLikeActionPending.value = false
if (result.success) {
// Update with server response
likesData.value = result.data
} else {
// Revert on error
likesData.value = {
totalLikes: currentLikes,
userHasLiked: currentlyLiked,
}
}
const likeAction = async () => {
if (user.value?.handle == null) {
authModal.open()
return
}
if (isLikeActionPending.value) return
const currentlyLiked = likesData.value?.userHasLiked ?? false
const currentLikes = likesData.value?.totalLikes ?? 0
// Optimistic update
likesData.value = {
totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
userHasLiked: !currentlyLiked,
}
isLikeActionPending.value = true
try {
const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)
if (result.success) {
// Update with server response
likesData.value = result.data
} else {
// Revert on error
likesData.value = {
totalLikes: currentLikes,
userHasLiked: currentlyLiked,
}
}
} catch {
likesData.value = {
totalLikes: currentLikes,
userHasLiked: currentlyLiked,
}
} finally {
isLikeActionPending.value = false
}
}

}

useHead({
link: [{ rel: 'canonical', href: canonicalUrl }],
})
Expand Down Expand Up @@ -497,10 +548,31 @@ defineOgImageComponent('Package', {
:is-binary="isBinaryOnly"
class="self-baseline ms-1 sm:ms-2"
/>

<!-- Package likes -->
<button
@click="likeAction"
type="button"
class="inline-flex items-center gap-1.5 font-mono text-sm text-fg hover:text-fg-muted transition-colors duration-200"
:title="$t('package.links.like')"
>
<span
:class="
likesData?.userHasLiked
? 'i-lucide-heart-minus text-red-500'
: 'i-lucide-heart-plus'
"
class="w-4 h-4"
aria-hidden="true"
/>
<span>{{ formatCompactNumber(likesData?.totalLikes ?? 0, { decimals: 1 }) }}</span>
</button>

<template #fallback>
<div class="flex items-center gap-1.5 self-baseline ms-1 sm:ms-2">
<SkeletonBlock class="w-8 h-5 rounded" />
<SkeletonBlock class="w-12 h-5 rounded" />
<SkeletonBlock class="w-5 h-5 rounded" />
</div>
</template>
</ClientOnly>
Expand Down
30 changes: 30 additions & 0 deletions app/utils/atproto/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { FetchError } from 'ofetch'
import type { LocationQueryRaw } from 'vue-router'

/**
* Redirect user to ATProto authentication
*/
export async function authRedirect(identifier: string, create: boolean = false) {
let query: LocationQueryRaw = { handle: identifier }
if (create) {
query = { ...query, create: 'true' }
}
await navigateTo(
{
path: '/api/auth/atproto',
query,
},
{ external: true },
)
}

export async function handleAuthError(
fetchError: FetchError,
userHandle?: string | null,
): Promise<never> {
const errorMessage = fetchError?.data?.message
if (errorMessage === ERROR_NEED_REAUTH && userHandle) {
await authRedirect(userHandle)
}
throw fetchError
}
Comment on lines +21 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Checking for nuxt.config files ==="
fd -t f -e 'ts' -e 'js' 'nuxt.config' .

echo -e "\n=== Searching for ERROR_NEED_REAUTH usage across codebase ==="
rg "ERROR_NEED_REAUTH" -n --type ts --type js

echo -e "\n=== Checking if ERROR_NEED_REAUTH is defined in constants ==="
fd -t f 'constants' | head -20 | xargs cat 2>/dev/null | grep -B 2 -A 2 "ERROR_NEED_REAUTH" || echo "No direct matches found"

echo -e "\n=== Checking app/utils/atproto/helpers.ts for all imports ==="
cat app/utils/atproto/helpers.ts

Repository: npmx-dev/npmx.dev

Length of output: 1492


🏁 Script executed:

#!/bin/bash
echo "=== Checking nuxt.config.ts for auto-import settings ==="
cat nuxt.config.ts | grep -A 30 "imports" || echo "No imports section found"

echo -e "\n=== Checking server/utils/atproto/oauth.ts for ERROR_NEED_REAUTH import ==="
head -40 server/utils/atproto/oauth.ts

Repository: npmx-dev/npmx.dev

Length of output: 2915


🏁 Script executed:

#!/bin/bash
echo "=== Checking server/utils/atproto/oauth.ts around line 76 where ERROR_NEED_REAUTH is used ==="
sed -n '1,90p' server/utils/atproto/oauth.ts | tail -50

Repository: npmx-dev/npmx.dev

Length of output: 1873


🏁 Script executed:

#!/bin/bash
echo "=== Checking complete imports in server/utils/atproto/oauth.ts ==="
head -20 server/utils/atproto/oauth.ts

Repository: npmx-dev/npmx.dev

Length of output: 1019


Import ERROR_NEED_REAUTH from shared constants.

ERROR_NEED_REAUTH is used on line 26 but not imported. Add the missing import:

import { ERROR_NEED_REAUTH } from '#shared/utils/constants'

This constant is defined in shared/utils/constants.ts and must be explicitly imported, as Nuxt's auto-import configuration only covers composables, not shared utilities.

60 changes: 60 additions & 0 deletions app/utils/atproto/likes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { FetchError } from 'ofetch'
import { handleAuthError } from '~/utils/atproto/helpers'
import type { PackageLikes } from '#shared/types/social'

export type LikeResult = { success: true; data: PackageLikes } | { success: false; error: Error }

/**
* Like a package via the API
*/
export async function likePackage(
packageName: string,
userHandle?: string | null,
): Promise<LikeResult> {
try {
const result = await $fetch<PackageLikes>('/api/social/like', {
method: 'POST',
body: { packageName },
})
return { success: true, data: result }
} catch (e) {
if (e instanceof FetchError) {
await handleAuthError(e, userHandle)
}
return { success: false, error: e as Error }
}
}

/**
* Unlike a package via the API
*/
export async function unlikePackage(
packageName: string,
userHandle?: string | null,
): Promise<LikeResult> {
try {
const result = await $fetch<PackageLikes>('/api/social/like', {
method: 'DELETE',
body: { packageName },
})
return { success: true, data: result }
} catch (e) {
if (e instanceof FetchError) {
await handleAuthError(e, userHandle)
}
return { success: false, error: e as Error }
}
}

/**
* Toggle like status for a package
*/
export async function togglePackageLike(
packageName: string,
currentlyLiked: boolean,
userHandle?: string | null,
): Promise<LikeResult> {
return currentlyLiked
? unlikePackage(packageName, userHandle)
: likePackage(packageName, userHandle)
}
6 changes: 6 additions & 0 deletions modules/cache.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import process from 'node:process'
import { defineNuxtModule } from 'nuxt/kit'
import { provider } from 'std-env'

Expand Down Expand Up @@ -27,6 +28,11 @@ export default defineNuxtModule({
...nitroConfig.storage[FETCH_CACHE_STORAGE_BASE],
driver: 'vercel-runtime-cache',
}

const env = process.env.VERCEL_ENV
nitroConfig.storage.atproto = {
driver: env === 'production' ? 'vercel-kv' : 'vercel-runtime-cache',
}
})
},
})
10 changes: 10 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export default defineNuxtConfig({
// never cache
'/search': { isr: false, cache: false },
'/api/auth/**': { isr: false, cache: false },
'/api/social/**': { isr: false, cache: false },
// infinite cache (versioned - doesn't change)
'/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
'/package-docs/:pkg/v/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
Expand Down Expand Up @@ -151,6 +152,10 @@ export default defineNuxtConfig({
driver: 'fsLite',
base: './.cache/fetch',
},
'atproto': {
driver: 'fsLite',
base: './.cache/atproto',
},
},
typescript: {
tsConfig: {
Expand Down Expand Up @@ -253,6 +258,11 @@ export default defineNuxtConfig({
'virtua/vue',
'semver',
'validate-npm-package-name',
'@atproto/lex',
'@atproto/lex-data',
'@atproto/lex-json',
'@atproto/lex-schema',
'@atproto/lex-client',
],
},
},
Expand Down
4 changes: 3 additions & 1 deletion server/api/auth/atproto.get.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Agent } from '@atproto/api'
import { NodeOAuthClient } from '@atproto/oauth-client-node'
import { createError, getQuery, sendRedirect } from 'h3'
import { getOAuthLock } from '#server/utils/atproto/lock'
import { useOAuthStorage } from '#server/utils/atproto/storage'
import { SLINGSHOT_HOST } from '#shared/utils/constants'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing import for UNSET_NUXT_SESSION_PASSWORD.

The constant is used on line 14 but not imported. This will cause a ReferenceError at runtime when config.sessionPassword is not set.

🐛 Proposed fix to add the missing import
-import { SLINGSHOT_HOST } from '#shared/utils/constants'
+import { SLINGSHOT_HOST, UNSET_NUXT_SESSION_PASSWORD } from '#shared/utils/constants'

Also applies to: 14-14

import { useServerSession } from '#server/utils/server-session'
Expand All @@ -11,7 +12,7 @@ export default defineEventHandler(async event => {
if (!config.sessionPassword) {
throw createError({
status: 500,
message: 'NUXT_SESSION_PASSWORD not set',
message: UNSET_NUXT_SESSION_PASSWORD,
})
}

Expand All @@ -24,6 +25,7 @@ export default defineEventHandler(async event => {
stateStore,
sessionStore,
clientMetadata,
requestLock: getOAuthLock(),
})

if (!query.code) {
Expand Down
41 changes: 41 additions & 0 deletions server/api/social/like.delete.ts
Copy link
Member

@alexdln alexdln Feb 2, 2026

Choose a reason for hiding this comment

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

How about making xrpc routes (sth like /xrpc/npmx.feed.like.create)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now we're just using the the server side oauth client so if we made this endpoint an XRPC it wouldn't be quite correct since it uses the cookie for authentication. But there is a plan for some XRPC endpoints for other things just need to make a middleware for it to authenticate the service auth jwt

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as v from 'valibot'
import { Client } from '@atproto/lex'
import * as dev from '#shared/types/lexicons/dev'
import { PackageLikeBodySchema } from '#shared/schemas/social'
import { throwOnMissingOAuthScope } from '#server/utils/atproto/oauth'
Comment on lines +1 to +5
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check nuxt.config for auto-import configuration
echo "=== Checking nuxt.config for auto-import settings ==="
find . -name 'nuxt.config.*' -type f | head -5 | xargs -I {} sh -c 'echo "File: {}"; cat {}'

echo -e "\n=== Checking like.post.ts for LIKES_SCOPE import pattern ==="
fd -t f 'like.post.ts' | head -1 | xargs -I {} sh -c 'echo "File: {}"; cat {}'

echo -e "\n=== Checking like.delete.ts full content ==="
cat server/api/social/like.delete.ts

echo -e "\n=== Verifying LIKES_SCOPE definition in constants ==="
grep -n "LIKES_SCOPE" '#shared/utils/constants' 2>/dev/null || find . -path '*/shared/utils/constants*' -type f | xargs grep -l "LIKES_SCOPE" 2>/dev/null | head -1 | xargs grep -n "LIKES_SCOPE"

Repository: npmx-dev/npmx.dev

Length of output: 10575


Add missing import for LIKES_SCOPE.

LIKES_SCOPE is used on line 14 but is not imported. The comparable like.post.ts explicitly imports it from #shared/utils/constants, and the code will fail at runtime without it.

🔧 Proposed fix
 import * as v from 'valibot'
 import { Client } from '@atproto/lex'
 import * as dev from '#shared/types/lexicons/dev'
 import { PackageLikeBodySchema } from '#shared/schemas/social'
 import { throwOnMissingOAuthScope } from '#server/utils/atproto/oauth'
+import { LIKES_SCOPE } from '#shared/utils/constants'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import * as v from 'valibot'
import { Client } from '@atproto/lex'
import * as dev from '#shared/types/lexicons/dev'
import { PackageLikeBodySchema } from '#shared/schemas/social'
import { throwOnMissingOAuthScope } from '#server/utils/atproto/oauth'
import * as v from 'valibot'
import { Client } from '@atproto/lex'
import * as dev from '#shared/types/lexicons/dev'
import { PackageLikeBodySchema } from '#shared/schemas/social'
import { throwOnMissingOAuthScope } from '#server/utils/atproto/oauth'
import { LIKES_SCOPE } from '#shared/utils/constants'


export default eventHandlerWithOAuthSession(async (event, oAuthSession) => {
const loggedInUsersDid = oAuthSession?.did.toString()

if (!oAuthSession || !loggedInUsersDid) {
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
}

//Checks if the user has a scope to like packages
await throwOnMissingOAuthScope(oAuthSession, LIKES_SCOPE)

const body = v.parse(PackageLikeBodySchema, await readBody(event))

const likesUtil = new PackageLikesUtils()

const getTheUsersLikedRecord = await likesUtil.getTheUsersLikedRecord(
body.packageName,
loggedInUsersDid,
)

if (getTheUsersLikedRecord) {
const client = new Client(oAuthSession)

await client.delete(dev.npmx.feed.like, {
rkey: getTheUsersLikedRecord.rkey,
})
const result = await likesUtil.unlikeAPackageAndReturnLikes(body.packageName, loggedInUsersDid)
return result
}
Comment on lines +26 to +34
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for the AT Protocol delete operation.

The client.delete call can throw network or API errors. If it fails, the subsequent unlikeAPackageAndReturnLikes call would not execute, but there's no error handling to provide a meaningful response or log the failure. This aligns with the PR author's note about "need for more error handling".

Consider wrapping the operation in a try-catch to handle failures gracefully:

🛡️ Proposed fix
   if (getTheUsersLikedRecord) {
     const client = new Client(oAuthSession)
 
-    await client.delete(dev.npmx.feed.like, {
-      rkey: getTheUsersLikedRecord.rkey,
-    })
-    const result = await likesUtil.unlikeAPackageAndReturnLikes(body.packageName, loggedInUsersDid)
-    return result
+    try {
+      await client.delete(dev.npmx.feed.like, {
+        rkey: getTheUsersLikedRecord.rkey,
+      })
+      const result = await likesUtil.unlikeAPackageAndReturnLikes(body.packageName, loggedInUsersDid)
+      return result
+    }
+    catch (error) {
+      console.error(`Failed to delete like for package ${body.packageName}:`, error)
+      throw createError({ statusCode: 500, statusMessage: 'Failed to unlike package' })
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (getTheUsersLikedRecord) {
const client = new Client(oAuthSession)
await client.delete(dev.npmx.feed.like, {
rkey: getTheUsersLikedRecord.rkey,
})
const result = await likesUtil.unlikeAPackageAndReturnLikes(body.packageName, loggedInUsersDid)
return result
}
if (getTheUsersLikedRecord) {
const client = new Client(oAuthSession)
try {
await client.delete(dev.npmx.feed.like, {
rkey: getTheUsersLikedRecord.rkey,
})
const result = await likesUtil.unlikeAPackageAndReturnLikes(body.packageName, loggedInUsersDid)
return result
}
catch (error) {
console.error(`Failed to delete like for package ${body.packageName}:`, error)
throw createError({ statusCode: 500, statusMessage: 'Failed to unlike package' })
}
}


console.warn(
`User ${loggedInUsersDid} tried to unlike a package ${body.packageName} but it was not liked by them.`,
)

return await likesUtil.getLikes(body.packageName, loggedInUsersDid)
})
Loading
Loading