Skip to content

Commit 5637945

Browse files
committed
Ideally should be the logic to read/set likes for npmx
1 parent 4830f1d commit 5637945

File tree

5 files changed

+164
-4
lines changed

5 files changed

+164
-4
lines changed

server/api/auth/atproto.get.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default defineEventHandler(async event => {
1010
if (!config.sessionPassword) {
1111
throw createError({
1212
status: 500,
13-
message: 'NUXT_SESSION_PASSWORD not set',
13+
message: UNSET_NUXT_SESSION_PASSWORD,
1414
})
1515
}
1616

server/api/likes/[...pkg].get.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default defineEventHandler(async _ => {
2+
//TODO: Use the new thing I wrote last night
3+
return 0
4+
})
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { getCacheAdatper } from '../../cache'
2+
import { $nsid as likeNsid } from '#shared/types/lexicons/dev/npmx/feed/like.defs'
3+
import { SUBJECT_REF_PREFIX } from '~~/shared/utils/constants'
4+
5+
/**
6+
* Likes for a npm package on npmx
7+
*/
8+
export type PackageLikes = {
9+
// The total likes found for the package
10+
totalLikes: number
11+
// If the logged in user has liked the package, false if not logged in
12+
userHasLiked: boolean
13+
}
14+
15+
const CACHE_PREFIX = 'atproto-likes:'
16+
const CACHE_PACKAGE_TOTAL_KEY = (packageName: string) => `${CACHE_PREFIX}:${packageName}:total`
17+
const CACHE_USER_LIKES_KEY = (packageName: string, did: string) =>
18+
`${CACHE_PREFIX}${packageName}users:${did}`
19+
20+
const CACHE_MAX_AGE = CACHE_MAX_AGE_ONE_MINUTE * 5
21+
22+
export class PackageLikesService {
23+
private readonly constellation: Constellation
24+
private readonly cache: CacheAdapter
25+
26+
constructor(cachedFunction: CachedFetchFunction) {
27+
this.constellation = new Constellation(cachedFunction)
28+
this.cache = getCacheAdatper(CACHE_PREFIX)
29+
}
30+
31+
/**
32+
* Gets the true total count of likes for a npm package from the network
33+
* @param subjectRef
34+
* @returns
35+
*/
36+
private async constellationLikes(subjectRef: string) {
37+
// TODO: I need to see what failed fetch calls do here
38+
const { data: totalLinks } = await this.constellation.getLinksDistinctDids(
39+
subjectRef,
40+
likeNsid,
41+
'.subjectRef',
42+
//Limit doesn't matter here since we are just counting the total likes
43+
1,
44+
undefined,
45+
CACHE_MAX_AGE_ONE_MINUTE * 10,
46+
)
47+
return totalLinks.total
48+
}
49+
50+
/**
51+
* Checks if the user has liked the npm package from the network
52+
* @param subjectRef
53+
* @param usersDid
54+
* @returns
55+
*/
56+
private async constellationUserHasLiked(subjectRef: string, usersDid: string) {
57+
const { data: userLikes } = await this.constellation.getBackLinks(
58+
subjectRef,
59+
likeNsid,
60+
'subjectRef',
61+
//Limit doesn't matter here since we are just counting the total likes
62+
1,
63+
undefined,
64+
false,
65+
[[usersDid]],
66+
)
67+
//TODO: need to double check this logic
68+
return userLikes.total > 0
69+
}
70+
71+
/**
72+
* Gets the likes for a npm package on npmx. Tries a local cahce first, if not found uses constellation
73+
* @param packageName
74+
* @param usersDid
75+
* @returns
76+
*/
77+
async getLikes(packageName: string, usersDid?: string) {
78+
//TODO: May need to do some clean up on the package name, and maybe even hash it? some of the charcteres may be a bit odd as keys
79+
const cache = getCacheAdatper(CACHE_PREFIX)
80+
81+
const cachedLikes = await cache.get<PackageLikes>(packageName)
82+
if (cachedLikes) {
83+
return cachedLikes
84+
}
85+
86+
const subjectRef = `${SUBJECT_REF_PREFIX}/${packageName}`
87+
88+
const totalLikes = await this.constellationLikes(subjectRef)
89+
90+
let userHasLiked = false
91+
92+
if (usersDid) {
93+
userHasLiked = await this.constellationUserHasLiked(subjectRef, usersDid)
94+
}
95+
96+
const packageLikes = {
97+
totalPackageLikes: totalLikes,
98+
userHasLiked,
99+
}
100+
if (userHasLiked && usersDid) {
101+
await cache.set(CACHE_USER_LIKES_KEY(packageName, usersDid), true, CACHE_MAX_AGE)
102+
}
103+
104+
const totalLikesKey = CACHE_PACKAGE_TOTAL_KEY(packageName)
105+
await cache.set(totalLikesKey, packageLikes.totalPackageLikes, CACHE_MAX_AGE)
106+
return packageLikes
107+
}
108+
109+
/**
110+
* Gets the definite answer if the user has liked a npm package. Either from the cache or the network
111+
* @param packageName
112+
* @param usersDid
113+
* @returns
114+
*/
115+
async hasTheUserLikedThePackage(packageName: string, usersDid: string) {
116+
const cache = getCacheAdatper(CACHE_PREFIX)
117+
const cached = await cache.get<boolean>(CACHE_USER_LIKES_KEY(packageName, usersDid))
118+
if (cached !== undefined) {
119+
return cached
120+
}
121+
const userHasLiked = await this.constellationUserHasLiked(
122+
`${SUBJECT_REF_PREFIX}/${packageName}`,
123+
usersDid,
124+
)
125+
await cache.set(CACHE_USER_LIKES_KEY(packageName, usersDid), userHasLiked, CACHE_MAX_AGE)
126+
return userHasLiked
127+
}
128+
129+
/**
130+
* It is asummed it has been checked by this point that if a user has liked a package and the new like was made as a record
131+
* to the user's atproto repostiory
132+
* @param packageName
133+
* @param usersDid
134+
*/
135+
async likeAPackageAndRetunLikes(packageName: string, usersDid: string): Promise<PackageLikes> {
136+
const cache = getCacheAdatper(CACHE_PREFIX)
137+
138+
const totalLikesKey = CACHE_PACKAGE_TOTAL_KEY(packageName)
139+
let totalLikes = await cache.get<number>(totalLikesKey)
140+
// If a cahce entry was found for total likes increase by 1
141+
if (totalLikes !== undefined) {
142+
await cache.set(totalLikesKey, totalLikes + 1, CACHE_MAX_AGE)
143+
} else {
144+
const subjectRef = `${SUBJECT_REF_PREFIX}/${packageName}`
145+
totalLikes = await this.constellationLikes(subjectRef)
146+
}
147+
// We already know the user has not liked the package so set in the cache
148+
await cache.set(CACHE_USER_LIKES_KEY(packageName, usersDid), true, CACHE_MAX_AGE)
149+
return {
150+
totalLikes: totalLikes,
151+
userHasLiked: true,
152+
}
153+
}
154+
}

server/utils/cache.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export class RedisCacheAdatper implements CacheAdapter {
9393
}
9494
}
9595

96-
export function getCache(prefix: string): CacheAdapter {
96+
export function getCacheAdatper(prefix: string): CacheAdapter {
9797
const config = useRuntimeConfig()
9898

9999
if (!import.meta.dev && config.upstash?.redisRestUrl && config.upstash?.redisRestToken) {
@@ -103,7 +103,5 @@ export function getCache(prefix: string): CacheAdapter {
103103
})
104104
return new RedisCacheAdatper(redis, prefix)
105105
}
106-
107-
console.log('using storage')
108106
return new StorageCacheAdapter()
109107
}

shared/utils/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export const ERROR_GRAVATAR_EMAIL_UNAVAILABLE = "User's email not accessible."
3030
export const CONSTELLATION_HOST = 'constellation.microcosm.blue'
3131
export const SLINGSHOT_HOST = 'slingshot.microcosm.blue'
3232

33+
// ATProtocol
34+
// Refrence prefix used to link packages to things that are not inherently atproto
35+
export const SUBJECT_REF_PREFIX = 'https://npmx.dev'
36+
3337
// Theming
3438
export const ACCENT_COLORS = {
3539
rose: 'oklch(0.797 0.084 11.056)',

0 commit comments

Comments
 (0)