mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat: force setup if app is not initialized
This commit is contained in:
@@ -1160,6 +1160,7 @@ paths:
|
|||||||
/settings/public:
|
/settings/public:
|
||||||
get:
|
get:
|
||||||
summary: Returns public settings
|
summary: Returns public settings
|
||||||
|
security: []
|
||||||
description: Returns settings that are not protected or sensitive. Mainly used to determine if the app has been configured for the first time.
|
description: Returns settings that are not protected or sensitive. Mainly used to determine if the app has been configured for the first time.
|
||||||
tags:
|
tags:
|
||||||
- settings
|
- settings
|
||||||
@@ -1170,6 +1171,19 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PublicSettings'
|
$ref: '#/components/schemas/PublicSettings'
|
||||||
|
/settings/initialize:
|
||||||
|
get:
|
||||||
|
summary: Set the application as initialized
|
||||||
|
description: Sets the app as initalized and allows the user to navigate to pages other than the setup page
|
||||||
|
tags:
|
||||||
|
- settings
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Public Settings returned
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PublicSettings'
|
||||||
/settings/jobs:
|
/settings/jobs:
|
||||||
get:
|
get:
|
||||||
summary: Returns list of scheduled jobs
|
summary: Returns list of scheduled jobs
|
||||||
@@ -1887,6 +1901,12 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
enum: [all, available, partial, processing, pending]
|
enum: [all, available, partial, processing, pending]
|
||||||
|
- in: query
|
||||||
|
name: sort
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [added, modified]
|
||||||
|
default: added
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Returned media
|
description: Returned media
|
||||||
|
@@ -16,6 +16,11 @@ const router = Router();
|
|||||||
|
|
||||||
router.use(checkUser);
|
router.use(checkUser);
|
||||||
router.use('/user', isAuthenticated(Permission.MANAGE_USERS), user);
|
router.use('/user', isAuthenticated(Permission.MANAGE_USERS), user);
|
||||||
|
router.get('/settings/public', (_req, res) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
return res.status(200).json(settings.public);
|
||||||
|
});
|
||||||
router.use(
|
router.use(
|
||||||
'/settings',
|
'/settings',
|
||||||
isAuthenticated(Permission.MANAGE_SETTINGS),
|
isAuthenticated(Permission.MANAGE_SETTINGS),
|
||||||
@@ -29,12 +34,6 @@ router.use('/tv', isAuthenticated(), tvRoutes);
|
|||||||
router.use('/media', isAuthenticated(), mediaRoutes);
|
router.use('/media', isAuthenticated(), mediaRoutes);
|
||||||
router.use('/auth', authRoutes);
|
router.use('/auth', authRoutes);
|
||||||
|
|
||||||
router.get('/settings/public', (_req, res) => {
|
|
||||||
const settings = getSettings();
|
|
||||||
|
|
||||||
return res.status(200).json(settings.public);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/', (_req, res) => {
|
router.get('/', (_req, res) => {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
api: 'Overseerr API',
|
api: 'Overseerr API',
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { getRepository, FindOperator } from 'typeorm';
|
import { getRepository, FindOperator, FindOneOptions } from 'typeorm';
|
||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
import { MediaStatus } from '../constants/media';
|
import { MediaStatus } from '../constants/media';
|
||||||
|
|
||||||
@@ -43,11 +43,21 @@ mediaRoutes.get('/', async (req, res, next) => {
|
|||||||
statusFilter = undefined;
|
statusFilter = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sortFilter: FindOneOptions<Media>['order'] = {
|
||||||
|
id: 'DESC',
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (req.query.sort) {
|
||||||
|
case 'modified':
|
||||||
|
sortFilter = {
|
||||||
|
updatedAt: 'DESC',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [media, mediaCount] = await mediaRepository.findAndCount({
|
const [media, mediaCount] = await mediaRepository.findAndCount({
|
||||||
order: {
|
order: sortFilter,
|
||||||
id: 'DESC',
|
|
||||||
},
|
|
||||||
where: {
|
where: {
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
},
|
},
|
||||||
|
@@ -13,6 +13,8 @@ import SonarrAPI from '../api/sonarr';
|
|||||||
import RadarrAPI from '../api/radarr';
|
import RadarrAPI from '../api/radarr';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { scheduledJobs } from '../job/schedule';
|
import { scheduledJobs } from '../job/schedule';
|
||||||
|
import { Permission } from '../lib/permissions';
|
||||||
|
import { isAuthenticated } from '../middleware/auth';
|
||||||
|
|
||||||
const settingsRoutes = Router();
|
const settingsRoutes = Router();
|
||||||
|
|
||||||
@@ -334,4 +336,17 @@ settingsRoutes.get('/jobs', (req, res) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
settingsRoutes.get(
|
||||||
|
'/initialize',
|
||||||
|
isAuthenticated(Permission.ADMIN),
|
||||||
|
(req, res) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
settings.public.initialized = true;
|
||||||
|
settings.save();
|
||||||
|
|
||||||
|
return res.status(200).json(settings.public);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default settingsRoutes;
|
export default settingsRoutes;
|
||||||
|
@@ -44,7 +44,7 @@ const Discover: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: media, error: mediaError } = useSWR<MediaResultsResponse>(
|
const { data: media, error: mediaError } = useSWR<MediaResultsResponse>(
|
||||||
'/api/v1/media?filter=available&take=20'
|
'/api/v1/media?filter=available&take=20&sort=modified'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: requests, error: requestError } = useSWR<MediaRequest[]>(
|
const { data: requests, error: requestError } = useSWR<MediaRequest[]>(
|
||||||
|
@@ -6,12 +6,33 @@ import SettingsPlex from '../Settings/SettingsPlex';
|
|||||||
import SettingsServices from '../Settings/SettingsServices';
|
import SettingsServices from '../Settings/SettingsServices';
|
||||||
import LoginWithPlex from './LoginWithPlex';
|
import LoginWithPlex from './LoginWithPlex';
|
||||||
import SetupSteps from './SetupSteps';
|
import SetupSteps from './SetupSteps';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
finish: 'Finish Setup',
|
||||||
|
finishing: 'Finishing...',
|
||||||
|
continue: 'Continue',
|
||||||
|
});
|
||||||
|
|
||||||
const Setup: React.FC = () => {
|
const Setup: React.FC = () => {
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
const [currentStep, setCurrentStep] = useState(1);
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
const [plexSettingsComplete, setPlexSettingsComplete] = useState(false);
|
const [plexSettingsComplete, setPlexSettingsComplete] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const finishSetup = async () => {
|
||||||
|
setIsUpdating(false);
|
||||||
|
const response = await axios.get<{ initialized: boolean }>(
|
||||||
|
'/api/v1/settings/initialize'
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsUpdating(false);
|
||||||
|
if (response.data.initialized) {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-cool-gray-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8 relative">
|
<div className="min-h-screen bg-cool-gray-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8 relative">
|
||||||
<ImageFader
|
<ImageFader
|
||||||
@@ -68,7 +89,7 @@ const Setup: React.FC = () => {
|
|||||||
disabled={!plexSettingsComplete}
|
disabled={!plexSettingsComplete}
|
||||||
onClick={() => setCurrentStep(3)}
|
onClick={() => setCurrentStep(3)}
|
||||||
>
|
>
|
||||||
Continue
|
<FormattedMessage {...messages.continue} />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,9 +104,14 @@ const Setup: React.FC = () => {
|
|||||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||||
<Button
|
<Button
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
onClick={() => router.push('/')}
|
onClick={() => finishSetup()}
|
||||||
|
disabled={isUpdating}
|
||||||
>
|
>
|
||||||
Finish Setup
|
{isUpdating ? (
|
||||||
|
<FormattedMessage {...messages.finishing} />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage {...messages.finish} />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -96,29 +96,47 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
let locale = 'en';
|
let locale = 'en';
|
||||||
|
|
||||||
if (ctx.res) {
|
if (ctx.res) {
|
||||||
try {
|
// Check if app is initialized and redirect if necessary
|
||||||
// Attempt to get the user by running a request to the local api
|
let initialized = true;
|
||||||
const response = await axios.get<User>(
|
|
||||||
`http://localhost:${process.env.PORT || 3000}/api/v1/auth/me`,
|
|
||||||
{ headers: ctx.req ? { cookie: ctx.req.headers.cookie } : undefined }
|
|
||||||
);
|
|
||||||
user = response.data;
|
|
||||||
|
|
||||||
if (router.pathname.match(/login/)) {
|
const response = await axios.get<{ initialized: boolean }>(
|
||||||
|
`http://localhost:${process.env.PORT || 3000}/api/v1/settings/public`
|
||||||
|
);
|
||||||
|
|
||||||
|
initialized = response.data.initialized;
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
if (!router.pathname.match(/(setup|login\/plex)/)) {
|
||||||
ctx.res.writeHead(307, {
|
ctx.res.writeHead(307, {
|
||||||
Location: '/',
|
Location: '/setup',
|
||||||
});
|
});
|
||||||
ctx.res.end();
|
ctx.res.end();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} else {
|
||||||
// If there is no user, and ctx.res is set (to check if we are on the server side)
|
try {
|
||||||
// _AND_ we are not already on the login or setup route, redirect to /login with a 307
|
// Attempt to get the user by running a request to the local api
|
||||||
// before anything actually renders
|
const response = await axios.get<User>(
|
||||||
if (!router.pathname.match(/(login|setup)/)) {
|
`http://localhost:${process.env.PORT || 3000}/api/v1/auth/me`,
|
||||||
ctx.res.writeHead(307, {
|
{ headers: ctx.req ? { cookie: ctx.req.headers.cookie } : undefined }
|
||||||
Location: '/login',
|
);
|
||||||
});
|
user = response.data;
|
||||||
ctx.res.end();
|
|
||||||
|
if (router.pathname.match(/login/)) {
|
||||||
|
ctx.res.writeHead(307, {
|
||||||
|
Location: '/',
|
||||||
|
});
|
||||||
|
ctx.res.end();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If there is no user, and ctx.res is set (to check if we are on the server side)
|
||||||
|
// _AND_ we are not already on the login or setup route, redirect to /login with a 307
|
||||||
|
// before anything actually renders
|
||||||
|
if (!router.pathname.match(/(login|setup)/)) {
|
||||||
|
ctx.res.writeHead(307, {
|
||||||
|
Location: '/login',
|
||||||
|
});
|
||||||
|
ctx.res.end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user