From 86e5c6c6f6f7fd7367363c0ce18c0e28f72e5ad1 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 6 Feb 2026 18:33:16 -0600 Subject: [PATCH 1/4] settings save, and types as schema --- app/composables/useSettings.ts | 36 +++++++++----------------------- server/api/auth/settings.get.ts | 3 +++ server/api/auth/settings.post.ts | 18 ++++++++++++++++ server/utils/atproto/oauth.ts | 18 ++++++++++------ shared/schemas/app-settings.ts | 27 ++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 server/api/auth/settings.get.ts create mode 100644 server/api/auth/settings.post.ts create mode 100644 shared/schemas/app-settings.ts diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts index 98c313957..1f8907229 100644 --- a/app/composables/useSettings.ts +++ b/app/composables/useSettings.ts @@ -1,33 +1,8 @@ import type { RemovableRef } from '@vueuse/core' import { useLocalStorage } from '@vueuse/core' import { ACCENT_COLORS } from '#shared/utils/constants' -import type { LocaleObject } from '@nuxtjs/i18n' import { BACKGROUND_THEMES } from '#shared/utils/constants' - -type BackgroundThemeId = keyof typeof BACKGROUND_THEMES - -type AccentColorId = keyof typeof ACCENT_COLORS.light - -/** - * Application settings stored in localStorage - */ -export interface AppSettings { - /** Display dates as relative (e.g., "3 days ago") instead of absolute */ - relativeDates: boolean - /** Include @types/* package in install command for packages without built-in types */ - includeTypesInInstall: boolean - /** Accent color theme */ - accentColorId: AccentColorId | null - /** Preferred background shade */ - preferredBackgroundTheme: BackgroundThemeId | null - /** Hide platform-specific packages (e.g., @scope/pkg-linux-x64) from search results */ - hidePlatformPackages: boolean - /** User-selected locale */ - selectedLocale: LocaleObject['code'] | null - sidebar: { - collapsed: string[] - } -} +import type { AccentColorId, BackgroundThemeId, AppSettings } from '#shared/schemas/app-settings' const DEFAULT_SETTINGS: AppSettings = { relativeDates: false, @@ -41,6 +16,14 @@ const DEFAULT_SETTINGS: AppSettings = { }, } +export const syncSettings = async (settings: AppSettings) => { + // DO some error handling + await $fetch('/api/auth/settings', { + method: 'POST', + body: settings, + }) +} + const STORAGE_KEY = 'npmx-settings' // Shared settings instance (singleton per app) @@ -133,6 +116,7 @@ export function useBackgroundTheme() { document.documentElement.removeAttribute('data-bg-theme') } settings.value.preferredBackgroundTheme = id + syncSettings(settings.value) } return { diff --git a/server/api/auth/settings.get.ts b/server/api/auth/settings.get.ts new file mode 100644 index 000000000..cc63a62bd --- /dev/null +++ b/server/api/auth/settings.get.ts @@ -0,0 +1,3 @@ +//Want to check the cooki login state, can be slightly more laxed? +// +export default defineEventHandler(async _ => {}) diff --git a/server/api/auth/settings.post.ts b/server/api/auth/settings.post.ts new file mode 100644 index 000000000..86ce90954 --- /dev/null +++ b/server/api/auth/settings.post.ts @@ -0,0 +1,18 @@ +import * as v from 'valibot' +import { AppSettingsSchema } from '#shared/schemas/app-settings' +import type { AppSettings } from '#shared/schemas/app-settings' + +export default eventHandlerWithOAuthSession(async (event, oauthSession) => { + // TODO: prob find a better spot. Can't be event handler cause i need oauth on session.delete + if (oauthSession == undefined) { + return createError({ + statusCode: 401, + statusMessage: 'Unauthorized', + }) + } + const body = v.parse(AppSettingsSchema, await readBody(event)) + + const storage = useStorage('atproto:generic') + const storageKey = `settings:${oauthSession.did}` + storage.setItem(storageKey, body) +}) diff --git a/server/utils/atproto/oauth.ts b/server/utils/atproto/oauth.ts index c8fc6cd41..83f7b8971 100644 --- a/server/utils/atproto/oauth.ts +++ b/server/utils/atproto/oauth.ts @@ -43,12 +43,6 @@ export function getOauthClientMetadata() { }) as OAuthClientMetadataInput } -type EventHandlerWithOAuthSession = ( - event: H3Event, - session: OAuthSession | undefined, - serverSession: SessionManager, -) => Promise - async function getOAuthSession( event: H3Event, ): Promise<{ oauthSession: OAuthSession | undefined; serverSession: SessionManager }> { @@ -100,11 +94,23 @@ export async function throwOnMissingOAuthScope(oAuthSession: OAuthSession, requi } } +type EventHandlerWithOAuthSession = ( + event: H3Event, + session: OAuthSession | undefined, + serverSession: SessionManager, +) => Promise + +/** + * Handler with a valid OAuth session that is ready to be used + * @param handler + * @returns + */ export function eventHandlerWithOAuthSession( handler: EventHandlerWithOAuthSession, ) { return defineEventHandler(async event => { const { oauthSession, serverSession } = await getOAuthSession(event) + return await handler(event, oauthSession, serverSession) }) } diff --git a/shared/schemas/app-settings.ts b/shared/schemas/app-settings.ts new file mode 100644 index 000000000..2fdb3434e --- /dev/null +++ b/shared/schemas/app-settings.ts @@ -0,0 +1,27 @@ +import * as v from 'valibot' +import { ACCENT_COLORS, BACKGROUND_THEMES } from '#shared/utils/constants' + +type AccentColorKey = keyof typeof ACCENT_COLORS.light +type BackgroundThemeKey = keyof typeof BACKGROUND_THEMES + +const accentColorIds = Object.keys(ACCENT_COLORS.light) as [AccentColorKey, ...AccentColorKey[]] +const backgroundThemeIds = Object.keys(BACKGROUND_THEMES) as [ + BackgroundThemeKey, + ...BackgroundThemeKey[], +] + +export const AppSettingsSchema = v.object({ + relativeDates: v.boolean(), + includeTypesInInstall: v.boolean(), + accentColorId: v.nullable(v.picklist(accentColorIds)), + preferredBackgroundTheme: v.nullable(v.picklist(backgroundThemeIds)), + hidePlatformPackages: v.boolean(), + selectedLocale: v.nullable(v.string()), + sidebar: v.object({ + collapsed: v.array(v.string()), + }), +}) + +export type AppSettings = v.InferOutput +export type AccentColorId = AppSettings['accentColorId'] +export type BackgroundThemeId = AppSettings['preferredBackgroundTheme'] From c3633986034d54842436de3aa14fa61486b79773 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 6 Feb 2026 23:50:59 -0600 Subject: [PATCH 2/4] set settings --- app/composables/useSettings.ts | 2 +- app/pages/settings.vue | 5 ++++- shared/schemas/app-settings.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts index 1f8907229..80d5fb500 100644 --- a/app/composables/useSettings.ts +++ b/app/composables/useSettings.ts @@ -5,6 +5,7 @@ import { BACKGROUND_THEMES } from '#shared/utils/constants' import type { AccentColorId, BackgroundThemeId, AppSettings } from '#shared/schemas/app-settings' const DEFAULT_SETTINGS: AppSettings = { + theme: 'system', relativeDates: false, includeTypesInInstall: true, accentColorId: null, @@ -116,7 +117,6 @@ export function useBackgroundTheme() { document.documentElement.removeAttribute('data-bg-theme') } settings.value.preferredBackgroundTheme = id - syncSettings(settings.value) } return { diff --git a/app/pages/settings.vue b/app/pages/settings.vue index 33e76be99..62d1e6bfc 100644 --- a/app/pages/settings.vue +++ b/app/pages/settings.vue @@ -1,5 +1,6 @@