Skip to content

Commit

Permalink
Add option to treat soft matches as unmonitored (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasodonnell authored Feb 4, 2025
1 parent 5a26683 commit 8bb6e78
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Settings" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"radarrUrl" TEXT,
"radarrApiKey" TEXT,
"radarrAddImportListExclusion" BOOLEAN NOT NULL DEFAULT true,
"tautulliUrl" TEXT,
"tautulliApiKey" TEXT,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"syncDays" INTEGER DEFAULT 1,
"syncHour" INTEGER DEFAULT 3,
"treatSoftMatchAsUnmonitored" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Settings" ("createdAt", "enabled", "id", "radarrAddImportListExclusion", "radarrApiKey", "radarrUrl", "syncDays", "syncHour", "tautulliApiKey", "tautulliUrl", "updatedAt") SELECT "createdAt", "enabled", "id", "radarrAddImportListExclusion", "radarrApiKey", "radarrUrl", "syncDays", "syncHour", "tautulliApiKey", "tautulliUrl", "updatedAt" FROM "Settings";
DROP TABLE "Settings";
ALTER TABLE "new_Settings" RENAME TO "Settings";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
1 change: 1 addition & 0 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ model Settings {
enabled Boolean @default(false)
syncDays Int? @default(1)
syncHour Int? @default(3)
treatSoftMatchAsUnmonitored Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/movie/movie.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { Module } from '@nestjs/common'

import { PrismaService } from '../prisma.service'
import { RuleModule } from '../rule/rule.module'
import { SettingsModule } from '../settings/settings.module'

import { MovieController } from './movie.controller'
import { MovieService } from './movie.service'

@Module({
controllers: [MovieController],
exports: [MovieService],
imports: [RuleModule],
imports: [RuleModule, SettingsModule],
providers: [MovieService, PrismaService],
})
export class MovieModule {}
27 changes: 16 additions & 11 deletions apps/api/src/movie/movie.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Prisma } from '@prisma/client'
import { PrismaService } from '../prisma.service'
import { Rule } from '../rule/rule.model'
import { RuleService } from '../rule/rule.service'
import { SettingsService } from '../settings/settings.service'
import { Tag } from '../tag/tag.model'
import { daysSince } from '../util/daysSince'

Expand Down Expand Up @@ -51,6 +52,7 @@ export class MovieService {
constructor(
private prisma: PrismaService,
private rule: RuleService,
private settings: SettingsService,
) {}

private async findMany(
Expand Down Expand Up @@ -402,12 +404,15 @@ export class MovieService {

/**
* Returns a list of movies that match the provided rule
* @param opts.includeSoftMatch Include movies that match the rule will do not meet dynamic delete conditions (e.g. downloadedDaysAgo, watchedDaysAgo)
*/
async getForRule(
rule: Rule,
opts?: { ignoreDynamic: boolean },
opts?: { includeSoftMatch: boolean },
): Promise<Movie[]> {
const { ignoreDynamic = false } = opts || {}
const { includeSoftMatch = false } = opts || {}
const { treatSoftMatchAsUnmonitored } = await this.settings.getGeneral()
const ignoreDynamic = includeSoftMatch && !treatSoftMatchAsUnmonitored

try {
const {
Expand All @@ -426,34 +431,34 @@ export class MovieService {
appearsInList: !ignoreDynamic ? appearsInList ?? undefined : undefined,
deleted: false,
downloadedAt:
!ignoreDynamic && downloadedDaysAgo
!includeSoftMatch && downloadedDaysAgo
? {
lte: new Date(Date.now() - downloadedDaysAgo * ONE_DAY_MS),
lt: new Date(Date.now() - downloadedDaysAgo * ONE_DAY_MS),
}
: undefined,
ignored: false,
imdbRating:
!ignoreDynamic && minimumImdbRating
? {
lte: minimumImdbRating,
lt: minimumImdbRating,
}
: undefined,
lastWatchedAt:
!ignoreDynamic && watchedDaysAgo
? {
lte: new Date(Date.now() - watchedDaysAgo * ONE_DAY_MS),
lt: new Date(Date.now() - watchedDaysAgo * ONE_DAY_MS),
}
: undefined,
metacriticRating:
!ignoreDynamic && minimumMetacriticRating
? {
lte: minimumMetacriticRating,
lt: minimumMetacriticRating,
}
: undefined,
rottenTomatoesRating:
!ignoreDynamic && minimumRottenTomatoesRating
? {
lte: minimumRottenTomatoesRating,
lt: minimumRottenTomatoesRating,
}
: undefined,
tags: tags.length
Expand All @@ -468,10 +473,10 @@ export class MovieService {
tmdbRating:
!ignoreDynamic && minimumTmdbRating
? {
lte: minimumTmdbRating,
lt: minimumTmdbRating,
}
: undefined,
watched: !ignoreDynamic ? watched ?? undefined : undefined,
watched: !includeSoftMatch ? watched ?? undefined : undefined,
}

return await this.findMany({ where })
Expand Down Expand Up @@ -515,7 +520,7 @@ export class MovieService {

for (const rule of enabledRules) {
const records: Movie[] = await this.getForRule(rule, {
ignoreDynamic: true,
includeSoftMatch: true,
})
const movies = records.map((movie) => ({
...movie,
Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/settings/settings.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class Settings implements ISettings {
@IsOptional()
tautulliUrl: null | string

@ApiProperty()
@IsBoolean()
treatSoftMatchAsUnmonitored: boolean

@ApiProperty()
@IsDate()
updatedAt: Date
Expand All @@ -70,7 +74,12 @@ export class Settings implements ISettings {
}

export class GeneralSettings
extends PickType(Settings, ['enabled', 'syncDays', 'syncHour'])
extends PickType(Settings, [
'enabled',
'syncDays',
'syncHour',
'treatSoftMatchAsUnmonitored',
])
implements IGeneralSettings
{
constructor(partial: Partial<GeneralSettings>) {
Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/settings/settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class SettingsService implements OnModuleInit {
enabled: true,
syncDays: true,
syncHour: true,
treatSoftMatchAsUnmonitored: true,
}
readonly radarrSettingsSelect: Prisma.SettingsSelect = {
radarrAddImportListExclusion: true,
Expand Down Expand Up @@ -131,8 +132,9 @@ export class SettingsService implements OnModuleInit {
*/
async updateGeneral(settings: GeneralSettingsDTO): Promise<GeneralSettings> {
try {
const { enabled, syncDays, syncHour } = settings
const data = { enabled, syncDays, syncHour }
const { enabled, syncDays, syncHour, treatSoftMatchAsUnmonitored } =
settings
const data = { enabled, syncDays, syncHour, treatSoftMatchAsUnmonitored }

const record = await this.update<GeneralSettings>({
select: this.generalSettingsSelect,
Expand Down
11 changes: 11 additions & 0 deletions apps/client/src/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export type LabelPros = {
required?: boolean
}

export type HintProps = {
children: React.ReactNode
className?: string
}

export type SelectOption<T> = {
label: string
value: T
Expand Down Expand Up @@ -134,6 +139,12 @@ export function Label({
)
}

export function Hint({ children, className }: HintProps): JSX.Element {
return (
<div className={cx('text-xs italic text-gray', className)}>{children}</div>
)
}

export function Input({
className,
disabled = false,
Expand Down
17 changes: 17 additions & 0 deletions apps/client/src/views/settings/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Checkbox,
Field,
Form,
Hint,
Label,
NumberInput,
Select,
Expand Down Expand Up @@ -102,6 +103,22 @@ export default function General(): JSX.Element {
value={settings?.syncHour}
/>
</Field>
<Field>
<Label className="col-span-1">
Treat soft matches as unmonitored
<Hint className="mt-2">
Soft matches are movies that match a rule but cannot be given a
deletion date because of dynamic conditions such as watch status,
rating, etc.?
</Hint>
</Label>
<Checkbox
className="col-span-3"
disabled={isLoading}
onChange={setProperty('treatSoftMatchAsUnmonitored')}
value={settings?.treatSoftMatchAsUnmonitored}
/>
</Field>
<Actions>
<Button disabled={isLoading} type="submit">
Save
Expand Down
3 changes: 2 additions & 1 deletion packages/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ export type Settings = {
syncHour: number
tautulliApiKey: string | null
tautulliUrl: string | null
treatSoftMatchAsUnmonitored: boolean
updatedAt: Date
}

export type GeneralSettings = Pick<Settings, 'enabled' | 'syncDays' | 'syncHour'>
export type GeneralSettings = Pick<Settings, 'enabled' | 'syncDays' | 'syncHour' | 'treatSoftMatchAsUnmonitored'>
export type RadarrSettings = Pick<Settings, 'radarrApiKey' | 'radarrUrl' | 'radarrAddImportListExclusion'>
export type TautulliSettings = Pick<Settings, 'tautulliApiKey' | 'tautulliUrl'>

Expand Down

0 comments on commit 8bb6e78

Please sign in to comment.