Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6167011ee5 | ||
|
|
bc399d95a0 | ||
|
|
e1032ff5df | ||
|
|
e80920faa1 | ||
|
|
f7c0769deb | ||
|
|
78444a9e64 | ||
|
|
519dd17d37 | ||
|
|
0761593411 | ||
|
|
8927c6d2e3 | ||
|
|
81a96db2e7 |
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -63,6 +63,7 @@ jobs:
|
||||
discord:
|
||||
name: Send Discord Notification
|
||||
needs: build_and_push
|
||||
if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
|
||||
54
.github/workflows/release.yml
vendored
54
.github/workflows/release.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint & Test Build
|
||||
runs-on: ubuntu-20.04
|
||||
container: node:12.18-alpine
|
||||
steps:
|
||||
@@ -41,8 +42,61 @@ jobs:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: npx semantic-release
|
||||
build-snap:
|
||||
name: Build Snap Package (${{ matrix.architecture }})
|
||||
needs: semantic-release
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture:
|
||||
- amd64
|
||||
- arm64
|
||||
- armhf
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
git fetch --prune --unshallow --tags
|
||||
if [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then
|
||||
echo ::set-output name=RELEASE::stable
|
||||
else
|
||||
echo ::set-output name=RELEASE::edge
|
||||
fi
|
||||
|
||||
- name: Set Up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Build Snap Package
|
||||
uses: diddlesnaps/snapcraft-multiarch-action@v1
|
||||
id: build
|
||||
with:
|
||||
architecture: ${{ matrix.architecture }}
|
||||
|
||||
- name: Upload Snap Package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: overseerr-snap-package-${{ matrix.architecture }}
|
||||
path: ${{ steps.build.outputs.snap }}
|
||||
|
||||
- name: Review Snap Package
|
||||
uses: diddlesnaps/snapcraft-review-tools-action@v1
|
||||
with:
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
|
||||
- name: Publish Snap Package
|
||||
uses: snapcore/action-publish@v1
|
||||
with:
|
||||
store_login: ${{ secrets.SNAP_LOGIN }}
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
release: ${{ steps.prepare.outputs.RELEASE }}
|
||||
discord:
|
||||
name: Send Discord Notification
|
||||
needs: semantic-release
|
||||
if: always()
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
|
||||
4
.github/workflows/snap.yaml
vendored
4
.github/workflows/snap.yaml
vendored
@@ -3,8 +3,6 @@ name: Publish Snap
|
||||
on:
|
||||
push:
|
||||
branches: [develop]
|
||||
tags: [v*]
|
||||
pull_request: ~
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -74,10 +72,10 @@ jobs:
|
||||
store_login: ${{ secrets.SNAP_LOGIN }}
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
release: ${{ steps.prepare.outputs.RELEASE }}
|
||||
|
||||
discord:
|
||||
name: Send Discord Notification
|
||||
needs: build-snap
|
||||
if: always() && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,3 +1,18 @@
|
||||
## [1.17.2](https://github.com/sct/overseerr/compare/v1.17.1...v1.17.2) (2021-01-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **requests:** allow declined season requests to be re-requested ([e1032ff](https://github.com/sct/overseerr/commit/e1032ff5dfac4a8c9d4da9cf2788c19822343ad9)), closes [#690](https://github.com/sct/overseerr/issues/690)
|
||||
* **requests:** update requests to approved when parent media is set as available ([78444a9](https://github.com/sct/overseerr/commit/78444a9e643829823162389dee60cca70da56bff)), closes [#688](https://github.com/sct/overseerr/issues/688)
|
||||
|
||||
## [1.17.1](https://github.com/sct/overseerr/compare/v1.17.0...v1.17.1) (2021-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** show auto approval on series request modal only with correct permissions ([8927c6d](https://github.com/sct/overseerr/commit/8927c6d2e39dbda2b1121095a7273f5cab1c9b74)), closes [#687](https://github.com/sct/overseerr/issues/687)
|
||||
|
||||
# [1.17.0](https://github.com/sct/overseerr/compare/v1.16.0...v1.17.0) (2021-01-19)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "overseerr",
|
||||
"version": "1.17.0",
|
||||
"version": "1.17.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts",
|
||||
|
||||
@@ -151,6 +151,15 @@ export class MediaRequest {
|
||||
logger.error('No parent media!', { label: 'Media Request' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (media[this.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE) {
|
||||
logger.warn(
|
||||
'Media became available before request was approved. Approval notification will be skipped.',
|
||||
{ label: 'Media Request' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tmdb = new TheMovieDb();
|
||||
if (this.media.mediaType === MediaType.MOVIE) {
|
||||
const movie = await tmdb.getMovie({ movieId: this.media.tmdbId });
|
||||
@@ -205,7 +214,13 @@ export class MediaRequest {
|
||||
return;
|
||||
}
|
||||
const seasonRequestRepository = getRepository(SeasonRequest);
|
||||
if (this.status === MediaRequestStatus.APPROVED) {
|
||||
if (
|
||||
this.status === MediaRequestStatus.APPROVED &&
|
||||
// Do not update the status if the item is already partially available or available
|
||||
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE &&
|
||||
media[this.is4k ? 'status4k' : 'status'] !==
|
||||
MediaStatus.PARTIALLY_AVAILABLE
|
||||
) {
|
||||
if (this.is4k) {
|
||||
media.status4k = MediaStatus.PROCESSING;
|
||||
} else {
|
||||
@@ -358,6 +373,21 @@ export class MediaRequest {
|
||||
});
|
||||
const movie = await tmdb.getMovie({ movieId: this.media.tmdbId });
|
||||
|
||||
const media = await mediaRepository.findOne({
|
||||
where: { id: this.media.id },
|
||||
});
|
||||
|
||||
if (!media) {
|
||||
logger.error('Media not present');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
|
||||
) {
|
||||
throw new Error('Media already available');
|
||||
}
|
||||
|
||||
// Run this asynchronously so we don't wait for it on the UI side
|
||||
radarr
|
||||
.addMovie({
|
||||
@@ -373,13 +403,6 @@ export class MediaRequest {
|
||||
})
|
||||
.then(async (success) => {
|
||||
if (!success) {
|
||||
const media = await mediaRepository.findOne({
|
||||
where: { id: this.media.id },
|
||||
});
|
||||
if (!media) {
|
||||
logger.error('Media not present');
|
||||
return;
|
||||
}
|
||||
media.status = MediaStatus.UNKNOWN;
|
||||
await mediaRepository.save(media);
|
||||
logger.warn(
|
||||
@@ -464,6 +487,12 @@ export class MediaRequest {
|
||||
throw new Error('Media data is missing');
|
||||
}
|
||||
|
||||
if (
|
||||
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
|
||||
) {
|
||||
throw new Error('Media already available');
|
||||
}
|
||||
|
||||
const tmdb = new TheMovieDb();
|
||||
const sonarr = new SonarrAPI({
|
||||
apiKey: sonarrSettings.apiKey,
|
||||
|
||||
@@ -160,7 +160,11 @@ requestRoutes.post(
|
||||
// (Unless there are no seasons, in which case we abort)
|
||||
if (media.requests) {
|
||||
existingSeasons = media.requests
|
||||
.filter((request) => request.is4k === req.body.is4k)
|
||||
.filter(
|
||||
(request) =>
|
||||
request.is4k === req.body.is4k &&
|
||||
request.status !== MediaRequestStatus.DECLINED
|
||||
)
|
||||
.reduce((seasons, request) => {
|
||||
const combinedSeasons = request.seasons.map(
|
||||
(season) => season.seasonNumber
|
||||
@@ -303,7 +307,12 @@ requestRoutes.put<{ requestId: string }>(
|
||||
|
||||
// Get all requested seasons that are not part of this request we are editing
|
||||
const existingSeasons = media.requests
|
||||
.filter((r) => r.is4k === request.is4k && r.id !== request.id)
|
||||
.filter(
|
||||
(r) =>
|
||||
r.is4k === request.is4k &&
|
||||
r.id !== request.id &&
|
||||
r.status !== MediaRequestStatus.DECLINED
|
||||
)
|
||||
.reduce((seasons, r) => {
|
||||
const combinedSeasons = r.seasons.map(
|
||||
(season) => season.seasonNumber
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
UpdateEvent,
|
||||
} from 'typeorm';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import { MediaStatus, MediaType } from '../constants/media';
|
||||
import { MediaRequestStatus, MediaStatus, MediaType } from '../constants/media';
|
||||
import Media from '../entity/Media';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import Season from '../entity/Season';
|
||||
@@ -102,6 +102,24 @@ export class MediaSubscriber implements EntitySubscriberInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private async updateChildRequestStatus(event: Media, is4k: boolean) {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
const requests = await requestRepository.find({
|
||||
where: { media: event.id },
|
||||
});
|
||||
|
||||
for (const request of requests) {
|
||||
if (
|
||||
request.is4k === is4k &&
|
||||
request.status === MediaRequestStatus.PENDING
|
||||
) {
|
||||
request.status = MediaRequestStatus.APPROVED;
|
||||
await requestRepository.save(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public beforeUpdate(event: UpdateEvent<Media>): void {
|
||||
if (!event.entity) {
|
||||
return;
|
||||
@@ -121,5 +139,19 @@ export class MediaSubscriber implements EntitySubscriberInterface {
|
||||
) {
|
||||
this.notifyAvailableSeries(event.entity, event.databaseEntity);
|
||||
}
|
||||
|
||||
if (
|
||||
event.entity.status === MediaStatus.AVAILABLE &&
|
||||
event.databaseEntity.status === MediaStatus.PENDING
|
||||
) {
|
||||
this.updateChildRequestStatus(event.entity, false);
|
||||
}
|
||||
|
||||
if (
|
||||
event.entity.status4k === MediaStatus.AVAILABLE &&
|
||||
event.databaseEntity.status4k === MediaStatus.PENDING
|
||||
) {
|
||||
this.updateChildRequestStatus(event.entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ const messages = defineMessages({
|
||||
request4kfrom: 'There is currently a pending 4K request from {username}',
|
||||
errorediting: 'Something went wrong editing the request.',
|
||||
requestedited: 'Request edited.',
|
||||
autoapproval: 'Auto Approval',
|
||||
});
|
||||
|
||||
interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@@ -259,7 +260,7 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
hasPermission(Permission.AUTO_APPROVE) ||
|
||||
hasPermission(Permission.AUTO_APPROVE_MOVIE)) && (
|
||||
<p className="mt-6">
|
||||
<Alert title="Auto Approval" type="info">
|
||||
<Alert title={intl.formatMessage(messages.autoapproval)} type="info">
|
||||
{intl.formatMessage(messages.requestadmin)}
|
||||
</Alert>
|
||||
</p>
|
||||
|
||||
@@ -39,6 +39,7 @@ const messages = defineMessages({
|
||||
errorediting: 'Something went wrong editing the request.',
|
||||
requestedited: 'Request edited.',
|
||||
requestcancelled: 'Request cancelled.',
|
||||
autoapproval: 'Auto Approval',
|
||||
});
|
||||
|
||||
interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@@ -168,7 +169,11 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
|
||||
const getAllRequestedSeasons = (): number[] => {
|
||||
const requestedSeasons = (data?.mediaInfo?.requests ?? [])
|
||||
.filter((request) => request.is4k === is4k)
|
||||
.filter(
|
||||
(request) =>
|
||||
request.is4k === is4k &&
|
||||
request.status !== MediaRequestStatus.DECLINED
|
||||
)
|
||||
.reduce((requestedSeasons, request) => {
|
||||
return [
|
||||
...requestedSeasons,
|
||||
@@ -254,8 +259,11 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
|
||||
if (
|
||||
data?.mediaInfo &&
|
||||
(data.mediaInfo.requests || []).filter((request) => request.is4k === is4k)
|
||||
.length > 0
|
||||
(data.mediaInfo.requests || []).filter(
|
||||
(request) =>
|
||||
request.is4k === is4k &&
|
||||
request.status !== MediaRequestStatus.DECLINED
|
||||
).length > 0
|
||||
) {
|
||||
data.mediaInfo.requests
|
||||
.filter((request) => request.is4k === is4k)
|
||||
@@ -313,10 +321,13 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
>
|
||||
{(hasPermission(Permission.MANAGE_REQUESTS) ||
|
||||
hasPermission(Permission.AUTO_APPROVE) ||
|
||||
hasPermission(Permission.AUTO_APPROVE_MOVIE)) &&
|
||||
hasPermission(Permission.AUTO_APPROVE_TV)) &&
|
||||
!editRequest && (
|
||||
<p className="mt-6">
|
||||
<Alert title="Auto Approval" type="info">
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.autoapproval)}
|
||||
type="info"
|
||||
>
|
||||
{intl.formatMessage(messages.requestadmin)}
|
||||
</Alert>
|
||||
</p>
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
"components.RequestModal.AdvancedRequester.loadingprofiles": "Loading profiles…",
|
||||
"components.RequestModal.AdvancedRequester.qualityprofile": "Quality Profile",
|
||||
"components.RequestModal.AdvancedRequester.rootfolder": "Root Folder",
|
||||
"components.RequestModal.autoapproval": "Auto Approval",
|
||||
"components.RequestModal.cancel": "Cancel Request",
|
||||
"components.RequestModal.cancelling": "Cancelling…",
|
||||
"components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?",
|
||||
|
||||
Reference in New Issue
Block a user