Skip to content

Commit

Permalink
feat(users): add editable usernames (#715)
Browse files Browse the repository at this point in the history
  • Loading branch information
ankarhem committed Jan 26, 2021
1 parent 82ac76b commit 20ca3f2
Show file tree
Hide file tree
Showing 19 changed files with 282 additions and 173 deletions.
15 changes: 13 additions & 2 deletions server/entity/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
UpdateDateColumn,
OneToMany,
RelationCount,
AfterLoad,
} from 'typeorm';
import { Permission, hasPermission } from '../lib/permissions';
import { MediaRequest } from './MediaRequest';
Expand All @@ -25,14 +26,19 @@ export class User {

static readonly filteredFields: string[] = ['plexToken', 'password'];

public displayName: string;

@PrimaryGeneratedColumn()
public id: number;

@Column({ unique: true })
public email: string;

@Column()
public username: string;
@Column({ nullable: true })
public plexUsername: string;

@Column({ nullable: true })
public username?: string;

@Column({ nullable: true, select: false })
public password?: string;
Expand Down Expand Up @@ -125,4 +131,9 @@ export class User {
});
}
}

@AfterLoad()
public setDisplayName(): void {
this.displayName = this.username || this.plexUsername;
}
}
8 changes: 4 additions & 4 deletions server/lib/notifications/agents/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class DiscordAgent
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
value: payload.notifyUser.displayName ?? '',
inline: true,
},
{
Expand All @@ -126,7 +126,7 @@ class DiscordAgent
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
value: payload.notifyUser.displayName ?? '',
inline: true,
},
{
Expand All @@ -148,7 +148,7 @@ class DiscordAgent
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
value: payload.notifyUser.displayName ?? '',
inline: true,
},
{
Expand All @@ -170,7 +170,7 @@ class DiscordAgent
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
value: payload.notifyUser.displayName ?? '',
inline: true,
},
{
Expand Down
10 changes: 5 additions & 5 deletions server/lib/notifications/agents/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class EmailAgent
mediaName: payload.subject,
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
requestedBy: payload.notifyUser.displayName,
actionUrl: applicationUrl
? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`
: undefined,
Expand Down Expand Up @@ -106,7 +106,7 @@ class EmailAgent
mediaName: payload.subject,
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
requestedBy: payload.notifyUser.displayName,
actionUrl: applicationUrl
? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`
: undefined,
Expand Down Expand Up @@ -144,7 +144,7 @@ class EmailAgent
mediaName: payload.subject,
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
requestedBy: payload.notifyUser.displayName,
actionUrl: applicationUrl
? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`
: undefined,
Expand Down Expand Up @@ -181,7 +181,7 @@ class EmailAgent
mediaName: payload.subject,
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
requestedBy: payload.notifyUser.displayName,
actionUrl: applicationUrl
? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`
: undefined,
Expand Down Expand Up @@ -218,7 +218,7 @@ class EmailAgent
mediaName: payload.subject,
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
requestedBy: payload.notifyUser.displayName,
actionUrl: applicationUrl
? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`
: undefined,
Expand Down
12 changes: 6 additions & 6 deletions server/lib/notifications/agents/pushover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,42 @@ class PushoverAgent

const title = payload.subject;
const plot = payload.message;
const user = payload.notifyUser.username;
const username = payload.notifyUser.displayName;

switch (type) {
case Notification.MEDIA_PENDING:
messageTitle = 'New Request';
message += `${title}\n\n`;
message += `${plot}\n\n`;
message += `<b>Requested By</b>\n${user}\n\n`;
message += `<b>Requested By</b>\n${username}\n\n`;
message += `<b>Status</b>\nPending Approval\n`;
break;
case Notification.MEDIA_APPROVED:
messageTitle = 'Request Approved';
message += `${title}\n\n`;
message += `${plot}\n\n`;
message += `<b>Requested By</b>\n${user}\n\n`;
message += `<b>Requested By</b>\n${username}\n\n`;
message += `<b>Status</b>\nProcessing Request\n`;
break;
case Notification.MEDIA_AVAILABLE:
messageTitle = 'Now available!';
message += `${title}\n\n`;
message += `${plot}\n\n`;
message += `<b>Requested By</b>\n${user}\n\n`;
message += `<b>Requested By</b>\n${username}\n\n`;
message += `<b>Status</b>\nAvailable\n`;
break;
case Notification.MEDIA_DECLINED:
messageTitle = 'Request Declined';
message += `${title}\n\n`;
message += `${plot}\n\n`;
message += `<b>Requested By</b>\n${user}\n\n`;
message += `<b>Requested By</b>\n${username}\n\n`;
message += `<b>Status</b>\nDeclined\n`;
break;
case Notification.TEST_NOTIFICATION:
messageTitle = 'Test Notification';
message += `${title}\n\n`;
message += `${plot}\n\n`;
message += `<b>Requested By</b>\n${user}\n`;
message += `<b>Requested By</b>\n${username}\n`;
break;
}

Expand Down
8 changes: 4 additions & 4 deletions server/lib/notifications/agents/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class SlackAgent
fields.push(
{
type: 'mrkdwn',
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`,
},
{
type: 'mrkdwn',
Expand All @@ -85,7 +85,7 @@ class SlackAgent
fields.push(
{
type: 'mrkdwn',
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`,
},
{
type: 'mrkdwn',
Expand All @@ -101,7 +101,7 @@ class SlackAgent
fields.push(
{
type: 'mrkdwn',
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`,
},
{
type: 'mrkdwn',
Expand All @@ -117,7 +117,7 @@ class SlackAgent
fields.push(
{
type: 'mrkdwn',
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`,
},
{
type: 'mrkdwn',
Expand Down
2 changes: 1 addition & 1 deletion server/lib/notifications/agents/telegram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class TelegramAgent

const title = this.escapeText(payload.subject);
const plot = this.escapeText(payload.message);
const user = this.escapeText(payload.notifyUser.username);
const user = this.escapeText(payload.notifyUser.displayName);

/* eslint-disable no-useless-escape */
switch (type) {
Expand Down
2 changes: 1 addition & 1 deletion server/lib/notifications/agents/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const KeyMap: Record<string, string | KeyMapFunction> = {
subject: 'subject',
message: 'message',
image: 'image',
notifyuser_username: 'notifyUser.username',
notifyuser_username: 'notifyUser.displayName',
notifyuser_email: 'notifyUser.email',
notifyuser_avatar: 'notifyUser.avatar',
media_tmdbid: 'media.tmdbId',
Expand Down
43 changes: 43 additions & 0 deletions server/migration/1611508672722-AddDisplayNameToUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddDisplayNameToUser1611508672722 implements MigrationInterface {
name = 'AddDisplayNameToUser1611508672722';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "temporary_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar NOT NULL, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), "plexUsername" varchar, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))`
);
await queryRunner.query(
`INSERT INTO "temporary_user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername") SELECT "id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "username" FROM "user"`
);
await queryRunner.query(`DROP TABLE "user"`);
await queryRunner.query(`ALTER TABLE "temporary_user" RENAME TO "user"`);
await queryRunner.query(
`CREATE TABLE "temporary_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), "plexUsername" varchar, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))`
);
await queryRunner.query(
`INSERT INTO "temporary_user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername") SELECT "id", "email", "", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername" FROM "user"`
);
await queryRunner.query(`DROP TABLE "user"`);
await queryRunner.query(`ALTER TABLE "temporary_user" RENAME TO "user"`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" RENAME TO "temporary_user"`);
await queryRunner.query(
`CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar NOT NULL, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), "plexUsername" varchar, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))`
);
await queryRunner.query(
`INSERT INTO "user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername") SELECT "id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername" FROM "temporary_user"`
);
await queryRunner.query(`DROP TABLE "temporary_user"`);
await queryRunner.query(`ALTER TABLE "user" RENAME TO "temporary_user"`);
await queryRunner.query(
`CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar NOT NULL, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))`
);
await queryRunner.query(
`INSERT INTO "user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType") SELECT "id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType" FROM "temporary_user"`
);
await queryRunner.query(`DROP TABLE "temporary_user"`);
}
}
14 changes: 9 additions & 5 deletions server/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ authRoutes.post('/login', async (req, res, next) => {
// Let's check if their plex token is up to date
if (user.plexToken !== body.authToken) {
user.plexToken = body.authToken;
await userRepository.save(user);
}

// Update the users avatar with their plex thumbnail (incase it changed)
user.avatar = account.thumb;
user.email = account.email;
user.username = account.username;
user.plexUsername = account.username;

if (user.username === account.username) {
user.username = '';
}
await userRepository.save(user);
} else {
// Here we check if it's the first user. If it is, we create the user with no check
// and give them admin permissions
Expand All @@ -63,7 +67,7 @@ authRoutes.post('/login', async (req, res, next) => {
if (totalUsers === 0) {
user = new User({
email: account.email,
username: account.username,
plexUsername: account.username,
plexId: account.id,
plexToken: account.authToken,
permissions: Permission.ADMIN,
Expand All @@ -86,7 +90,7 @@ authRoutes.post('/login', async (req, res, next) => {
if (await mainPlexTv.checkUserAccess(account)) {
user = new User({
email: account.email,
username: account.username,
plexUsername: account.username,
plexId: account.id,
plexToken: account.authToken,
permissions: settings.main.defaultPermissions,
Expand Down Expand Up @@ -141,7 +145,7 @@ authRoutes.post('/local', async (req, res, next) => {
try {
const user = await userRepository.findOne({
select: ['id', 'password'],
where: { email: body.email, userType: UserType.LOCAL },
where: { email: body.email },
});

const isCorrectCredentials = await user?.passwordMatch(body.password);
Expand Down
24 changes: 20 additions & 4 deletions server/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ router.put<{ id: string }>('/:id', async (req, res, next) => {
});
}

Object.assign(user, req.body);
Object.assign(user, {
username: req.body.username,
permissions: req.body.permissions,
});

await userRepository.save(user);

return res.status(200).json(user.filter());
Expand Down Expand Up @@ -213,20 +217,32 @@ router.post('/import-from-plex', async (req, res, next) => {
const createdUsers: User[] = [];
for (const rawUser of plexUsersResponse.MediaContainer.User) {
const account = rawUser.$;

const user = await userRepository.findOne({
where: { plexId: account.id },
where: [{ plexId: account.id }, { email: account.email }],
});

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;
user.plexUsername = account.username;

// in-case the user was previously a local account
if (user.userType === UserType.LOCAL) {
user.userType = UserType.PLEX;
user.plexId = parseInt(account.id);

if (user.username === account.username) {
user.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,
plexUsername: account.username,
email: account.email,
permissions: settings.main.defaultPermissions,
plexId: parseInt(account.id),
Expand Down
4 changes: 2 additions & 2 deletions src/components/RequestBlock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
/>
</svg>
<span className="w-40 truncate md:w-auto">
{request.requestedBy.username}
{request.requestedBy.displayName}
</span>
</div>
{request.modifiedBy && (
Expand All @@ -101,7 +101,7 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
/>
</svg>
<span className="w-40 truncate md:w-auto">
{request.modifiedBy?.username}
{request.modifiedBy?.displayName}
</span>
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/RequestCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const RequestCard: React.FC<RequestCardProps> = ({ request }) => {
</h2>
<div className="text-xs truncate sm:text-sm">
{intl.formatMessage(messages.requestedby, {
username: requestData.requestedBy.username,
username: requestData.requestedBy.displayName,
})}
</div>
{requestData.media.status && (
Expand Down

0 comments on commit 20ca3f2

Please sign in to comment.