Auth Email Rework (#1567)

* Hooked up Send to for Series and volumes and fixed a bug where Email Service errors weren't propagating to the UI layer.

When performing actions on series detail, don't disable the button anymore.

* Added send to action to volumes

* Fixed a bug where .kavitaignore wasn't being applied at library root level

* Added a notification for when a device is being sent a file.

* Added a check in forgot password for users that do not have an email set or aren't confirmed.

* Added a new api for change email and moved change password directly into new Account tab (styling and logic needs testing)

* Save approx scroll position like with jump key, but on normal click of card.

* Implemented the ability to change your email address or set one. This requires a 2 step process using a confirmation token. This needs polishing and css.

* Removed an unused directive from codebase

* Fixed up some typos on publicly

* Updated query for Pending Invites to also check if the user account has not logged in at least once.

* Cleaned up the css for validate email change

* Hooked in an indicator to tell user that a user has an unconfirmed email

* Cleaned up code smells
This commit is contained in:
Joe Milazzo 2022-10-01 08:23:35 -05:00 committed by GitHub
parent 3792ac3421
commit 5f17c2fb73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 816 additions and 274 deletions

View file

@ -11,6 +11,7 @@ import { ThemeService } from './theme.service';
import { InviteUserResponse } from '../_models/invite-user-response';
import { UserUpdateEvent } from '../_models/events/user-update-event';
import { DeviceService } from './device.service';
import { UpdateEmailResponse } from '../_models/email/update-email-response';
@Injectable({
providedIn: 'root'
@ -132,6 +133,10 @@ export class AccountService implements OnDestroy {
);
}
isEmailConfirmed() {
return this.httpClient.get<boolean>(this.baseUrl + 'account/email-confirmed');
}
migrateUser(model: {email: string, username: string, password: string, sendEmail: boolean}) {
return this.httpClient.post<string>(this.baseUrl + 'account/migrate-email', model, {responseType: 'text' as 'json'});
}
@ -152,6 +157,10 @@ export class AccountService implements OnDestroy {
return this.httpClient.post<User>(this.baseUrl + 'account/confirm-email', model);
}
confirmEmailUpdate(model: {email: string, token: string}) {
return this.httpClient.post<User>(this.baseUrl + 'account/confirm-email-update', model);
}
/**
* Given a user id, returns a full url for setting up the user account
* @param userId
@ -181,6 +190,10 @@ export class AccountService implements OnDestroy {
return this.httpClient.post(this.baseUrl + 'account/update', model);
}
updateEmail(email: string) {
return this.httpClient.post<UpdateEmailResponse>(this.baseUrl + 'account/update/email', {email});
}
/**
* This will get latest preferences for a user and cache them into user store
* @returns

View file

@ -383,6 +383,24 @@ export class ActionFactoryService {
}
]
},
{
action: Action.Submenu,
title: 'Send To',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.SendTo,
title: '',
callback: this.dummyCallback,
requiresAdmin: false,
dynamicList: this.deviceService.devices$.pipe(map((devices: Array<Device>) => devices.map(d => {
return {'title': d.name, 'data': d};
}), shareReplay())),
children: []
}
],
},
{
action: Action.Download,
title: 'Download',

View file

@ -8,10 +8,12 @@ import { AddToListModalComponent, ADD_FLOW } from '../reading-list/_modals/add-t
import { EditReadingListModalComponent } from '../reading-list/_modals/edit-reading-list-modal/edit-reading-list-modal.component';
import { ConfirmService } from '../shared/confirm.service';
import { Chapter } from '../_models/chapter';
import { Device } from '../_models/device/device';
import { Library } from '../_models/library';
import { ReadingList } from '../_models/reading-list';
import { Series } from '../_models/series';
import { Volume } from '../_models/volume';
import { DeviceService } from './device.service';
import { LibraryService } from './library.service';
import { MemberService } from './member.service';
import { ReaderService } from './reader.service';
@ -39,7 +41,7 @@ export class ActionService implements OnDestroy {
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal,
private confirmService: ConfirmService, private memberService: MemberService) { }
private confirmService: ConfirmService, private memberService: MemberService, private deviceSerivce: DeviceService) { }
ngOnDestroy() {
this.onDestroy.next();
@ -552,6 +554,15 @@ export class ActionService implements OnDestroy {
});
}
sendToDevice(chapterIds: Array<number>, device: Device, callback?: VoidActionCallback) {
this.deviceSerivce.sendTo(chapterIds, device.id).subscribe(() => {
this.toastr.success('File emailed to ' + device.name);
if (callback) {
callback();
}
});
}
private async promptIfForce(extraContent: string = '') {
// Prompt user if we should do a force or not
const config = this.confirmService.defaultConfirm;

View file

@ -40,8 +40,8 @@ export class DeviceService {
}));
}
sendTo(chapterId: number, deviceId: number) {
return this.httpClient.post(this.baseUrl + 'device/send-to', {deviceId, chapterId}, {responseType: 'text' as 'json'});
sendTo(chapterIds: Array<number>, deviceId: number) {
return this.httpClient.post(this.baseUrl + 'device/send-to', {deviceId, chapterIds}, {responseType: 'text' as 'json'});
}

View file

@ -51,31 +51,35 @@ export enum EVENTS {
/**
* A subtype of NotificationProgress that represents a file being processed for cover image extraction
*/
CoverUpdateProgress = 'CoverUpdateProgress',
CoverUpdateProgress = 'CoverUpdateProgress',
/**
* A library is created or removed from the instance
*/
LibraryModified = 'LibraryModified',
LibraryModified = 'LibraryModified',
/**
* A user updates an entities read progress
*/
UserProgressUpdate = 'UserProgressUpdate',
UserProgressUpdate = 'UserProgressUpdate',
/**
* A user updates account or preferences
*/
UserUpdate = 'UserUpdate',
UserUpdate = 'UserUpdate',
/**
* When bulk bookmarks are being converted
*/
ConvertBookmarksProgress = 'ConvertBookmarksProgress',
ConvertBookmarksProgress = 'ConvertBookmarksProgress',
/**
* When files are being scanned to calculate word count
*/
WordCountAnalyzerProgress = 'WordCountAnalyzerProgress',
WordCountAnalyzerProgress = 'WordCountAnalyzerProgress',
/**
* When the user needs to be informed, but it's not a big deal
*/
Info = 'Info',
Info = 'Info',
/**
* A user is sending files to their device
*/
SendingToDevice = 'SendingToDevice',
}
export interface Message<T> {
@ -261,6 +265,13 @@ export class MessageHubService {
payload: resp.body
});
});
this.hubConnection.on(EVENTS.SendingToDevice, resp => {
this.messagesSource.next({
event: EVENTS.SendingToDevice,
payload: resp.body
});
});
}
stopHubConnection() {