mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(requests): add request quotas (#1277)
* feat(quotas): rebased * feat: add getQuota() method to User entity * feat(ui): add default quota setting options * feat: user quota settings * feat: quota display in request modals * fix: only show user quotas on own profile or with manage users permission * feat: add request progress circles to profile page * feat: add migration * fix: add missing restricted field to api schema * fix: dont show auto approve message for movie request when restricted * fix(lang): change enable checkbox langauge to "enable override" Co-authored-by: Jakob Ankarhem <jakob.ankarhem@outlook.com> Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
This commit is contained in:
@@ -1,28 +1,34 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToMany,
|
||||
RelationCount,
|
||||
AfterLoad,
|
||||
OneToOne,
|
||||
} from 'typeorm';
|
||||
import {
|
||||
Permission,
|
||||
hasPermission,
|
||||
PermissionCheckOptions,
|
||||
} from '../lib/permissions';
|
||||
import { MediaRequest } from './MediaRequest';
|
||||
import bcrypt from 'bcrypt';
|
||||
import path from 'path';
|
||||
import PreparedEmail from '../lib/email';
|
||||
import logger from '../logger';
|
||||
import { getSettings } from '../lib/settings';
|
||||
import { default as generatePassword } from 'secure-random-password';
|
||||
import { UserType } from '../constants/user';
|
||||
import {
|
||||
AfterLoad,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
getRepository,
|
||||
MoreThan,
|
||||
Not,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
RelationCount,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { MediaRequestStatus, MediaType } from '../constants/media';
|
||||
import { UserType } from '../constants/user';
|
||||
import { QuotaResponse } from '../interfaces/api/userInterfaces';
|
||||
import PreparedEmail from '../lib/email';
|
||||
import {
|
||||
hasPermission,
|
||||
Permission,
|
||||
PermissionCheckOptions,
|
||||
} from '../lib/permissions';
|
||||
import { getSettings } from '../lib/settings';
|
||||
import logger from '../logger';
|
||||
import { MediaRequest } from './MediaRequest';
|
||||
import SeasonRequest from './SeasonRequest';
|
||||
import { UserSettings } from './UserSettings';
|
||||
|
||||
@Entity()
|
||||
@@ -80,6 +86,18 @@ export class User {
|
||||
@OneToMany(() => MediaRequest, (request) => request.requestedBy)
|
||||
public requests: MediaRequest[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
public movieQuotaLimit?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public movieQuotaDays?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public tvQuotaLimit?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public tvQuotaDays?: number;
|
||||
|
||||
@OneToOne(() => UserSettings, (settings) => settings.user, {
|
||||
cascade: true,
|
||||
eager: true,
|
||||
@@ -199,4 +217,105 @@ export class User {
|
||||
public setDisplayName(): void {
|
||||
this.displayName = this.username || this.plexUsername;
|
||||
}
|
||||
|
||||
public async getQuota(): Promise<QuotaResponse> {
|
||||
const {
|
||||
main: { defaultQuotas },
|
||||
} = getSettings();
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
const canBypass = this.hasPermission([Permission.MANAGE_USERS], {
|
||||
type: 'or',
|
||||
});
|
||||
|
||||
const movieQuotaLimit = !canBypass
|
||||
? this.movieQuotaLimit ?? defaultQuotas.movie.quotaLimit
|
||||
: 0;
|
||||
const movieQuotaDays = this.movieQuotaDays ?? defaultQuotas.movie.quotaDays;
|
||||
|
||||
// Count movie requests made during quota period
|
||||
const movieDate = new Date();
|
||||
if (movieQuotaDays) {
|
||||
movieDate.setDate(movieDate.getDate() - movieQuotaDays);
|
||||
} else {
|
||||
movieDate.setDate(0);
|
||||
}
|
||||
// YYYY-MM-DD format
|
||||
const movieQuotaStartDate = movieDate.toJSON().split('T')[0];
|
||||
const movieQuotaUsed = movieQuotaLimit
|
||||
? await requestRepository.count({
|
||||
where: {
|
||||
requestedBy: this,
|
||||
createdAt: MoreThan(movieQuotaStartDate),
|
||||
type: MediaType.MOVIE,
|
||||
status: Not(MediaRequestStatus.DECLINED),
|
||||
},
|
||||
})
|
||||
: 0;
|
||||
|
||||
const tvQuotaLimit = !canBypass
|
||||
? this.tvQuotaLimit ?? defaultQuotas.tv.quotaLimit
|
||||
: 0;
|
||||
const tvQuotaDays = this.tvQuotaDays ?? defaultQuotas.tv.quotaDays;
|
||||
|
||||
// Count tv season requests made during quota period
|
||||
const tvDate = new Date();
|
||||
if (tvQuotaDays) {
|
||||
tvDate.setDate(tvDate.getDate() - tvQuotaDays);
|
||||
} else {
|
||||
tvDate.setDate(0);
|
||||
}
|
||||
// YYYY-MM-DD format
|
||||
const tvQuotaStartDate = tvDate.toJSON().split('T')[0];
|
||||
const tvQuotaUsed = tvQuotaLimit
|
||||
? (
|
||||
await requestRepository
|
||||
.createQueryBuilder('request')
|
||||
.leftJoin('request.seasons', 'seasons')
|
||||
.leftJoin('request.requestedBy', 'requestedBy')
|
||||
.where('request.type = :requestType', {
|
||||
requestType: MediaType.TV,
|
||||
})
|
||||
.andWhere('requestedBy.id = :userId', {
|
||||
userId: this.id,
|
||||
})
|
||||
.andWhere('request.createdAt > :date', {
|
||||
date: tvQuotaStartDate,
|
||||
})
|
||||
.andWhere('request.status != :declinedStatus', {
|
||||
declinedStatus: MediaRequestStatus.DECLINED,
|
||||
})
|
||||
.addSelect((subQuery) => {
|
||||
return subQuery
|
||||
.select('COUNT(season.id)', 'seasonCount')
|
||||
.from(SeasonRequest, 'season')
|
||||
.leftJoin('season.request', 'parentRequest')
|
||||
.where('parentRequest.id = request.id');
|
||||
}, 'seasonCount')
|
||||
.getMany()
|
||||
).reduce((sum: number, req: MediaRequest) => sum + req.seasonCount, 0)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
movie: {
|
||||
days: movieQuotaDays,
|
||||
limit: movieQuotaLimit,
|
||||
used: movieQuotaUsed,
|
||||
remaining: movieQuotaLimit
|
||||
? movieQuotaLimit - movieQuotaUsed
|
||||
: undefined,
|
||||
restricted:
|
||||
movieQuotaLimit && movieQuotaLimit - movieQuotaUsed <= 0
|
||||
? true
|
||||
: false,
|
||||
},
|
||||
tv: {
|
||||
days: tvQuotaDays,
|
||||
limit: tvQuotaLimit,
|
||||
used: tvQuotaUsed,
|
||||
remaining: tvQuotaLimit ? tvQuotaLimit - tvQuotaUsed : undefined,
|
||||
restricted:
|
||||
tvQuotaLimit && tvQuotaLimit - tvQuotaUsed <= 0 ? true : false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user