Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add configurable auto-approval #1190

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1944,6 +1944,11 @@ components:
properties:
id:
type: string
AutoApprovalRule:
type: object
properties:
id:
type: string
securitySchemes:
cookieAuth:
type: apiKey
Expand Down Expand Up @@ -7042,6 +7047,21 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/OverrideRule'
/autoApprovalRule:
get:
summary: 'Get autoapproval rules'
description: 'Returns a list of all autoapproval rules with their conditions'
tags:
- 'autoapprovalrule'
responses:
'200':
description: 'Values were successfully created'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AutoApprovalRule'
security:
- cookieAuth: []
- apiKey: []
11 changes: 11 additions & 0 deletions server/entity/AutoApproval/AutoApprovalSpecificationBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class AutoApprovalSpecificationBase {
public implementationName: string;
public comparator: string;
public value: unknown;

isSatisfiedBy(): boolean {
return false;
}
}

export default AutoApprovalSpecificationBase;
19 changes: 19 additions & 0 deletions server/entity/AutoApproval/GenreSpecification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import AutoApprovalSpecificationBase from '@server/entity/AutoApproval/AutoApprovalSpecificationBase';

class GenreSpecification extends AutoApprovalSpecificationBase {
public implementationName = 'genre';
public isSatisfiedBy(): boolean {
return false;
}

public value: string;

public comparator = 'is';

Check warning

Code scanning / CodeQL

Useless assignment to property Warning

This write to property 'comparator' is useless, since
another property write
always overrides it.
public constructor(comparator?: string, value?: string) {
super();
this.comparator = comparator ?? 'is';
this.value = value ?? '';
}
}

export default GenreSpecification;
43 changes: 43 additions & 0 deletions server/entity/AutoApproval/ReleaseYearSpecification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AutoApprovalSpecificationBase } from '@server/entity/AutoApproval/AutoApprovalSpecificationBase';

class ReleaseYearSpecification extends AutoApprovalSpecificationBase {
public implementationName = 'releaseyear';
public comparator = 'equals';

Check warning

Code scanning / CodeQL

Useless assignment to property Warning

This write to property 'comparator' is useless, since
another property write
always overrides it.
public value: string;
public options = [
{ value: 'equals', text: '=' },
{ value: 'equalsnot', text: '!=' },
{ value: 'greaterthan', text: '>' },
{ value: 'lessthan', text: '<' },
];
public optionsContent;
constructor(comparison: string, value?: string) {
super({});
this.comparator = comparison;
this.value = value ?? '';
this.optionsContent = this.options.map((option) => (
<option key={'condition-release'} value={option.value}>
{option.text}
</option>
));
}
public Component = () => {
return (
<>
<select
key="mykey"
id="comparison-type"
name="comparison-type"
className="rounded-r-only"
defaultValue={this.comparator}
>
{this.optionsContent}
{}
</select>
<input type="text"></input>
</>
);
};
}

export default ReleaseYearSpecification;
17 changes: 17 additions & 0 deletions server/entity/AutoApproval/UserSpecification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import AutoApprovalSpecificationBase from './AutoApprovalSpecificationBase';

class UserSpecification extends AutoApprovalSpecificationBase {
public implementationName = 'user';
public isSatisfiedBy(): boolean {
return false;
}
public value: number;
public comparator: string;
public constructor(comparator?: string, value?: number) {
super();
this.comparator = comparator ?? 'is';
this.value = value ?? 0;
}
}

export default UserSpecification;
5 changes: 5 additions & 0 deletions server/lib/autoapproval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type AutoApprovalSpecificationBase from '@server/entity/AutoApproval/AutoApprovalSpecificationBase';
export interface AutoApprovalRule {
name: string;
conditions: AutoApprovalSpecificationBase[];
}
27 changes: 27 additions & 0 deletions server/routes/autoApprovalRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import GenreSpecification from '@server/entity/AutoApproval/GenreSpecification';
import UserSpecification from '@server/entity/AutoApproval/UserSpecification';
import type { AutoApprovalRule } from '@server/lib/autoapproval';
import { isAuthenticated } from '@server/middleware/auth';
import { Router } from 'express';

