feat(email validation): email requirement and validation + better importer

This commit is contained in:
Nicolai Van der Storm
2022-06-13 14:21:05 +02:00
parent cc69f66ba9
commit d835336d33
6 changed files with 78 additions and 59 deletions

View File

@@ -13,6 +13,7 @@ import {
NotificationAgentKey, NotificationAgentKey,
} from '../../settings'; } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import * as EmailValidator from 'email-validator';
class EmailAgent class EmailAgent
extends BaseAgent<NotificationAgentEmail> extends BaseAgent<NotificationAgentEmail>
@@ -215,14 +216,23 @@ class EmailAgent
this.getSettings(), this.getSettings(),
payload.notifyUser.settings?.pgpKey payload.notifyUser.settings?.pgpKey
); );
await email.send( if (EmailValidator.validate(payload.notifyUser.email)) {
this.buildMessage( await email.send(
type, this.buildMessage(
payload, type,
payload.notifyUser.email, payload,
payload.notifyUser.displayName payload.notifyUser.email,
) payload.notifyUser.displayName
); )
);
} else {
logger.warn('Invalid email address provided for user', {
label: 'Notifications',
recipient: payload.notifyUser.displayName,
type: Notification[type],
subject: payload.subject,
});
}
} catch (e) { } catch (e) {
logger.error('Error sending email notification', { logger.error('Error sending email notification', {
label: 'Notifications', label: 'Notifications',
@@ -268,9 +278,18 @@ class EmailAgent
this.getSettings(), this.getSettings(),
user.settings?.pgpKey user.settings?.pgpKey
); );
await email.send( if (EmailValidator.validate(user.email)) {
this.buildMessage(type, payload, user.email, user.displayName) await email.send(
); this.buildMessage(type, payload, user.email, user.displayName)
);
} else {
logger.warn('Invalid email address provided for user', {
label: 'Notifications',
recipient: user.displayName,
type: Notification[type],
subject: payload.subject,
});
}
} catch (e) { } catch (e) {
logger.error('Error sending email notification', { logger.error('Error sending email notification', {
label: 'Notifications', label: 'Notifications',

View File

@@ -1,6 +1,6 @@
import { Router } from 'express'; import { Router } from 'express';
import gravatarUrl from 'gravatar-url'; import gravatarUrl from 'gravatar-url';
import { findIndex, sortBy } from 'lodash'; import { findIndex, forEach, sortBy } from 'lodash';
import { getRepository, In, Not } from 'typeorm'; import { getRepository, In, Not } from 'typeorm';
import JellyfinAPI from '../../api/jellyfin'; import JellyfinAPI from '../../api/jellyfin';
import PlexTvAPI from '../../api/plextv'; import PlexTvAPI from '../../api/plextv';
@@ -492,62 +492,44 @@ router.post(
); );
jellyfinClient.setUserId(admin.jellyfinUserId ?? ''); jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
const jellyfinUsersResponse = await jellyfinClient.getUsers(); //const jellyfinUsersResponse = await jellyfinClient.getUsers();
const createdUsers: User[] = []; const createdUsers: User[] = [];
const { hostname, externalHostname } = getSettings().jellyfin; const { hostname, externalHostname } = getSettings().jellyfin;
const jellyfinHost = const jellyfinHost =
externalHostname && externalHostname.length > 0 externalHostname && externalHostname.length > 0
? externalHostname ? externalHostname
: hostname; : hostname;
for (const account of jellyfinUsersResponse.users) {
if (account.Name) {
const user = await userRepository
.createQueryBuilder('user')
.where('user.jellyfinUserId = :id', { id: account.Id })
.orWhere('user.email = :email', {
email: account.Name,
})
.getOne();
const avatar = account.PrimaryImageTag forEach(body.jellyfinUserIds, async (jellyfinUserId) => {
? `${jellyfinHost}/Users/${account.Id}/Images/Primary/?tag=${account.PrimaryImageTag}&quality=90` jellyfinClient.setUserId(jellyfinUserId);
: '/os_logo_square.png'; const jellyfinUser = await jellyfinClient.getUser();
if (user) { const user = await userRepository.findOne({
// Update the user's avatar with their Jellyfin thumbnail, in case it changed select: ['id', 'jellyfinUserId'],
user.avatar = avatar; where: { jellyfinUserId: jellyfinUserId },
user.email = account.Name; });
user.jellyfinUsername = account.Name;
// In case the user was previously a local account if (!user) {
if (user.userType === UserType.LOCAL) { const newUser = new User({
user.userType = UserType.JELLYFIN; jellyfinUsername: jellyfinUser.Name,
user.jellyfinUserId = account.Id; jellyfinUserId: jellyfinUser.Id,
} jellyfinDeviceId: Buffer.from(
await userRepository.save(user); `BOT_jellyseerr_${jellyfinUser.Name ?? ''}`
} else if (!body || body.jellyfinUserIds.includes(account.Id)) { ).toString('base64'),
// logger.error('CREATED USER', { email: jellyfinUser.Name,
// label: 'API', permissions: settings.main.defaultPermissions,
// }); avatar: jellyfinUser.PrimaryImageTag
? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
userType: UserType.JELLYFIN,
});
const newUser = new User({ await userRepository.save(newUser);
jellyfinUsername: account.Name, createdUsers.push(newUser);
jellyfinUserId: account.Id,
jellyfinDeviceId: Buffer.from(
`BOT_overseerr_${account.Name ?? ''}`
).toString('base64'),
email: account.Name,
permissions: settings.main.defaultPermissions,
avatar,
userType: UserType.JELLYFIN,
});
await userRepository.save(newUser);
createdUsers.push(newUser);
}
} }
}
return res.status(201).json(User.filterMany(createdUsers)); return res.status(201).json(User.filterMany(createdUsers));
});
} catch (e) { } catch (e) {
next({ status: 500, message: e.message }); next({ status: 500, message: e.message });
} }

View File

@@ -5,7 +5,7 @@ import { useUser } from '../../hooks/useUser';
import PlexLoginButton from '../PlexLoginButton'; import PlexLoginButton from '../PlexLoginButton';
const messages = defineMessages({ const messages = defineMessages({
welcome: 'Welcome to Overseerr', welcome: 'Welcome to Jellyseerr',
signinMessage: 'Get started by signing in with your Plex account', signinMessage: 'Get started by signing in with your Plex account',
}); });

View File

@@ -9,7 +9,7 @@ import { MediaServerType } from '../../../server/constants/server';
import getConfig from 'next/config'; import getConfig from 'next/config';
const messages = defineMessages({ const messages = defineMessages({
welcome: 'Welcome to Overseerr', welcome: 'Welcome to Jellyseerr',
signinMessage: 'Get started by signing in', signinMessage: 'Get started by signing in',
signinWithJellyfin: 'Use your {mediaServerName} account', signinWithJellyfin: 'Use your {mediaServerName} account',
signinWithPlex: 'Use your Plex account', signinWithPlex: 'Use your Plex account',

View File

@@ -9,6 +9,7 @@ import globalMessages from '../../i18n/globalMessages';
import Alert from '../Common/Alert'; import Alert from '../Common/Alert';
import Modal from '../Common/Modal'; import Modal from '../Common/Modal';
import getConfig from 'next/config'; import getConfig from 'next/config';
import { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
interface JellyfinImportProps { interface JellyfinImportProps {
onCancel?: () => void; onCancel?: () => void;
@@ -30,6 +31,7 @@ const messages = defineMessages({
const JellyfinImportModal: React.FC<JellyfinImportProps> = ({ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
onCancel, onCancel,
onComplete, onComplete,
children,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const settings = useSettings(); const settings = useSettings();
@@ -117,6 +119,20 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
} }
}; };
const { data: existingUsers } = useSWR<UserResultsResponse>(
`/api/v1/user?take=${children}`
);
data?.forEach((user, pos) => {
if (
existingUsers?.results.some(
(existingUser) => existingUser.jellyfinUserId === user.id
)
) {
delete data[pos];
}
});
return ( return (
<Modal <Modal
loading={!data && !error} loading={!data && !error}

View File

@@ -482,7 +482,9 @@ const UserList: React.FC = () => {
setShowImportModal(false); setShowImportModal(false);
revalidate(); revalidate();
}} }}
/> >
{data.pageInfo.results}
</JellyfinImportModal>
)} )}
</Transition> </Transition>