kopia lustrzana https://github.com/elk-zone/elk
				
				
				
			feat: use memory/fs/kv storage drivers for server details (#34)
							rodzic
							
								
									2ece5f5619
								
							
						
					
					
						commit
						521ad7a332
					
				|  | @ -1 +1,6 @@ | |||
| MASTODON_TOKEN= | ||||
| 
 | ||||
| # Production only | ||||
| NUXT_CLOUDFLARE_ACCOUNT_ID= | ||||
| NUXT_CLOUDFLARE_NAMESPACE_ID= | ||||
| NUXT_CLOUDFLARE_API_TOKEN= | ||||
|  |  | |||
|  | @ -4,5 +4,3 @@ dist | |||
| .output | ||||
| .nuxt | ||||
| .env | ||||
| 
 | ||||
| registered-apps.json | ||||
|  |  | |||
|  | @ -33,6 +33,10 @@ export default defineNuxtConfig({ | |||
|     }, | ||||
|   }, | ||||
|   runtimeConfig: { | ||||
|     registedAppsUrl: process.env.APPS_JSON_URL || 'http://localhost:3000/registered-apps.json', | ||||
|     cloudflare: { | ||||
|       accountId: '', | ||||
|       namespaceId: '', | ||||
|       apiToken: '', | ||||
|     }, | ||||
|   }, | ||||
| }) | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ | |||
|     "dev": "nuxi dev", | ||||
|     "start": "node .output/server/index.mjs", | ||||
|     "lint": "eslint .", | ||||
|     "register-apps": "esno ./scripts/registerApps.ts", | ||||
|     "postinstall": "nuxi prepare", | ||||
|     "generate": "nuxi generate" | ||||
|   }, | ||||
|  |  | |||
|  | @ -1,52 +0,0 @@ | |||
| import fs from 'fs-extra' | ||||
| import { $fetch } from 'ohmyfetch' | ||||
| import { APP_NAME } from '~/constants' | ||||
| import type { AppInfo } from '~/types' | ||||
| 
 | ||||
| const KNOWN_SERVERS = [ | ||||
|   'mastodon.social', | ||||
|   'mas.to', | ||||
|   'fosstodon.org', | ||||
|   'm.cmx.im', | ||||
|   'mastodon.world', | ||||
| ] | ||||
| 
 | ||||
| const KNOWN_DOMAINS = [ | ||||
|   'http://localhost:3000', | ||||
|   'https://elk.netlify.app', | ||||
|   'https://elk.zone', | ||||
| ] | ||||
| 
 | ||||
| const filename = 'public/registered-apps.json' | ||||
| 
 | ||||
| let registeredApps: Record<string, AppInfo> = {} | ||||
| 
 | ||||
| if (fs.existsSync(filename)) | ||||
|   registeredApps = await fs.readJSON(filename) | ||||
| 
 | ||||
