feat: import users from plex (#428)

* feat: import users from plex

fix #281

* fix(frontend): re-enable delete user confirmation button after finished
pull/435/head
johnpyp 3 years ago committed by GitHub
parent 08c22b03e0
commit 7e8f361af7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1673,6 +1673,24 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/User'
/user/import-from-plex:
post:
summary: Imports all users from Plex
description: |
Requests users from the Plex Server and creates a new user for each of them
Requires the `MANAGE_USERS` permission.
tags:
- users
responses:
'201':
description: A list of the newly created users
content:
application/json:
schema:
type: array
$ref: '#/components/schemas/User'
/user/{userId}:
get:
summary: Retrieve a user by ID

@ -56,6 +56,21 @@ interface FriendResponse {
};
}
interface UsersResponse {
MediaContainer: {
User: {
$: {
id: string;
title: string;
username: string;
email: string;
thumb: string;
};
Server: ServerResponse[];
}[];
};
}
class PlexTvAPI {
private authToken: string;
private axios: AxiosInstance;
@ -129,6 +144,18 @@ class PlexTvAPI {
return false;
}
}
public async getUsers(): Promise<UsersResponse> {
const response = await this.axios.get('/api/users', {
transformResponse: [],
responseType: 'text',
});
const parsedXml = (await xml2js.parseStringPromise(
response.data
)) as UsersResponse;
return parsedXml;
}
}
export default PlexTvAPI;

@ -1,8 +1,10 @@
import { Router } from 'express';
import { getRepository } from 'typeorm';
import PlexTvAPI from '../api/plextv';
import { MediaRequest } from '../entity/MediaRequest';
import { User } from '../entity/User';
import { hasPermission, Permission } from '../lib/permissions';
import { getSettings } from '../lib/settings';
import logger from '../logger';
const router = Router();
@ -142,4 +144,51 @@ router.delete<{ id: string }>('/:id', async (req, res, next) => {
}
});
router.post('/import-from-plex', async (req, res, next) => {
try {
const settings = getSettings();
const userRepository = getRepository(User);
// taken from auth.ts
const mainUser = await userRepository.findOneOrFail({
select: ['id', 'plexToken'],
order: { id: 'ASC' },
});
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
const plexUsersResponse = await mainPlexTv.getUsers();
const createdUsers: User[] = [];
for (const rawUser of plexUsersResponse.MediaContainer.User) {
const account = rawUser.$;
const user = await userRepository.findOne({
where: { plexId: account.id },
});
if (user) {
// Update the users avatar with their plex thumbnail (incase it changed)
user.avatar = account.thumb;
user.email = account.email;
user.username = account.username;
await userRepository.save(user);
} else {
// Check to make sure it's a real account
if (account.email && account.username) {
const newUser = new User({
username: account.username,
email: account.email,
permissions: settings.main.defaultPermissions,
plexId: parseInt(account.id),
plexToken: '',
avatar: account.thumb,
});
await userRepository.save(newUser);
createdUsers.push(newUser);
}
}
}
return res.status(201).json(User.filterMany(createdUsers));
} catch (e) {
next({ status: 500, message: e.message });
}
});
export default router;

@ -18,6 +18,10 @@ import globalMessages from '../../i18n/globalMessages';
const messages = defineMessages({
userlist: 'User List',
importfromplex: 'Import Users From Plex',
importfromplexerror: 'Something went wrong importing users from Plex',
importedfromplex:
'{userCount, plural, =0 {No new users} one {# new user} other {# new users}} imported from Plex',
username: 'Username',
totalrequests: 'Total Requests',
usertype: 'User Type',
@ -42,6 +46,7 @@ const UserList: React.FC = () => {
const { addToast } = useToasts();
const { data, error, revalidate } = useSWR<User[]>('/api/v1/user');
const [isDeleting, setDeleting] = useState(false);
const [isImporting, setImporting] = useState(false);
const [deleteModal, setDeleteModal] = useState<{
isOpen: boolean;
user?: User;
@ -66,10 +71,38 @@ const UserList: React.FC = () => {
appearance: 'error',
});
} finally {
setDeleting(false);
revalidate();
}
};
const importFromPlex = async () => {
setImporting(true);
try {
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-plex'
);
addToast(
intl.formatMessage(messages.importedfromplex, {
userCount: createdUsers.length,
}),
{
autoDismiss: true,
appearance: 'success',
}
);
} catch (e) {
addToast(intl.formatMessage(messages.importfromplexerror), {
autoDismiss: true,
appearance: 'error',
});
} finally {
revalidate();
setImporting(false);
}
};
if (!data && !error) {
return <LoadingSpinner />;
}
@ -116,7 +149,17 @@ const UserList: React.FC = () => {
{intl.formatMessage(messages.deleteconfirm)}
</Modal>
</Transition>
<Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header>
<div className="flex items-center justify-between">
<Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header>
<Button
className="mx-4 my-8"
buttonType="primary"
disabled={isImporting}
onClick={() => importFromPlex()}
>
{intl.formatMessage(messages.importfromplex)}
</Button>
</div>
<Table>
<thead>
<tr>
@ -134,18 +177,18 @@ const UserList: React.FC = () => {
<tr key={`user-list-${user.id}`}>
<Table.TD>
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="flex-shrink-0 w-10 h-10">
<img
className="h-10 w-10 rounded-full"
className="w-10 h-10 rounded-full"
src={user.avatar}
alt=""
/>
</div>
<div className="ml-4">
<div className="text-sm leading-5 font-medium">
<div className="text-sm font-medium leading-5">
{user.username}
</div>
<div className="text-sm leading-5 text-gray-300">
<div className="text-sm text-gray-300 leading-5">
{user.email}
</div>
</div>

@ -334,6 +334,9 @@
"components.UserList.deleteconfirm": "Are you sure you want to delete this user? All existing request data from this user will be removed.",
"components.UserList.deleteuser": "Delete User",
"components.UserList.edit": "Edit",
"components.UserList.importedfromplex": "{userCount, plural, =0 {No new users} one {# new user} other {# new users}} imported from Plex",
"components.UserList.importfromplex": "Import Users From Plex",
"components.UserList.importfromplexerror": "Something went wrong importing users from Plex",
"components.UserList.lastupdated": "Last Updated",
"components.UserList.plexuser": "Plex User",
"components.UserList.role": "Role",

Loading…
Cancel
Save