const autoApprovalRuleRoutes = Router();

autoApprovalRuleRoutes.get('/', isAuthenticated(), async (req, res, next) => {
try {
const data = [
{
name: 'Test Rule',
conditions: [
new UserSpecification('is', 1),
new GenreSpecification('is', '16,14'),
],
},
];

return res.status(200).json(data as AutoApprovalRule[]);
} catch (e) {
next({ status: 404, message: e.message });
}
});

export default autoApprovalRuleRoutes;
6 changes: 6 additions & 0 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { checkUser, isAuthenticated } from '@server/middleware/auth';
import { mapWatchProviderDetails } from '@server/models/common';
import { mapProductionCompany } from '@server/models/Movie';
import { mapNetwork } from '@server/models/Tv';
import autoApprovalRuleRoutes from '@server/routes/autoApprovalRule';
import overrideRuleRoutes from '@server/routes/overrideRule';
import settingsRoutes from '@server/routes/settings';
import watchlistRoutes from '@server/routes/watchlist';
Expand Down Expand Up @@ -166,6 +167,11 @@ router.use(
isAuthenticated(Permission.ADMIN),
overrideRuleRoutes
);
router.use(
'/autoApprovalRule',
isAuthenticated(Permission.ADMIN),
autoApprovalRuleRoutes
);

router.get('/regions', isAuthenticated(), async (req, res, next) => {
const tmdb = new TheMovieDb();
Expand Down
1 change: 1 addition & 0 deletions src/components/Layout/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const menuMessages = defineMessages('components.Layout.Sidebar', {
blacklist: 'Blacklist',
issues: 'Issues',
users: 'Users',
autoapproval: 'Auto Approval',
settings: 'Settings',
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Table from '@app/components/Common/Table';
import { GenreSelector } from '@app/components/Selector';

interface GenreSpecificationItemProps {
currentValue: string;
isMovie: boolean;
comparator: string;
}
export default function GenreSpecificationItem({
currentValue,
isMovie,
comparator,
}: GenreSpecificationItemProps) {
const comparatorOptions = [
{ label: 'is', value: 'is' },
{ label: 'is not', value: 'isnot' },
{ label: 'contains', value: 'contains' },
{ label: 'does not contain', value: 'containsnot' },
];
const comparatorItems = comparatorOptions.map((item) => (
<option value={item.value}>{item.label}</option>
));
return (
<>
<Table.TD>
<select
id="condition-type"
name="condition-type"
className=""
defaultValue={comparator}
>
{comparatorItems}
</select>
</Table.TD>
<Table.TD>
<GenreSelector
type={isMovie ? 'movie' : 'tv'}
isMulti
defaultValue={currentValue}
onChange={() => {
return;
}}
/>
</Table.TD>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Table from '@app/components/Common/Table';
import { UserSelector } from '@app/components/Selector';

interface UserSpecificationItemProps {
currentValue: number;
comparator: string;
}

export default function UserSpecificationItem({
currentValue,
comparator,
}: UserSpecificationItemProps) {
const comparatorItems = [
{ label: 'is', value: 'is' },
{ label: 'is not', value: 'isnot' },
{ label: 'contains', value: 'contains' },
{ label: 'does not contain', value: 'containsnot' },
].map((item) => <option value={item.value}>{item.label}</option>);
return (
<>
<Table.TD>
<select
id="condition-type"
name="condition-type"
className=""
defaultValue={comparator}
>
{comparatorItems}
</select>
</Table.TD>
<Table.TD>
<UserSelector
isMulti
defaultValue={currentValue.toString()}
onChange={() => {
return;
}}
/>
</Table.TD>
</>
);
}
Loading
Loading