Skip to content

Commit

Permalink
fix: settings handling on missing keys
Browse files Browse the repository at this point in the history
  • Loading branch information
RaunoT committed Jul 30, 2024
1 parent bcaa9aa commit 75261c4
Show file tree
Hide file tree
Showing 20 changed files with 70 additions and 82 deletions.
1 change: 1 addition & 0 deletions config/settings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"connection": {
"tautulliUrl": "",
"tautulliApiKey": "",
"plexUrl": "",
"overseerrUrl": "",
"overseerrApiKey": ""
},
Expand Down
2 changes: 1 addition & 1 deletion src/actions/update-connection-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export async function saveConnectionSettings(

// Save settings
try {
const settings = await getSettings()
const settings = getSettings()
schema.parse(data)
settings.connection = data
settings.test = true
Expand Down
2 changes: 1 addition & 1 deletion src/actions/update-feature-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function saveFeaturesSettings(

// Save settings
try {
const settings = await getSettings()
const settings = getSettings()

schema.parse(data)
settings.features = data
Expand Down
5 changes: 3 additions & 2 deletions src/app/_components/AppProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import { Settings, Version } from '@/types'
import { checkRequiredSettings } from '@/utils/helpers'
import {
ArrowPathIcon,
CogIcon,
Expand Down Expand Up @@ -29,7 +30,7 @@ export default function AppProvider({ children, settings, version }: Props) {
setIsSettings(pathname.startsWith('/settings'))
}, [pathname])

if (!settings.test && pathname !== '/settings/connection') {
if (!checkRequiredSettings(settings) && pathname !== '/settings/connection') {
redirect('/settings/connection')
}

Expand Down Expand Up @@ -65,7 +66,7 @@ export default function AppProvider({ children, settings, version }: Props) {
<ArrowPathIcon className='size-6' />
</a>
)}
{settings.test && session?.user?.isAdmin && (
{checkRequiredSettings(settings) && session?.user?.isAdmin && (
<Link
href={isSettings ? '/' : '/settings/features'}
aria-label={isSettings ? 'Close settings' : 'Open settings'}
Expand Down
2 changes: 1 addition & 1 deletion src/app/dashboard/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function DashboardContent({ params, searchParams }: Props) {
const library = libraries.find(
(library) => kebabCase(library.section_name) === params.slug,
)
const settings = await getSettings()
const settings = getSettings()

if (!library || !library.is_active) {
return notFound()
Expand Down
2 changes: 1 addition & 1 deletion src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Props = {
}

export default async function DashboardLayout({ children }: Props) {
const settings = await getSettings()
const settings = getSettings()

if (!settings.features.isDashboardActive) {
return notFound()
Expand Down
4 changes: 2 additions & 2 deletions src/app/dashboard/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function getUsers(
return
}

const settings = await getSettings()
const settings = getSettings()
const [moviesLib, showsLib, audioLib] = await Promise.all([
getLibrariesByType('movie'),
getLibrariesByType('show'),
Expand Down Expand Up @@ -184,7 +184,7 @@ type Props = {
}

async function DashboardUsersContent({ searchParams }: Props) {
const settings = await getSettings()
const settings = getSettings()

if (!settings.features.isUsersPageActive) {
return notFound()
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type Props = {
}

export default async function RootLayout({ children }: Props) {
const settings = await getSettings()
const settings = getSettings()
const version = await getVersion()

return (
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import getSettings from '@/utils/getSettings'
import { Suspense } from 'react'
import Home from './_components/Home'

export default async function HomePage() {
const settings = await getSettings()
export default function HomePage() {
const settings = getSettings()

return (
<Suspense>
Expand Down
4 changes: 2 additions & 2 deletions src/app/rewind/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ type Props = {
children: ReactNode
}

export default async function RewindLayout({ children }: Props) {
const settings = await getSettings()
export default function RewindLayout({ children }: Props) {
const settings = getSettings()

if (!settings.features.isRewindActive) {
return notFound()
Expand Down
2 changes: 1 addition & 1 deletion src/app/rewind/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Props = {

export default async function RewindPage({ searchParams }: Props) {
const session = await getServerSession(authOptions)
const settings = await getSettings()
const settings = getSettings()

if (!session?.user) {
return
Expand Down
4 changes: 2 additions & 2 deletions src/app/settings/connection/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const metadata: Metadata = {
title: 'Connection settings',
}

export default async function ConnectionSettingsPage() {
const settings = await getSettings()
export default function ConnectionSettingsPage() {
const settings = getSettings()

return <ConnectionSettingsForm settings={settings} />
}
2 changes: 1 addition & 1 deletion src/app/settings/features/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const metadata: Metadata = {

export default async function FeaturesSettingsPage() {
const libraries = await getLibraries(false)
const settings = await getSettings()
const settings = getSettings()

return <FeaturesSettingsForm settings={settings} libraries={libraries} />
}
9 changes: 6 additions & 3 deletions src/app/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import githubSvg from '@/assets/github.svg'
import { authOptions } from '@/lib/auth'
import getSettings from '@/utils/getSettings'
import getVersion from '@/utils/getVersion'
import { checkRequiredSettings } from '@/utils/helpers'
import { CurrencyEuroIcon, HeartIcon } from '@heroicons/react/24/outline'
import { getServerSession } from 'next-auth'
import Image from 'next/image'
Expand All @@ -16,7 +17,7 @@ type Props = {

export default async function SettingsLayout({ children }: Props) {
const session = await getServerSession(authOptions)
const settings = await getSettings()
const settings = getSettings()
const version = await getVersion()

if (!session?.user?.isAdmin && settings.test) {
Expand All @@ -26,10 +27,12 @@ export default async function SettingsLayout({ children }: Props) {
return (
<div className='mb-auto w-full max-w-screen-sm'>
<PageTitle
title={settings.test ? 'Settings' : "Let's get started"}
title={
checkRequiredSettings(settings) ? 'Settings' : "Let's get started"
}
noBack
/>
{settings.test && <SettingsNav />}
{checkRequiredSettings(settings) && <SettingsNav />}
{children}

<div className='glass-sheet mt-4 flex flex-col flex-wrap justify-between gap-3 py-4 sm:flex-row'>
Expand Down
6 changes: 6 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,11 @@ export const DEFAULT_SETTINGS: Settings = {
},
test: false,
}
export const REQUIRED_SETTINGS = [
'connection.tautulliUrl',
'connection.tautulliApiKey',
'connection.plexUrl',
'test',
]

export const TMDB_API_KEY = '4675b5b5d8cd1463ff16adca2680157b'
2 changes: 1 addition & 1 deletion src/utils/fetchOverseerr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default async function fetchOverseerr<T>(
endpoint: string,
cache: boolean = false,
): Promise<T | null> {
const settings = await getSettings()
const settings = getSettings()

const overseerrUrl = settings.connection.overseerrUrl
const apiKey = settings.connection.overseerrApiKey
Expand Down
6 changes: 3 additions & 3 deletions src/utils/fetchTautulli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default async function fetchTautulli<T>(
params?: QueryParams,
cache: boolean = false,
): Promise<TautulliResponse<T> | null> {
const settings = await getSettings()
const settings = getSettings()
const tautulliUrl = settings.connection.tautulliUrl
const apiKey = settings.connection.tautulliApiKey

Expand Down Expand Up @@ -62,7 +62,7 @@ export default async function fetchTautulli<T>(
}

export async function getServerId(): Promise<string> {
const settings = await getSettings()
const settings = getSettings()
const plexUrl = new URL(settings.connection.plexUrl)
const plexHost = plexUrl.hostname
const plexPort = plexUrl.port
Expand All @@ -87,7 +87,7 @@ export async function getServerId(): Promise<string> {
}

export async function getLibraries(excludeInactive = true): Promise<Library[]> {
const settings = await getSettings()
const settings = getSettings()
const activeLibraries = settings.features.activeLibraries
const libraries = await fetchTautulli<Library[]>('get_libraries')

Expand Down
2 changes: 1 addition & 1 deletion src/utils/getMediaAdditionalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default async function getMediaAdditionalData(
)
}

const settings = await getSettings()
const settings = getSettings()
const tautulliUrl = settings.connection.tautulliUrl
const isPostersTmdbOnly = settings.features.isPostersTmdbOnly

Expand Down
80 changes: 23 additions & 57 deletions src/utils/getSettings.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,34 @@
import { Settings } from '@/types'
import { promises as fs } from 'fs'
import fs from 'fs'
import { merge } from 'lodash'
import { DEFAULT_SETTINGS, SETTINGS_PATH } from './constants'

export default async function getSettings(): Promise<Settings> {
try {
// Attempt to read the file
const file = await fs.readFile(SETTINGS_PATH, 'utf8')
const settings: Partial<Settings> = JSON.parse(file)
let updated = false

// Ensure connection settings
const connectionSettings = {
...DEFAULT_SETTINGS.connection,
...settings.connection,
}
if (
JSON.stringify(settings.connection) !== JSON.stringify(connectionSettings)
) {
settings.connection = connectionSettings
updated = true
}

// Ensure features settings
const featuresSettings = {
...DEFAULT_SETTINGS.features,
...settings.features,
}
if (
JSON.stringify(settings.features) !== JSON.stringify(featuresSettings)
) {
settings.features = featuresSettings
updated = true
}
export default function getSettings(): Settings {
if (!fs.existsSync(SETTINGS_PATH)) {
writeSettings(DEFAULT_SETTINGS)
}

// Ensure test setting
if (settings.test === undefined) {
settings.test = DEFAULT_SETTINGS.test
updated = true
}
let settings: Settings

if (updated) {
await writeSettings(settings as Settings)
}
try {
const fileContent = fs.readFileSync(SETTINGS_PATH, 'utf-8')
settings = JSON.parse(fileContent)
} catch (error) {
console.error('[SETTINGS] - Error reading or parsing settings file!', error)
settings = { ...DEFAULT_SETTINGS }
writeSettings(settings)
return settings
}

return settings as Settings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
// If reading fails because the file does not exist, create the file with default settings
if (error.code === 'ENOENT') {
console.warn('[SETTINGS] - File not found! Creating a new one...')
await writeSettings(DEFAULT_SETTINGS)
const updatedSettings = merge({}, DEFAULT_SETTINGS, settings)

return DEFAULT_SETTINGS
} else {
throw new Error('[SETTINGS] - Unable to read file!')
}
if (JSON.stringify(updatedSettings) !== JSON.stringify(settings)) {
writeSettings(updatedSettings)
}

return updatedSettings
}

async function writeSettings(settings: Settings): Promise<void> {
try {
await fs.writeFile(SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
throw new Error('[SETTINGS] - Unable to write file!')
}
function writeSettings(settings: Settings): void {
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, undefined, 2))
}
11 changes: 11 additions & 0 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Settings } from '@/types'
import { REQUIRED_SETTINGS } from './constants'

export function checkRequiredSettings(settings: Settings): boolean {
return REQUIRED_SETTINGS.every((key) => {
const keys = key.split('.')

// @ts-expect-error - we know this is safe, but should still look to resolve without exception
return keys.reduce((acc, curr) => acc && acc[curr], settings)
})
}

0 comments on commit 75261c4

Please sign in to comment.