diff --git a/servers/themes/package-lock.json b/servers/themes/package-lock.json index ddae93595..bbbcb20c2 100644 --- a/servers/themes/package-lock.json +++ b/servers/themes/package-lock.json @@ -1,12 +1,12 @@ { "name": "@notesnook/themes-server", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@notesnook/themes-server", - "version": "1.0.2", + "version": "1.0.3", "license": "ISC", "dependencies": { "@notesnook/theme": "file:../../packages/theme", diff --git a/servers/themes/package.json b/servers/themes/package.json index 62fe4d014..9efadb5e2 100644 --- a/servers/themes/package.json +++ b/servers/themes/package.json @@ -1,6 +1,6 @@ { "name": "@notesnook/themes-server", - "version": "1.0.2", + "version": "1.0.3", "description": "A simple rest api for notesnook themes", "private": "true", "main": "src/index.ts", diff --git a/servers/themes/src/api.ts b/servers/themes/src/api.ts index bd64fcc5b..3fb03f8ea 100644 --- a/servers/themes/src/api.ts +++ b/servers/themes/src/api.ts @@ -19,7 +19,7 @@ along with this program. If not, see . import { z } from "zod"; import { InstallsCounter } from "./constants"; -import { findTheme, getThemes } from "./orama"; +import { findTheme, getThemes, updateTotalInstalls } from "./orama"; import { syncThemes } from "./sync"; import { publicProcedure, router } from "./trpc"; import { THEME_COMPATIBILITY_VERSION } from "@notesnook/theme"; @@ -41,7 +41,12 @@ export const ThemesAPI = router({ const theme = await findTheme(id, compatibilityVersion); if (!theme) return; - if (userId) await InstallsCounter.increment(theme.id, userId); + if (userId) { + updateTotalInstalls( + theme, + await InstallsCounter.increment(theme.id, userId) + ); + } return theme; }), updateTheme: publicProcedure diff --git a/servers/themes/src/counter/fs.ts b/servers/themes/src/counter/fs.ts index 0003036eb..e06ab0e0a 100644 --- a/servers/themes/src/counter/fs.ts +++ b/servers/themes/src/counter/fs.ts @@ -30,12 +30,13 @@ export class FsCounter { } async increment(key: string, uid: string) { - await this.mutex.runExclusive(async () => { + return await this.mutex.runExclusive(async () => { const counts = await this.all(); counts[key] = counts[key] || []; - if (counts[key].includes(uid)) return; + if (counts[key].includes(uid)) return counts[key].length; counts[key].push(uid); await this.save(counts); + return counts[key].length; }); } diff --git a/servers/themes/src/counter/kv.ts b/servers/themes/src/counter/kv.ts index 9b30759c6..378ecf37e 100644 --- a/servers/themes/src/counter/kv.ts +++ b/servers/themes/src/counter/kv.ts @@ -28,6 +28,7 @@ type WorkersKVRESTConfig = { export class KVCounter { private readonly client: Cloudflare; private readonly mutex: Mutex; + private installs: Record = {}; constructor(private readonly config: WorkersKVRESTConfig) { this.mutex = new Mutex(); this.client = new Cloudflare({ @@ -36,15 +37,12 @@ export class KVCounter { } async increment(key: string, uid: string) { - await this.mutex.runExclusive(async () => { - const installs = await readMulti(this.client, this.config, [key]); - const existing = installs[key] || []; - await write( - this.client, - this.config, - key, - Array.from(new Set([...existing, uid])) - ); + return await this.mutex.runExclusive(async () => { + const existing = this.installs[key] || []; + const installsSet = Array.from(new Set([...existing, uid])); + await write(this.client, this.config, key, installsSet); + this.installs[key] = installsSet; + return installsSet.length; }); } @@ -54,6 +52,7 @@ export class KVCounter { for (const [key, value] of Object.entries(installs)) { result[key] = value.length; } + this.installs = installs; return result; } } @@ -87,13 +86,8 @@ function write( key: string, data: T ) { - return client.kv.namespaces.bulkUpdate(config.namespaceId, { + return client.kv.namespaces.values.update(config.namespaceId, key, { account_id: config.cfAccountId, - body: [ - { - key, - value: JSON.stringify(data) - } - ] + value: JSON.stringify(data) }); } diff --git a/servers/themes/src/orama.ts b/servers/themes/src/orama.ts index 744bec650..8f970c97f 100644 --- a/servers/themes/src/orama.ts +++ b/servers/themes/src/orama.ts @@ -16,7 +16,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { Orama, SearchParams, create, search } from "@orama/orama"; +import { Orama, SearchParams, create, search, update } from "@orama/orama"; import { CompiledThemeDefinition, ThemeMetadata } from "./sync"; import { ThemeQuerySchema } from "./schemas"; @@ -30,7 +30,8 @@ export async function initializeDatabase(): Promise { colorScheme: "string", compatibilityVersion: "number", description: "string", - tags: "string[]" + tags: "string[]", + totalInstalls: "number" }, id: "notesnook-themes" }); @@ -53,6 +54,14 @@ export async function findTheme( return results.hits[0].document as CompiledThemeDefinition; } +export async function updateTotalInstalls( + theme: CompiledThemeDefinition, + totalInstalls: number +) { + if (!ThemesDatabase) await initializeDatabase(); + await update(ThemesDatabase!, theme.id, { ...theme, totalInstalls }); +} + export async function getThemes(query: (typeof ThemeQuerySchema)["_type"]) { if (!ThemesDatabase) await initializeDatabase(); @@ -60,6 +69,10 @@ export async function getThemes(query: (typeof ThemeQuerySchema)["_type"]) { const count = query.limit; const searchParams: SearchParams = { + sortBy: { + property: "totalInstalls", + order: "DESC" + }, where: { compatibilityVersion: { eq: query.compatibilityVersion diff --git a/servers/themes/src/server.ts b/servers/themes/src/server.ts index d1c0d5dce..63895bfd5 100644 --- a/servers/themes/src/server.ts +++ b/servers/themes/src/server.ts @@ -32,6 +32,9 @@ server.listen(PORT, HOST); console.log(`Server started successfully on: http://${HOST}:${PORT}/`); syncThemes(); +setInterval(() => { + syncThemes(); +}, 1000 * 60 * 60); // every hour if (import.meta.hot) { import.meta.hot.on("vite:beforeFullReload", () => {