Skip to content

Commit 784bcc7

Browse files
committed
feels so close
1 parent cd6207c commit 784bcc7

File tree

7 files changed

+144
-22
lines changed

7 files changed

+144
-22
lines changed

app/composables/useAtproto.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,19 @@ export function useLikePackage(packageName: string) {
6868

6969
return { data, error, pending, mutate }
7070
}
71+
72+
export function useUnlikePackage(packageName: string) {
73+
const { user } = useAtproto()
74+
75+
const { data, error, pending, execute } = useFetch<PackageLikes>('/api/auth/social/like', {
76+
method: 'DELETE',
77+
body: { packageName },
78+
immediate: false,
79+
watch: false,
80+
onResponseError: async ({ error: e }) => {
81+
await handleAuthError(e, user.value?.handle)
82+
},
83+
})
84+
85+
return { data, error, pending, mutate: execute }
86+
}

app/pages/package/[...package].vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,14 @@ const { data: likesData } = useFetch(() => `/api/social/likes/${packageName.valu
363363
})
364364
365365
const { mutate: likePackage } = useLikePackage(packageName.value)
366+
const { mutate: unlikePackage } = useUnlikePackage(packageName.value)
366367
367368
const likeAction = async () => {
368369
if (user.value?.handle == null) {
369370
authModal.open()
370371
} else {
371-
const result = await likePackage()
372-
if (result?.totalLikes) {
372+
const result = likesData.value?.userHasLiked ? await unlikePackage() : await likePackage()
373+
if (result?.totalLikes != null) {
373374
likesData.value = result
374375
}
375376
}

server/api/auth/session.get.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,5 @@ export default eventHandlerWithOAuthSession(async (event, oAuthSession, serverSe
77
return null
88
}
99

10-
if (oAuthSession) {
11-
let tokenInfo = await oAuthSession.getTokenInfo()
12-
console.log('scopes', tokenInfo.scope)
13-
14-
// return null
15-
}
16-
1710
return result.output
1811
})
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Client } from '@atproto/lex'
2+
import * as dev from '#shared/types/lexicons/dev'
3+
import { LIKES_SCOPE } from '~~/shared/utils/constants'
4+
import { checkOAuthScope } from '~~/server/utils/atproto/oauth'
5+
6+
export default eventHandlerWithOAuthSession(async (event, oAuthSession) => {
7+
const loggedInUsersDid = oAuthSession?.did.toString()
8+
9+
if (!oAuthSession || !loggedInUsersDid) {
10+
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
11+
}
12+
13+
const body = await readBody<{ packageName: string }>(event)
14+
15+
if (!body.packageName) {
16+
throw createError({
17+
status: 400,
18+
message: 'packageName is required',
19+
})
20+
}
21+
22+
const cachedFetch = event.context.cachedFetch
23+
if (!cachedFetch) {
24+
// TODO: Probably needs to add in a normal fetch if not provided
25+
// but ideally should not happen
26+
throw createError({
27+
status: 500,
28+
message: 'cachedFetch not provided in context',
29+
})
30+
}
31+
32+
const likesUtil = new PackageLikesUtils(cachedFetch)
33+
34+
const getTheUsersLikedRecord = await likesUtil.getTheUsersLikedRecord(
35+
body.packageName,
36+
loggedInUsersDid,
37+
)
38+
if (getTheUsersLikedRecord) {
39+
//Checks if the user has a scope to like packages
40+
await checkOAuthScope(oAuthSession, LIKES_SCOPE)
41+
const client = new Client(oAuthSession)
42+
43+
var result = await client.delete(dev.npmx.feed.like, {
44+
rkey: getTheUsersLikedRecord.rkey,
45+
})
46+
console.log(result)
47+
return await likesUtil.unlikeAPackageAndReturnLikes(body.packageName, loggedInUsersDid)
48+
}
49+
})

server/api/auth/social/like.post.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Client } from '@atproto/lex'
2-
// import { main as likeRecord } from '#shared/types/lexicons/dev/npmx/feed/like.defs'
32
import * as dev from '#shared/types/lexicons/dev'
43
import type { UriString } from '@atproto/lex'
5-
import { ERROR_NEED_REAUTH, LIKES_SCOPE } from '~~/shared/utils/constants'
4+
import { LIKES_SCOPE } from '~~/shared/utils/constants'
5+
import { checkOAuthScope } from '~~/server/utils/atproto/oauth'
66

77
export default eventHandlerWithOAuthSession(async (event, oAuthSession) => {
88
const loggedInUsersDid = oAuthSession?.did.toString()
@@ -41,28 +41,21 @@ export default eventHandlerWithOAuthSession(async (event, oAuthSession) => {
4141
}
4242

4343
//Checks if the user has a scope to like packages
44-
const tokenInfo = await oAuthSession.getTokenInfo()
45-
if (!tokenInfo.scope.includes(LIKES_SCOPE)) {
46-
throw createError({
47-
status: 403,
48-
message: ERROR_NEED_REAUTH,
49-
})
50-
}
44+
await checkOAuthScope(oAuthSession, LIKES_SCOPE)
5145

5246
const subjectRef = PACKAGE_SUBJECT_REF(body.packageName)
5347
const client = new Client(oAuthSession)
5448

5549
const like = dev.npmx.feed.like.$build({
5650
createdAt: new Date().toISOString(),
57-
//TODO test this?
5851
subjectRef: subjectRef as UriString,
5952
})
6053

6154
const result = await client.create(dev.npmx.feed.like, like)
6255
if (!result) {
6356
throw createError({
6457
status: 500,
65-
message: 'Failed to create like',
58+
message: 'Failed to create a like',
6659
})
6760
}
6861

server/utils/atproto/oauth.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import { LIKES_SCOPE, UNSET_NUXT_SESSION_PASSWORD } from '#shared/utils/constant
88
import { OAuthMetadataSchema } from '#shared/schemas/oauth'
99
// @ts-expect-error virtual file from oauth module
1010
import { clientUri } from '#oauth/config'
11-
// TODO: limit scope as features gets added. atproto just allows login so no scary login screen till we have scopes
12-
// export const scope = 'atproto'
11+
// TODO: If you add writing a new record you will need to add a scope for it
1312
export const scope = `atproto ${LIKES_SCOPE}`
1413

1514
export function getOauthClientMetadata() {
@@ -61,6 +60,22 @@ async function getOAuthSession(event: H3Event): Promise<OAuthSession | undefined
6160
return await client.restore(currentSession.tokenSet.sub)
6261
}
6362

63+
/**
64+
* As we add new scopes we need to check if the client has the ability to use it.
65+
* If not need to let the client know to redirect the user to the PDS to upgrade their scopes.
66+
* @param oAuthSession - The current OAuth session from the event
67+
* @param requiredScopes - The required scope you are checking if you can use
68+
*/
69+
export async function checkOAuthScope(oAuthSession: OAuthSession, requiredScopes: string) {
70+
const tokenInfo = await oAuthSession.getTokenInfo()
71+
if (!tokenInfo.scope.includes(requiredScopes)) {
72+
throw createError({
73+
status: 403,
74+
message: ERROR_NEED_REAUTH,
75+
})
76+
}
77+
}
78+
6479
export function eventHandlerWithOAuthSession<T extends EventHandlerRequest, D>(
6580
handler: EventHandlerWithOAuthSession<T, D>,
6681
) {

server/utils/atproto/utils/likes.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getCacheAdatper } from '../../cache'
22
import { $nsid as likeNsid } from '#shared/types/lexicons/dev/npmx/feed/like.defs'
3+
import type { Backlink } from '~~/shared/utils/constellation'
34

45
/**
56
* Likes for a npm package on npmx
@@ -40,6 +41,8 @@ export class PackageLikesUtils {
4041
'.subjectRef',
4142
//Limit doesn't matter here since we are just counting the total likes
4243
1,
44+
undefined,
45+
0,
4346
)
4447
return totalLinks.total
4548
}
@@ -60,6 +63,7 @@ export class PackageLikesUtils {
6063
undefined,
6164
false,
6265
[[usersDid]],
66+
0,
6367
)
6468
//TODO: need to double check this logic
6569
return userLikes.total > 0
@@ -149,4 +153,55 @@ export class PackageLikesUtils {
149153
userHasLiked: true,
150154
} as PackageLikes
151155
}
156+
157+
/**
158+
* We need to get the record the user has that they liked the package
159+
* @param packageName
160+
* @param usersDid
161+
* @returns
162+
*/
163+
async getTheUsersLikedRecord(
164+
packageName: string,
165+
usersDid: string,
166+
): Promise<Backlink | undefined> {
167+
const subjectRef = PACKAGE_SUBJECT_REF(packageName)
168+
const { data: userLikes } = await this.constellation.getBackLinks(
169+
subjectRef,
170+
likeNsid,
171+
'subjectRef',
172+
//Limit doesn't matter here since we are just counting the total likes
173+
1,
174+
undefined,
175+
false,
176+
[[usersDid]],
177+
0,
178+
)
179+
if (userLikes.total > 0 && userLikes.records.length > 0) {
180+
return userLikes.records[0]
181+
}
182+
}
183+
184+
/**
185+
* At this point you should have checked if the user had a record for the package on the network and removed it before updating the cache
186+
* @param packageName
187+
* @param usersDid
188+
* @returns
189+
*/
190+
async unlikeAPackageAndReturnLikes(packageName: string, usersDid: string): Promise<PackageLikes> {
191+
const totalLikesKey = CACHE_PACKAGE_TOTAL_KEY(packageName)
192+
const subjectRef = PACKAGE_SUBJECT_REF(packageName)
193+
194+
let totalLikes = await this.cache.get<number>(totalLikesKey)
195+
if (!totalLikes) {
196+
totalLikes = await this.constellationLikes(subjectRef)
197+
}
198+
totalLikes = totalLikes - 1
199+
await this.cache.set(totalLikesKey, totalLikes, CACHE_MAX_AGE)
200+
201+
await this.cache.delete(CACHE_USER_LIKES_KEY(packageName, usersDid))
202+
return {
203+
totalLikes: totalLikes,
204+
userHasLiked: false,
205+
} as PackageLikes
206+
}
152207
}

0 commit comments

Comments
 (0)