Skip to content

Commit

Permalink
chore: release 4.4.0 (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
RaunoT authored Oct 13, 2024
2 parents 5773b4b + c3c94cc commit 2668327
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 240 deletions.
2 changes: 1 addition & 1 deletion src/app/dashboard/_components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import DashboardFilters from './DashboardFilters'

type Props = {
title: string
items?: TautulliItemRow[]
items?: TautulliItemRow[] | null
totalDuration?: string
totalSize?: string | number
type: 'movie' | 'show' | 'artist' | 'users'
Expand Down
205 changes: 4 additions & 201 deletions src/app/dashboard/users/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { authOptions } from '@/lib/auth'
import { DashboardSearchParams } from '@/types/dashboard'
import { Settings } from '@/types/settings'
import { TautulliItem, TautulliItemRow, TautulliUser } from '@/types/tautulli'
import {
fetchOverseerrStats,
fetchOverseerrUserId,
} from '@/utils/fetchOverseerr'
import fetchTautulli, { getLibrariesByType } from '@/utils/fetchTautulli'
import { fetchOverseerrStats } from '@/utils/fetchOverseerr'
import fetchTautulli, { getUsersCount } from '@/utils/fetchTautulli'
import { secondsToTime, timeToSeconds } from '@/utils/formatting'
import getPeriod from '@/utils/getPeriod'
import getSettings from '@/utils/getSettings'
import getUsersTop from '@/utils/getUsersTop'
import { Metadata } from 'next'
import { getServerSession } from 'next-auth'
import { notFound } from 'next/navigation'
Expand All @@ -21,176 +18,6 @@ export const metadata: Metadata = {
title: 'Users',
}

type UserRequestCounts =
| {
requests: number
}
| undefined

async function getInactiveUserInTimePeriod(
id: string,
): Promise<TautulliItemRow> {
const user = await fetchTautulli<TautulliItemRow>('get_user', {
user_id: id,
})
const nonActive = user?.response?.data ?? ({} as TautulliItemRow)

nonActive.total_duration = 0

return nonActive
}

async function getUsers(
loggedInUserId: string,
period: number,
requestsPeriod: string,
periodString: string,
settings: Settings,
) {
const numberOfUsers = 6
const allUsersCount = await getUsersCount(settings)

if (!allUsersCount) {
console.error('[TAUTULLI] - Could not determine the number of users.')

return
}

const usersRes = await fetchTautulli<TautulliItem>('get_home_stats', {
stat_id: 'top_users',
stats_count: allUsersCount,
stats_type: 'duration',
time_range: period,
})
const users = usersRes?.response?.data?.rows

if (!users) {
return
}

const isAnonymousAccess = settings.general.isOutsideAccess && !loggedInUserId
const listedUsers = isAnonymousAccess
? users.slice(0, numberOfUsers)
: await getStatsWithLoggedInUser(loggedInUserId, users, numberOfUsers)
const [moviesLib, showsLib, audioLib] = await Promise.all([
getLibrariesByType('movie'),
getLibrariesByType('show'),
getLibrariesByType('artist'),
])
const isOverseerrActive =
settings.connection.overseerrUrl && settings.connection.overseerrApiKey

let usersRequestsCounts: UserRequestCounts[] = []

if (isOverseerrActive) {
const overseerrUserIds = await Promise.all(
listedUsers.map(
async (user) => await fetchOverseerrUserId(String(user.user_id)),
),
)

usersRequestsCounts = await Promise.all(
overseerrUserIds.map(async (overseerrId) => {
if (overseerrId) {
const userTotal = await fetchOverseerrStats(
`user/${overseerrId}/requests`,
requestsPeriod,
)

return {
requests: userTotal.length,
}
}
}),
)
}

const usersPlaysAndDurations = await Promise.all(
listedUsers.map(async (user) => {
let moviesPlaysCount = 0
let showsPlaysCount = 0
let audioPlaysCount = 0

for (const movieLib of moviesLib) {
const userMovies = await fetchTautulli<{ recordsFiltered: number }>(
'get_history',
{
user_id: user.user_id,
after: periodString,
section_id: movieLib.section_id,
},
)

moviesPlaysCount += userMovies?.response?.data?.recordsFiltered || 0
}

for (const showLib of showsLib) {
const userShows = await fetchTautulli<{ recordsFiltered: number }>(
'get_history',
{
user_id: user.user_id,
after: periodString,
section_id: showLib.section_id,
},
)

showsPlaysCount += userShows?.response?.data?.recordsFiltered || 0
}

for (const audioLibItem of audioLib) {
const userAudio = await fetchTautulli<{ recordsFiltered: number }>(
'get_history',
{
user_id: user.user_id,
after: periodString,
section_id: audioLibItem.section_id,
},
)

audioPlaysCount += userAudio?.response?.data?.recordsFiltered || 0
}

return {
movies_plays_count: moviesPlaysCount,
shows_plays_count: showsPlaysCount,
audio_plays_count: audioPlaysCount,
}
}),
)

listedUsers.map((user, i) => {
user.requests = usersRequestsCounts[i]?.requests || 0
user.movies_plays_count = usersPlaysAndDurations[i].movies_plays_count
user.shows_plays_count = usersPlaysAndDurations[i].shows_plays_count
user.audio_plays_count = usersPlaysAndDurations[i].audio_plays_count
})

return listedUsers
}

async function getStatsWithLoggedInUser(
userId: string,
users: TautulliItemRow[],
numberOfUsers: number,
) {
const loggedInUserRank = users.findIndex(
(user) => String(user.user_id) == userId,
)
const loggedInUser =
users[loggedInUserRank] || (await getInactiveUserInTimePeriod(userId))

let slicedUsers = users.slice(0, numberOfUsers)

if (loggedInUserRank === -1 || loggedInUserRank >= numberOfUsers) {
slicedUsers = users.slice(0, numberOfUsers - 1)
loggedInUser.rank =
loggedInUserRank === -1 ? users.length : loggedInUserRank
slicedUsers.push(loggedInUser)
}

return slicedUsers
}

async function getTotalDuration(period: string, settings: Settings) {
if (settings.dashboard.activeTotalStatistics.includes('duration')) {
const totalDuration = await fetchTautulli<{ total_duration: string }>(
Expand All @@ -209,24 +36,6 @@ async function getTotalDuration(period: string, settings: Settings) {
return undefined
}

async function getUsersCount(settings: Settings) {
if (settings.dashboard.activeTotalStatistics.includes('count')) {
const usersRes = await fetchTautulli<TautulliUser[]>('get_users')

let users = usersRes?.response?.data

if (users) {
users = users.filter(
(user) => user.is_active && user.username !== 'Local',
)
}

return users?.length
}

return undefined
}

async function getTotalRequests(period: string, settings: Settings) {
const isOverseerrActive =
settings.connection.overseerrUrl && settings.connection.overseerrApiKey
Expand Down Expand Up @@ -259,13 +68,7 @@ async function DashboardUsersContent({ searchParams }: Props) {
const period = getPeriod(searchParams, settings)
const [usersData, totalDuration, usersCount, totalRequests] =
await Promise.all([
getUsers(
loggedInUserId,
period.daysAgo,
period.date,
period.string,
settings,
),
getUsersTop(loggedInUserId, period.string, period.daysAgo),
getTotalDuration(period.string, settings),
getUsersCount(settings),
getTotalRequests(period.date, settings),
Expand Down
4 changes: 4 additions & 0 deletions src/app/rewind/_components/RewindStories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import StoryRequests from './Stories/Requests'
import StoryShows from './Stories/Shows'
import StoryShowsTop from './Stories/ShowsTop'
import StoryTotal from './Stories/Total'
import StoryUsersTop from './Stories/UsersTop'
import StoryWelcome from './Stories/Welcome'

type Story = {
Expand Down Expand Up @@ -66,6 +67,9 @@ export default function RewindStories({ userRewind, settings }: Props) {
const stories = [
createStory(StoryWelcome, 7000),
createStory(StoryTotal, 8000),
...(userRewind.usersTop && userRewind.usersTop.length > 1
? [createStory(StoryUsersTop, 9000)]
: []),
...(userRewind.libraries_total_size && isLibrariesSizeAndCountActive
? [createStory(StoryLibraries, 9000)]
: []),
Expand Down
2 changes: 1 addition & 1 deletion src/app/rewind/_components/Stories/Libraries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function StoryLibraries({
<StoryWrapper isPaused={isPaused} pause={pause} resume={resume}>
<RewindStat isPaused={isPaused} scaleDelay={3}>
<p>
But, did you know the{' '}
Did you know the{' '}
<span className='rewind-cat'>
Filesize
<FolderIcon />
Expand Down
38 changes: 38 additions & 0 deletions src/app/rewind/_components/Stories/UsersTop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import MediaItems from '@/components/MediaItem/MediaItems'
import { RewindStory } from '@/types/rewind'
import { UsersIcon } from '@heroicons/react/24/outline'
import RewindStat from '../RewindStat'
import StoryWrapper from '../StoryWrapper'

export default function StoryUsersTop({
userRewind,
isPaused,
pause,
resume,
settings,
}: RewindStory) {
return (
<StoryWrapper isPaused={isPaused} pause={pause} resume={resume}>
<RewindStat noScale>
<p className='mb-2'>
Here&apos;s how you compared to other{''}
<span className='rewind-cat'>
Users
<UsersIcon />
</span>
</p>

<div className='text-base not-italic'>
<MediaItems
type='users'
items={userRewind.usersTop!}
serverId={userRewind.server_id}
rows
settings={settings}
loggedInUserId={userRewind.user.id}
/>
</div>
</RewindStat>
</StoryWrapper>
)
}
6 changes: 6 additions & 0 deletions src/app/rewind/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
getlibrariesTotalSize,
} from '@/utils/getRewind'
import getSettings from '@/utils/getSettings'
import getUsersTop from '@/utils/getUsersTop'
import { getRewindDateRange } from '@/utils/helpers'
import { getServerSession } from 'next-auth'
import { notFound } from 'next/navigation'
import { Suspense } from 'react'
Expand Down Expand Up @@ -59,20 +61,23 @@ async function RewindContent({ searchParams }: Props) {
}

const libraries = await getLibraries()
const { startDate, endDate } = getRewindDateRange(settings)
const [
topMediaItems,
topMediaStats,
userTotalDuration,
librariesTotalSize,
librariesTotalDuration,
serverId,
usersTop,
] = await Promise.all([
getTopMediaItems(user.id, libraries),
getTopMediaStats(user.id, libraries),
getUserTotalDuration(user.id, libraries),
getlibrariesTotalSize(libraries),
getLibrariesTotalDuration(libraries),
getServerId(),
getUsersTop(user.id, startDate, 0, endDate),
])
const userRewind: UserRewind = {
duration: {
Expand All @@ -82,6 +87,7 @@ async function RewindContent({ searchParams }: Props) {
)}%`,
total: secondsToTime(librariesTotalDuration),
},
usersTop: usersTop,
shows: {
top: topMediaItems.shows,
count: topMediaStats.shows.count,
Expand Down
2 changes: 2 additions & 0 deletions src/app/settings/general/_actions/updateGeneralSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const schema = z.object({
isPostersTmdbOnly: z.boolean(),
googleAnalyticsId: z.string(),
isOutsideAccess: z.boolean(),
isAnonymized: z.boolean(),
complete: z.boolean(),
})

Expand All @@ -21,6 +22,7 @@ export default async function saveGeneralSettings(
isPostersTmdbOnly: formData.get('isPostersTmdbOnly') === 'on',
googleAnalyticsId: formData.get('googleAnalyticsId') as string,
isOutsideAccess: formData.get('isOutsideAccess') === 'on',
isAnonymized: formData.get('isAnonymized') === 'on',
complete: true,
}

Expand Down
11 changes: 11 additions & 0 deletions src/app/settings/general/_components/GeneralSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ export default function GeneralSettingsForm({ settings, libraries }: Props) {
<small>Access without login.</small>
</span>
</Switch>
<Switch
className='switch items-start'
name='isAnonymized'
defaultSelected={generalSettings.isAnonymized}
>
<div className='indicator'></div>
<span className='label'>
<span className='label-wrapper'>Anonymize</span>
<small>Hide usernames for other users.</small>
</span>
</Switch>
</section>
<section className='group-settings group'>
<h2 className='heading-settings'>Analytics</h2>
Expand Down
Loading

0 comments on commit 2668327

Please sign in to comment.