Fixed #1583 you can now delete users.

Fixed the issue where the requested by was not showing.
Finally fixed the broken poster paths
pull/1614/head
tidusjar 7 years ago
parent c3b2ef2f31
commit 1dc8eac035

@ -13,5 +13,6 @@ namespace Ombi.Core.Engine.Interfaces
RequestCountModel RequestCount();
Task<IEnumerable<T>> GetRequests(int count, int position);
Task<IEnumerable<T>> GetRequests();
Task<bool> UserHasRequest(string userId);
}
}

@ -120,7 +120,10 @@ namespace Ombi.Core.Engine
public async Task<IEnumerable<MovieRequests>> GetRequests(int count, int position)
{
var allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).ToListAsync();
allRequests.ForEach(x => PosterPathHelper.FixPosterPath(x.PosterPath));
allRequests.ForEach(x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
});
return allRequests;
}
@ -258,6 +261,11 @@ namespace Ombi.Core.Engine
await MovieRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)
{
return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
}
private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName)
{
await MovieRepository.Add(model);

@ -242,6 +242,11 @@ namespace Ombi.Core.Engine
await TvRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)
{
return await TvRepository.GetChild().AnyAsync(x => x.RequestedUserId == userId);
}
private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest)
{
// Add the child

@ -6,7 +6,7 @@ namespace Ombi.Core.Models.UI
public class UserViewModel
{
public string Id { get; set; }
public string Username { get; set; }
public string UserName { get; set; }
public string Alias { get; set; }
public List<ClaimCheckboxes> Claims { get; set; }
public string EmailAddress { get; set; }

@ -4,6 +4,7 @@ using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Rule.Rules.Request
{

@ -14,6 +14,8 @@ namespace Ombi.Store.Repository
IQueryable<T> GetAll();
Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate);
Task AddRange(IEnumerable<T> content);
Task DeleteRange(IEnumerable<T> req);
Task Delete(T request);
IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>(
IQueryable<TEntity> source, Expression<Func<TEntity, TProperty>> navigationPropertyPath)

@ -48,6 +48,18 @@ namespace Ombi.Store.Repository
return content;
}
public async Task Delete(T request)
{
_db.Remove(request);
await _ctx.SaveChangesAsync();
}
public async Task DeleteRange(IEnumerable<T> req)
{
_db.RemoveRange(req);
await _ctx.SaveChangesAsync();
}
public IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>(
IQueryable<TEntity> source, Expression<Func<TEntity, TProperty>> navigationPropertyPath)
where TEntity : class

@ -2,12 +2,11 @@
using System.Threading.Tasks;
using Ombi.Store.Entities.Requests;
namespace Ombi.Store.Repository
namespace Ombi.Store.Repository.Requests
{
public interface IMovieRequestRepository : IRepository<MovieRequests>
{
Task<MovieRequests> Add(MovieRequests request);
Task Delete(MovieRequests request);
Task<MovieRequests> GetRequestAsync(int theMovieDbId);
MovieRequests GetRequest(int theMovieDbId);
Task Update(MovieRequests request);

@ -1,11 +1,12 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Store.Context;
using Ombi.Store.Entities.Requests;
namespace Ombi.Store.Repository.Requests
{
public interface ITvRequestRepository
public interface ITvRequestRepository
{
IOmbiContext Db { get; }
Task<TvRequests> Add(TvRequests request);
@ -19,5 +20,6 @@ namespace Ombi.Store.Repository.Requests
Task UpdateChild(ChildRequests request);
IQueryable<ChildRequests> GetChild();
Task Save();
Task DeleteChildRange(IEnumerable<ChildRequests> request);
}
}

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@ -46,12 +47,6 @@ namespace Ombi.Store.Repository.Requests
.AsQueryable();
}
public async Task Delete(MovieRequests request)
{
Db.MovieRequests.Remove(request);
await Db.SaveChangesAsync();
}
public async Task Update(MovieRequests request)
{
if (Db.Entry(request).State == EntityState.Detached)

@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Store.Context;
@ -82,13 +83,19 @@ namespace Ombi.Store.Repository.Requests
Db.TvRequests.Remove(request);
await Db.SaveChangesAsync();
}
public async Task DeleteChild(ChildRequests request)
{
Db.ChildRequests.Remove(request);
await Db.SaveChangesAsync();
}
public async Task DeleteChildRange(IEnumerable<ChildRequests> request)
{
Db.ChildRequests.RemoveRange(request);
await Db.SaveChangesAsync();
}
public async Task Update(TvRequests request)
{
Db.Update(request);

@ -0,0 +1,5 @@
{
"cSpell.words": [
"usermanagement"
]
}

@ -10,7 +10,7 @@ import { RouterModule, Routes } from "@angular/router";
//import { DragulaModule, DragulaService } from 'ng2-dragula/ng2-dragula';
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { GrowlModule } from "primeng/components/growl/growl";
import { ButtonModule, CaptchaModule, DataTableModule,DialogModule, SharedModule, TooltipModule } from "primeng/primeng";
import { ButtonModule, CaptchaModule,ConfirmationService, ConfirmDialogModule, DataTableModule,DialogModule, SharedModule, TooltipModule } from "primeng/primeng";
// Components
import { AppComponent } from "./app.component";
@ -80,6 +80,7 @@ const routes: Routes = [
RequestsModule,
CaptchaModule,
TooltipModule,
ConfirmDialogModule,
],
declarations: [
AppComponent,
@ -98,6 +99,7 @@ const routes: Routes = [
IdentityService,
StatusService,
LandingPageService,
ConfirmationService,
ImageService,
//DragulaService
],

@ -2,7 +2,7 @@
export interface IUser {
id: string;
username: string;
userName: string;
alias: string;
claims: ICheckbox[];
emailAddress: string;

@ -30,9 +30,9 @@
<br />
<div>
<span>Requested By: </span>
<span *ngIf="!isAdmin">{{request.requestedUser.username}}</span>
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.username}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
</div>
<div>
<span>Status: </span>

@ -47,6 +47,10 @@ export class IdentityService extends ServiceAuthHelpers {
return this.http.delete(`${this.url}${user.id}`, { headers: this.headers }).map(this.extractData);
}
public hasUserRequested(userId: string): Observable<boolean> {
return this.http.get(`${this.url}userhasrequest/${userId}`).map(this.extractData);
}
public submitResetPassword(email: string): Observable<IIdentityResult> {
return this.regularHttp.post(this.url + "reset", JSON.stringify({email}), { headers: this.headers }).map(this.extractData);
}

@ -20,7 +20,7 @@ export class UpdateDetailsComponent implements OnInit {
const localUser = x as IUpdateLocalUser;
this.form = this.fb.group({
id:[localUser.id],
username: [localUser.username],
username: [localUser.userName],
emailAddress: [localUser.emailAddress, [Validators.email]],
confirmNewPassword: [localUser.confirmNewPassword],
currentPassword: [localUser.currentPassword, [Validators.required]],

@ -14,7 +14,7 @@
<div class="form-group">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" [(ngModel)]="user.username" class="form-control form-control-custom " id="username" name="username" value="{{user?.username}}">
<input type="text" [(ngModel)]="user.userName" class="form-control form-control-custom " id="username" name="username" value="{{user?.userName}}">
</div>
</div>
<div class="form-group">

@ -24,7 +24,7 @@ export class UserManagementAddComponent implements OnInit {
emailAddress: "",
id: "",
password: "",
username: "",
userName: "",
userType: UserType.LocalUser,
checked:false,
hasLoggedIn: false,
@ -54,7 +54,7 @@ export class UserManagementAddComponent implements OnInit {
this.identityService.createUser(this.user).subscribe(x => {
if (x.successful) {
this.notificationSerivce.success("Updated", `The user ${this.user.username} has been created successfully`);
this.notificationSerivce.success("Updated", `The user ${this.user.userName} has been created successfully`);
this.router.navigate(["usermanagement"]);
} else {
x.errors.forEach((val) => {

@ -1,8 +1,10 @@
<div *ngIf="user">
<h3>User: {{user.username}}</h3>
<h3>User: {{user.userName}}</h3>
<button type="button" class="btn btn-primary-outline" style="float:right;" [routerLink]="['/usermanagement/']">Back</button>
<p-confirmDialog></p-confirmDialog>
<div class="modal-body" style="margin-top: 45px;">
<div class="col-md-6">
<h4>User Details</h4>
@ -14,7 +16,7 @@
<div class="form-group">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" [(ngModel)]="user.username" [readonly]="true" class="form-control form-control-custom " id="username" name="username" value="{{user?.username}}">
<input type="text" [(ngModel)]="user.userName" [readonly]="true" class="form-control form-control-custom " id="username" name="username" value="{{user?.userName}}">
</div>
</div>
<div class="form-group">

@ -1,5 +1,6 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ConfirmationService } from "primeng/primeng";
import { ActivatedRoute } from "@angular/router";
import { IUser } from "../interfaces";
@ -12,11 +13,12 @@ import { NotificationService } from "../services";
export class UserManagementEditComponent {
public user: IUser;
public userId: string;
constructor(private identityService: IdentityService,
private route: ActivatedRoute,
private notificationSerivce: NotificationService,
private router: Router) {
private notificationService: NotificationService,
private router: Router,
private confirmationService: ConfirmationService) {
this.route.params
.subscribe((params: any) => {
this.userId = params.id;
@ -28,27 +30,38 @@ export class UserManagementEditComponent {
}
public delete() {
this.identityService.deleteUser(this.user).subscribe(x => {
if (x.successful) {
this.notificationSerivce.success("Deleted", `The user ${this.user.username} was deleted`);
this.router.navigate(["usermanagement"]);
} else {
x.errors.forEach((val) => {
this.notificationSerivce.error("Error", val);
});
}
});
this.confirmationService.confirm({
message: "Are you sure that you want to delete this user? If this user has any requests they will also be deleted.",
header: "Are you sure?",
icon: "fa fa-trash",
accept: () => {
this.identityService.deleteUser(this.user).subscribe(x => {
if (x.successful) {
this.notificationService.success("Deleted", `The user ${this.user.userName} was deleted`);
this.router.navigate(["usermanagement"]);
} else {
x.errors.forEach((val) => {
this.notificationService.error("Error", val);
});
}
});
},
reject: () => {
return;
},
});
}
public resetPassword() {
this.identityService.submitResetPassword(this.user.emailAddress).subscribe(x => {
if (x.successful) {
this.notificationSerivce.success("Reset", `Sent reset password email to ${this.user.emailAddress}`);
this.notificationService.success("Reset", `Sent reset password email to ${this.user.emailAddress}`);
this.router.navigate(["usermanagement"]);
} else {
x.errors.forEach((val) => {
this.notificationSerivce.error("Error", val);
this.notificationService.error("Error", val);
});
}
@ -63,17 +76,17 @@ export class UserManagementEditComponent {
});
if (!hasClaims) {
this.notificationSerivce.error("Error", "Please assign a role");
this.notificationService.error("Error", "Please assign a role");
return;
}
this.identityService.updateUser(this.user).subscribe(x => {
if (x.successful) {
this.notificationSerivce.success("Updated", `The user ${this.user.username} has been updated successfully`);
this.notificationService.success("Updated", `The user ${this.user.userName} has been updated successfully`);
this.router.navigate(["usermanagement"]);
} else {
x.errors.forEach((val) => {
this.notificationSerivce.error("Error", val);
this.notificationService.error("Error", val);
});
}
});

@ -63,7 +63,7 @@
<input type="checkbox" [(ngModel)]="u.checked">
</td>
<td>
{{u.username}}
{{u.userName}}
</td>
<td>
{{u.alias}}
@ -97,108 +97,4 @@
<div class="modal fade in" *ngIf="showEditDialog" style="display: block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" (click)="showEditDialog=false">&times;</button>
<h4 class="modal-title">Editing User {{selectedUser?.username}}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" [(ngModel)]="selectedUser.username" [readonly]="true" class="form-control form-control-custom " id="username" name="username" value="{{selectedUser?.username}}">
</div>
</div>
<div class="form-group">
<label for="alias" class="control-label">Alias</label>
<div>
<input type="text" [(ngModel)]="selectedUser.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{selectedUser?.alias}}">
</div>
</div>
<div class="form-group">
<label for="alias" class="control-label">Email Address</label>
<div>
<input type="text" [(ngModel)]="selectedUser.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{selectedUser?.emailAddress}}">
</div>
</div>
<div *ngFor="let c of selectedUser.claims">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" id="create{{c.value}}" [attr.name]="'create' + c.value" ng-checked="c.enabled">
<label for="create{{c.value}}">{{c.value}}</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" (click)="showEditDialog=false">Close</button>
<button type="button" class="btn btn-primary-outline" (click)="updateUser()">Save changes</button>
</div>
</div>
</div>
</div>
<div>
<div class="modal fade in " *ngIf="showCreateDialogue" style="display: block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" (click)="showCreateDialogue=false">&times;</button>
<h4 class="modal-title">Create User</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" [(ngModel)]="createdUser.username" class="form-control form-control-custom " id="username" name="username" value="{{createdUser?.username}}">
</div>
</div>
<div class="form-group">
<label for="alias" class="control-label">Alias</label>
<div>
<input type="text" [(ngModel)]="createdUser.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{createdUser?.alias}}">
</div>
</div>
<div class="form-group">
<label for="emailAddress" class="control-label">Email Address</label>
<div>
<input type="text" [(ngModel)]="createdUser.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{createdUser?.emailAddress}}">
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">Password</label>
<div>
<input type="password" [(ngModel)]="createdUser.password" class="form-control form-control-custom " id="password" name="password" value="{{createdUser?.password}}">
</div>
</div>
<div *ngFor="let c of availableClaims">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" id="create{{c.value}}" [attr.name]="'create' + c.value" ng-checked="c.enabled">
<label for="create{{c.value}}">{{c.value}}</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" (click)="showCreateDialogue=false">Close</button>
<button type="button" class="btn btn-primary-outline" (click)="create()">Add User</button>
</div>
</div>
</div>
</div>
</div>

@ -2,7 +2,7 @@
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule, Routes } from "@angular/router";
import { MultiSelectModule, TooltipModule } from "primeng/primeng";
import { ConfirmationService, ConfirmDialogModule, MultiSelectModule, TooltipModule } from "primeng/primeng";
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
@ -32,7 +32,7 @@ const routes: Routes = [
NgbModule.forRoot(),
MultiSelectModule,
PipeModule,
ConfirmDialogModule,
TooltipModule,
],
declarations: [
@ -46,6 +46,7 @@ const routes: Routes = [
],
providers: [
IdentityService,
ConfirmationService,
],
})

@ -300,4 +300,14 @@ button.list-group-item:focus {
.ui-state-default {
border: 2px solid $bg-colour-disabled;
}
.ui-button {
background: $primary-colour $i;
border: 1px solid $primary-colour-outline $i;
}
.ui-dialog-titlebar{
background:$bg-colour-disabled;
color:white;
}

@ -27,6 +27,8 @@ using Ombi.Schedule.Jobs.Ombi;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using OmbiIdentityResult = Ombi.Models.Identity.IdentityResult;
namespace Ombi.Controllers
@ -41,7 +43,9 @@ namespace Ombi.Controllers
public IdentityController(OmbiUserManager user, IMapper mapper, RoleManager<IdentityRole> rm, IEmailProvider prov,
ISettingsService<EmailNotificationSettings> s,
ISettingsService<CustomizationSettings> c,
IWelcomeEmail welcome)
IWelcomeEmail welcome,
IMovieRequestRepository m,
ITvRequestRepository t)
{
UserManager = user;
Mapper = mapper;
@ -50,6 +54,8 @@ namespace Ombi.Controllers
EmailSettings = s;
CustomizationSettings = c;
WelcomeEmail = welcome;
MovieRepo = m;
TvRepo = t;
}
private OmbiUserManager UserManager { get; }
@ -59,6 +65,8 @@ namespace Ombi.Controllers
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private IWelcomeEmail WelcomeEmail { get; }
private IMovieRequestRepository MovieRepo { get; }
private ITvRequestRepository TvRepo { get; }
/// <summary>
/// This is what the Wizard will call when creating the user for the very first time.
@ -134,7 +142,7 @@ namespace Ombi.Controllers
model.Add(await GetUserWithRoles(user));
}
return model.OrderBy(x => x.Username);
return model.OrderBy(x => x.UserName);
}
/// <summary>
@ -169,7 +177,7 @@ namespace Ombi.Controllers
var vm = new UserViewModel
{
Alias = user.Alias,
Username = user.UserName,
UserName = user.UserName,
Id = user.Id,
EmailAddress = user.Email,
UserType = (Core.Models.UserType)(int)user.UserType,
@ -223,7 +231,7 @@ namespace Ombi.Controllers
{
Alias = user.Alias,
Email = user.EmailAddress,
UserName = user.Username,
UserName = user.UserName,
UserType = UserType.LocalUser,
};
var userResult = await UserManager.CreateAsync(ombiUser, user.Password);
@ -423,6 +431,20 @@ namespace Ombi.Controllers
{
return Error("You do not have the correct permissions to delete this user");
}
// We need to delete all the requests first
var moviesUserRequested = MovieRepo.GetAll().Where(x => x.RequestedUserId == userId);
var tvUserRequested = TvRepo.GetChild().Where(x => x.RequestedUserId == userId);
if (moviesUserRequested.Any())
{
await MovieRepo.DeleteRange(moviesUserRequested);
}
if (tvUserRequested.Any())
{
await TvRepo.DeleteChildRange(tvUserRequested);
}
var result = await UserManager.DeleteAsync(userToDelete);
if (result.Succeeded)
{
@ -559,7 +581,7 @@ namespace Ombi.Controllers
var ombiUser = new OmbiUser
{
Email = user.EmailAddress,
UserName = user.Username
UserName = user.UserName
};
BackgroundJob.Enqueue(() => WelcomeEmail.SendEmail(ombiUser));
}

@ -279,6 +279,21 @@ namespace Ombi.Controllers
return TvRequestEngine.RequestCount();
}
/// <summary>
/// Checks if the passed in user has a request
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[HttpGet("userhasrequest")]
public async Task<bool> UserHasRequest(string userId)
{
var movies = await MovieRequestEngine.UserHasRequest(userId);
var tv = await TvRequestEngine.UserHasRequest(userId);
return movies || tv;
}
///// <summary>
///// Gets the specific grid model for the requests (for modelling the UI).
///// </summary>

@ -44,7 +44,7 @@ namespace Ombi.Controllers
public async Task<IActionResult> GetToken([FromBody] UserAuthModel model)
{
await _audit.Record(AuditType.None, AuditArea.Authentication,
$"Username {model.Username} attempting to authenticate");
$"UserName {model.Username} attempting to authenticate");
var user = await _userManager.FindByNameAsync(model.Username);

Loading…
Cancel
Save