| for (const server of KNOWN_SERVERS) { | ||||
|   const redirect_uris = [ | ||||
|     'urn:ietf:wg:oauth:2.0:oob', | ||||
|     ...KNOWN_DOMAINS.map(d => `${d}/api/${server}/oauth`), | ||||
|   ].join('\n') | ||||
| 
 | ||||
|   if (!registeredApps[server] || registeredApps[server].redirect_uri !== redirect_uris) { | ||||
|     const app = await $fetch(`https://${server}/api/v1/apps`, { | ||||
|       method: 'POST', | ||||
|       body: { | ||||
|         client_name: APP_NAME, | ||||
|         redirect_uris, | ||||
|         scopes: 'read write follow push', | ||||
|       }, | ||||
|     }) | ||||
| 
 | ||||
|     registeredApps[server] = app | ||||
| 
 | ||||
|     console.log(`Registered app for ${server}`) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| if (!fs.existsSync('public')) | ||||
|   await fs.mkdir('public') | ||||
| 
 | ||||
| await fs.writeJSON(filename, registeredApps, { spaces: 2, EOL: '\n' }) | ||||
|  | @ -0,0 +1,40 @@ | |||
| import type { Driver } from 'unstorage' | ||||
| // @ts-expect-error unstorage needs to provide backwards-compatible subpath types
 | ||||
| import _memory from 'unstorage/drivers/memory' | ||||
| import { defineDriver } from 'unstorage' | ||||
| 
 | ||||
| const memory = _memory as typeof import('unstorage/dist/drivers/memory')['default'] | ||||
| 
 | ||||
| export interface CacheDriverOptions { | ||||
|   driver: Driver | ||||
| } | ||||
| 
 | ||||
| export default defineDriver((driver: Driver = memory()) => { | ||||
|   const memoryDriver = memory() | ||||
|   return { | ||||
|     ...driver, | ||||
|     async hasItem(key: string) { | ||||
|       if (await memoryDriver.hasItem(key)) | ||||
|         return true | ||||
| 
 | ||||
|       return driver.hasItem(key) | ||||
|     }, | ||||
|     async setItem(key: string, value: any) { | ||||
|       await Promise.all([ | ||||
|         memoryDriver.setItem(key, value), | ||||
|         driver.setItem?.(key, value), | ||||
|       ]) | ||||
|     }, | ||||
|     async getItem(key: string) { | ||||
|       let value = await memoryDriver.getItem(key) | ||||
| 
 | ||||
|       if (value !== null) | ||||
|         return value | ||||
| 
 | ||||
|       value = await driver.getItem(key) | ||||
|       memoryDriver.setItem(key, value) | ||||
| 
 | ||||
|       return value | ||||
|     }, | ||||
|   } | ||||
| }) | ||||
|  | @ -1,26 +1,69 @@ | |||
| // @ts-expect-error unstorage needs to provide backwards-compatible subpath types
 | ||||
| import _fs from 'unstorage/drivers/fs' | ||||
| // @ts-expect-error unstorage needs to provide backwards-compatible subpath types
 | ||||
| import _kv from 'unstorage/drivers/cloudflare-kv-http' | ||||
| 
 | ||||
| import { $fetch } from 'ohmyfetch' | ||||
| import type { Storage } from 'unstorage' | ||||
| 
 | ||||
| import cached from './cache-driver' | ||||
| 
 | ||||
| import type { AppInfo } from '~/types' | ||||
| import { APP_NAME } from '~/constants' | ||||
| 
 | ||||
| export const registeredApps: Record<string, AppInfo> = {} | ||||
| const fs = _fs as typeof import('unstorage/dist/drivers/fs')['default'] | ||||
| const kv = _kv as typeof import('unstorage/dist/drivers/cloudflare-kv-http')['default'] | ||||
| 
 | ||||
| const runtimeConfig = useRuntimeConfig() | ||||
| const promise = $fetch(runtimeConfig.registedAppsUrl, { responseType: 'json' }) | ||||
|   .then((r) => { | ||||
|     Object.assign(registeredApps, r) | ||||
|     // eslint-disable-next-line no-console
 | ||||
|     console.log(`\n${Object.keys(registeredApps).length} registered apps loaded from ${runtimeConfig.registedAppsUrl.split(/\/+/g)[1]}`) | ||||
|     // eslint-disable-next-line no-console
 | ||||
|     console.log(`${Object.keys(registeredApps).map(i => ` - ${i}`).join('\n')}\n`) | ||||
|   }) | ||||
|   .catch((e) => { | ||||
|     if (process.dev) | ||||
|       console.error('Failed to fetch registered apps,\nyou may need to run `nr register-apps` first') | ||||
|     else | ||||
|       console.error('Failed to fetch registered apps') | ||||
|     console.error(e) | ||||
| const storage = useStorage() as Storage | ||||
| 
 | ||||
| if (process.dev) { | ||||
|   storage.mount('servers', fs({ base: 'node_modules/.cache/servers' })) | ||||
| } | ||||
| else { | ||||
|   const config = useRuntimeConfig() | ||||
|   storage.mount('servers', cached(kv({ | ||||
|     accountId: config.cloudflare.accountId, | ||||
|     namespaceId: config.cloudflare.namespaceId, | ||||
|     apiToken: config.cloudflare.apiToken, | ||||
|   }))) | ||||
| } | ||||
| 
 | ||||
| const KNOWN_DOMAINS = [ | ||||
|   'http://localhost:3000', | ||||
|   'https://elk.netlify.app', | ||||
|   'https://elk.zone', | ||||
| ] | ||||
| 
 | ||||
| async function fetchAppInfo(server: string) { | ||||
|   const redirect_uris = [ | ||||
|     'urn:ietf:wg:oauth:2.0:oob', | ||||
|     ...KNOWN_DOMAINS.map(d => `${d}/api/${server}/oauth`), | ||||
|   ].join('\n') | ||||
| 
 | ||||
|   const app: AppInfo = await $fetch(`https://${server}/api/v1/apps`, { | ||||
|     method: 'POST', | ||||
|     body: { | ||||
|       client_name: APP_NAME, | ||||
|       redirect_uris, | ||||
|       scopes: 'read write follow push', | ||||
|     }, | ||||
|   }) | ||||
|   return app | ||||
| } | ||||
| 
 | ||||
| const serverKey = (server: string) => `servers:${server}.json` | ||||
| 
 | ||||
| export async function getApp(server: string) { | ||||
|   await promise | ||||
|   return registeredApps[server] | ||||
|   const key = serverKey(server) | ||||
|   if (await storage.hasItem(key)) | ||||
|     return storage.getItem(key) as Promise<AppInfo> | ||||
| 
 | ||||
|   try { | ||||
|     const appInfo = await fetchAppInfo(server) | ||||
|     await storage.setItem(key, appInfo) | ||||
|     return appInfo | ||||
|   } | ||||
|   catch { | ||||
|     return null | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Daniel Roe
						Daniel Roe