Nearing feature complete for driver auth
This commit is contained in:
@@ -7,15 +7,15 @@ namespace Web.Tests.Utility
|
||||
[TestFixture]
|
||||
public class AlgorithmsTests
|
||||
{
|
||||
private const int _existingNumber = 2;
|
||||
private const int ExistingNumber = 2;
|
||||
private readonly DateTime _existingDate = DateTime.Today.AddDays(-1);
|
||||
|
||||
[Test]
|
||||
public void IsChronological_True_If_Greater_And_After()
|
||||
{
|
||||
var date = _existingDate.AddDays(1);
|
||||
const int number = _existingNumber + 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, _existingNumber, date, number);
|
||||
const int number = ExistingNumber + 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, ExistingNumber, date, number);
|
||||
|
||||
Assert.That(result);
|
||||
}
|
||||
@@ -24,8 +24,8 @@ namespace Web.Tests.Utility
|
||||
public void IsChronological_False_If_LessThan_And_After()
|
||||
{
|
||||
var date = _existingDate.AddDays(1);
|
||||
const int number = _existingNumber - 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, _existingNumber, date, number);
|
||||
const int number = ExistingNumber - 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, ExistingNumber, date, number);
|
||||
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
@@ -34,8 +34,8 @@ namespace Web.Tests.Utility
|
||||
public void IsChronological_True_If_LessThan_And_Before()
|
||||
{
|
||||
var date = _existingDate.AddDays(-1);
|
||||
const int number = _existingNumber - 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, _existingNumber, date, number);
|
||||
const int number = ExistingNumber - 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, ExistingNumber, date, number);
|
||||
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
@@ -44,8 +44,8 @@ namespace Web.Tests.Utility
|
||||
public void IsChronological_False_If_GreaterThan_And_Before()
|
||||
{
|
||||
var date = _existingDate.AddDays(-1);
|
||||
const int number = _existingNumber + 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, _existingNumber, date, number);
|
||||
const int number = ExistingNumber + 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, ExistingNumber, date, number);
|
||||
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
@@ -54,8 +54,8 @@ namespace Web.Tests.Utility
|
||||
public void IsChronological_True_If_Same_And_Before()
|
||||
{
|
||||
var date = _existingDate.AddDays(-1);
|
||||
const int number = _existingNumber;
|
||||
var result = Algorithms.IsChronological(_existingDate, _existingNumber, date, number);
|
||||
const int number = ExistingNumber;
|
||||
var result = Algorithms.IsChronological(_existingDate, ExistingNumber, date, number);
|
||||
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
@@ -65,8 +65,8 @@ namespace Web.Tests.Utility
|
||||
public void IsChronological_True_If_Greater_And_SameTime()
|
||||
{
|
||||
var date = _existingDate;
|
||||
const int number = _existingNumber + 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, _existingNumber, date, number);
|
||||
const int number = ExistingNumber + 1;
|
||||
var result = Algorithms.IsChronological(_existingDate, ExistingNumber, date, number);
|
||||
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MileageTraker.Web.Utility;
|
||||
using NUnit.Framework;
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using MileageTraker.Web.Models;
|
||||
using MileageTraker.Web.ViewModels.Log;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Web.Tests.ViewModels.Log
|
||||
{
|
||||
[TestFixture]
|
||||
public class LogViewModelTests
|
||||
{
|
||||
[Test]
|
||||
public void GetLog_Converts_To_Log()
|
||||
{
|
||||
var logId = 1;
|
||||
var cityName = "My Town";
|
||||
var date = DateTime.Today;
|
||||
var endOdometer = "1234";
|
||||
var driver = "name";
|
||||
var ethraId = "4567";
|
||||
string gasPurchased = "2.546";
|
||||
var mileageLogType = new MileageLogTypeWrapper { Enum = MileageLogType.Commuting };
|
||||
|
||||
var viewModel =
|
||||
new LogViewModel
|
||||
{
|
||||
LogId = logId,
|
||||
CityName = cityName,
|
||||
Date = date,
|
||||
EndOdometer = endOdometer,
|
||||
UserFullName = driver,
|
||||
VehicleId = ethraId,
|
||||
GasPurchased = gasPurchased,
|
||||
LogType = mileageLogType
|
||||
};
|
||||
|
||||
var log = viewModel.GetLog();
|
||||
|
||||
Assert.That(log.LogId, Is.EqualTo(logId));
|
||||
Assert.That(log.CityName, Is.EqualTo(cityName));
|
||||
Assert.That(log.Date, Is.EqualTo(date));
|
||||
Assert.That(log.EndOdometer.ToString(), Is.EqualTo(endOdometer));
|
||||
Assert.That(log.VehicleId, Is.EqualTo(ethraId));
|
||||
Assert.That(log.GasPurchased.ToString(), Is.EqualTo(gasPurchased));
|
||||
Assert.That(log.LogType.Enum, Is.EqualTo(mileageLogType.Enum));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateLog_Converts_To_Log()
|
||||
{
|
||||
var viewModelLogId = 1;
|
||||
var viewModelCityName = "My Town";
|
||||
var viewModelDate = DateTime.Today;
|
||||
var viewModelEndOdometer = "1234";
|
||||
var viewModelDriver = "name";
|
||||
var viewModelVehicleId = "4567";
|
||||
string viewModelGasPurchased = "2.546";
|
||||
var viewModelLogType = new MileageLogTypeWrapper { Enum = MileageLogType.Commuting };
|
||||
|
||||
var viewModel =
|
||||
new LogViewModel
|
||||
{
|
||||
LogId = viewModelLogId,
|
||||
CityName = viewModelCityName,
|
||||
Date = viewModelDate,
|
||||
EndOdometer = viewModelEndOdometer,
|
||||
UserFullName = viewModelDriver,
|
||||
VehicleId = viewModelVehicleId,
|
||||
GasPurchased = viewModelGasPurchased,
|
||||
LogType = viewModelLogType
|
||||
};
|
||||
|
||||
var logUser = new User{Username = "log"};
|
||||
var logSource = "here";
|
||||
var log =
|
||||
new MileageTraker.Web.Models.Log
|
||||
{
|
||||
LogId = 2,
|
||||
CityName = "Different Town",
|
||||
Date = DateTime.MinValue,
|
||||
EndOdometer = 1,
|
||||
VehicleId = "1",
|
||||
GasPurchased = 1.0,
|
||||
LogType = new MileageLogTypeWrapper { Enum = MileageLogType.GasPurchase },
|
||||
User = logUser,
|
||||
Source = logSource
|
||||
};
|
||||
|
||||
viewModel.UpdateLog(log);
|
||||
|
||||
Assert.That(log.LogId, Is.EqualTo(viewModelLogId));
|
||||
Assert.That(log.CityName, Is.EqualTo(viewModelCityName));
|
||||
Assert.That(log.Date, Is.EqualTo(viewModelDate));
|
||||
Assert.That(log.EndOdometer.ToString(), Is.EqualTo(viewModelEndOdometer));
|
||||
Assert.That(log.VehicleId, Is.EqualTo(viewModelVehicleId));
|
||||
Assert.That(log.GasPurchased.ToString(), Is.EqualTo(viewModelGasPurchased));
|
||||
Assert.That(log.LogType.Enum, Is.EqualTo(viewModelLogType.Enum));
|
||||
|
||||
Assert.That(log.User, Is.EqualTo(logUser));
|
||||
Assert.That(log.Source, Is.EqualTo(logSource));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_FromLog()
|
||||
{
|
||||
var logId = 1;
|
||||
var cityName = "My Town";
|
||||
var date = DateTime.Today;
|
||||
var endOdometer = 1234;
|
||||
var driver = "name";
|
||||
var ethraId = "4567";
|
||||
var gasPurchased = 2.546;
|
||||
var mileageLogType = new MileageLogTypeWrapper { Enum = MileageLogType.Commuting };
|
||||
|
||||
var log =
|
||||
new MileageTraker.Web.Models.Log
|
||||
{
|
||||
LogId = logId,
|
||||
CityName = cityName,
|
||||
Date = date,
|
||||
EndOdometer = endOdometer,
|
||||
VehicleId = ethraId,
|
||||
GasPurchased = gasPurchased,
|
||||
LogType = mileageLogType
|
||||
};
|
||||
|
||||
var viewModel = new LogViewModel(log);
|
||||
|
||||
Assert.That(viewModel.LogId, Is.EqualTo(logId));
|
||||
Assert.That(viewModel.CityName, Is.EqualTo(cityName));
|
||||
Assert.That(viewModel.Date, Is.EqualTo(date));
|
||||
Assert.That(viewModel.EndOdometer, Is.EqualTo(endOdometer.ToString()));
|
||||
Assert.That(viewModel.VehicleId, Is.EqualTo(ethraId));
|
||||
Assert.That(viewModel.GasPurchased, Is.EqualTo(gasPurchased.ToString()));
|
||||
Assert.That(viewModel.LogType.Enum, Is.EqualTo(mileageLogType.Enum));
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -34,7 +34,6 @@ namespace Web.Tests.ViewModels
|
||||
var email = vm.Email = "bob@dobalina.com";
|
||||
var username = vm.Username = "bobdobalina";
|
||||
var fullName = vm.FullName = "Bob Dobalina";
|
||||
var roles = vm.Roles = new [] {"Administrator", "Developer"};
|
||||
var userId = vm.UserId = Guid.NewGuid();
|
||||
vm.UserId = Guid.NewGuid();
|
||||
|
||||
@@ -89,9 +89,10 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Utility\AlgorithmsTests.cs" />
|
||||
<Compile Include="Utility\CustomExtensionsTests.cs" />
|
||||
<Compile Include="ViewModels\CreateLogViewModelTests.cs" />
|
||||
<Compile Include="ViewModels\CreateUserViewModelTests.cs" />
|
||||
<Compile Include="ViewModels\EditUserViewModelTests.cs" />
|
||||
<Compile Include="ViewModels\CreateLog\CreateLogViewModelTests.cs" />
|
||||
<Compile Include="ViewModels\Log\LogViewModelTests.cs" />
|
||||
<Compile Include="ViewModels\User\CreateUserViewModelTests.cs" />
|
||||
<Compile Include="ViewModels\User\EditUserViewModelTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="DAL\Vehicles.xls">
|
||||
|
||||
Binary file not shown.
@@ -9,6 +9,7 @@ namespace MileageTraker.Web.Attributes
|
||||
{
|
||||
if (filterContext != null)
|
||||
{
|
||||
// TODO: capture current user
|
||||
var controller = filterContext.RouteData.Values["controller"].ToString();
|
||||
var action = filterContext.RouteData.Values["action"].ToString();
|
||||
var loggerName = string.Format("{0}Controller.{1}", controller, action);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Web.Mvc;
|
||||
using System.Web.Security;
|
||||
using MileageTraker.Web.DAL;
|
||||
using MileageTraker.Web.Email;
|
||||
using MileageTraker.Web.Models;
|
||||
using MileageTraker.Web.Utility;
|
||||
using MileageTraker.Web.ViewModels.Account;
|
||||
|
||||
@@ -146,20 +147,9 @@ namespace MileageTraker.Web.Controllers
|
||||
var user = DataService.FindUserByUsername(viewModel.Username);
|
||||
if (user != null && Request.Url != null)
|
||||
{
|
||||
var passwordResetToken = Algorithms.GenerateToken();
|
||||
var url = Request.Url.Scheme +
|
||||
@"://" +
|
||||
Request.Url.Authority +
|
||||
Url.Action("NewPassword", "Account",
|
||||
new NewPasswordViewModel
|
||||
{
|
||||
UserId = user.UserId,
|
||||
PasswordResetToken = passwordResetToken
|
||||
});
|
||||
user.PasswordResetToken = passwordResetToken;
|
||||
DataService.UpdateUser(user);
|
||||
var email = new EmailNotificationService();
|
||||
email.NotifyResetPassword(user,url);
|
||||
var resetPasswordUrl = ResetPassword(user);
|
||||
email.SendResetPassword(user, resetPasswordUrl);
|
||||
}
|
||||
|
||||
TempData["StatusMessage"] = "Please check your email - we have sent a request for you to reset the password.";
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
using MileageTraker.Web.Attributes;
|
||||
using MileageTraker.Web.DAL;
|
||||
using MileageTraker.Web.Models;
|
||||
using MileageTraker.Web.Utility;
|
||||
using MileageTraker.Web.ViewModels.Account;
|
||||
|
||||
namespace MileageTraker.Web.Controllers
|
||||
{
|
||||
@@ -28,5 +32,45 @@ namespace MileageTraker.Web.Controllers
|
||||
|
||||
base.OnException(filterContext);
|
||||
}
|
||||
|
||||
protected string GetCookieValue(string key)
|
||||
{
|
||||
return
|
||||
HttpContext.Request.Cookies[key] != null
|
||||
? HttpContext.Request.Cookies[key].Value
|
||||
: null;
|
||||
}
|
||||
|
||||
protected void SetCookieValue(string key, string value)
|
||||
{
|
||||
var cookies = HttpContext.Response.Cookies;
|
||||
var httpCookie = cookies[key];
|
||||
if (httpCookie == null) return;
|
||||
|
||||
httpCookie.Value = value;
|
||||
httpCookie.Expires = DateTime.MaxValue;
|
||||
}
|
||||
|
||||
protected string LinkForUrlAction(string urlAction)
|
||||
{
|
||||
return
|
||||
string.Format("{0}://{1}{2}",
|
||||
Request.Url.Scheme, Request.Url.Authority, urlAction);
|
||||
}
|
||||
|
||||
protected string ResetPassword(User user)
|
||||
{
|
||||
var passwordResetToken = Algorithms.GenerateToken();
|
||||
var url = LinkForUrlAction(
|
||||
Url.Action("NewPassword", "Account",
|
||||
new NewPasswordViewModel
|
||||
{
|
||||
UserId = user.UserId,
|
||||
PasswordResetToken = passwordResetToken
|
||||
}));
|
||||
user.PasswordResetToken = passwordResetToken;
|
||||
DataService.UpdateUser(user);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using MileageTraker.Web.ViewModels.CreateLog;
|
||||
|
||||
namespace MileageTraker.Web.Controllers
|
||||
{
|
||||
[Authorize(Roles = "Driver, Administrator, Developer")]
|
||||
public class CreateLogController : ControllerBase
|
||||
{
|
||||
private const string CookeNameVehicleid = "mr_vehicleId";
|
||||
@@ -72,10 +73,12 @@ namespace MileageTraker.Web.Controllers
|
||||
return View("Index", model);
|
||||
}
|
||||
|
||||
public PartialViewResult RecentLogs(string employeeName)
|
||||
public PartialViewResult RecentLogs()
|
||||
{
|
||||
var logs = DataService.GetRecentLogsByEmployee(employeeName);
|
||||
ViewData["employeeName"] = employeeName;
|
||||
var username = User.Identity.Name;
|
||||
var user = DataService.FindUserByUsername(username);
|
||||
var logs = DataService.GetRecentLogsByUsername(user.Username);
|
||||
ViewData["name"] = user.FullName;
|
||||
return PartialView(logs);
|
||||
}
|
||||
|
||||
@@ -92,20 +95,5 @@ namespace MileageTraker.Web.Controllers
|
||||
Date = DateTime.Today
|
||||
};
|
||||
}
|
||||
|
||||
private string GetCookieValue(string key)
|
||||
{
|
||||
return
|
||||
HttpContext.Request.Cookies[key] != null
|
||||
? HttpContext.Request.Cookies[key].Value
|
||||
: null;
|
||||
}
|
||||
|
||||
private void SetCookieValue(string key, string value)
|
||||
{
|
||||
var cookies = HttpContext.Response.Cookies;
|
||||
cookies[key].Value = value;
|
||||
cookies[key].Expires = DateTime.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using MileageTraker.Web.Attributes;
|
||||
using MileageTraker.Web.DAL;
|
||||
using MileageTraker.Web.Models;
|
||||
using MileageTraker.Web.Utility;
|
||||
using MileageTraker.Web.ViewModels;
|
||||
using MileageTraker.Web.ViewModels.Log;
|
||||
@@ -84,7 +83,8 @@ namespace MileageTraker.Web.Controllers
|
||||
public ViewResult Details(int id)
|
||||
{
|
||||
var log = DataService.GetLog(id);
|
||||
return View(log);
|
||||
var viewModel = new LogViewModel(log);
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
public ActionResult PreviousDetails(int id)
|
||||
@@ -121,22 +121,24 @@ namespace MileageTraker.Web.Controllers
|
||||
|
||||
public ActionResult Create()
|
||||
{
|
||||
var log = new Log { Date = DateTime.Today };
|
||||
var viewModel = new LogViewModel { Date = DateTime.Today };
|
||||
var vehicleId = Request["vehicleId"];
|
||||
|
||||
if (vehicleId != null)
|
||||
{
|
||||
log.VehicleId = vehicleId;
|
||||
viewModel.VehicleId = vehicleId;
|
||||
}
|
||||
return View(log);
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ActionLog]
|
||||
public ActionResult Create(Log log)
|
||||
public ActionResult Create(LogViewModel viewModel)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var log = viewModel.GetLog();
|
||||
log.User = DataService.FindUserByFullName(viewModel.UserFullName);
|
||||
log.Source = HttpContext.Request.Url.AbsolutePath;
|
||||
log.UserHostAddress = HttpContext.Request.UserHostAddress;
|
||||
log.UserAgent = HttpContext.Request.UserAgent;
|
||||
@@ -147,42 +149,39 @@ namespace MileageTraker.Web.Controllers
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View(log);
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
public ActionResult Edit(int id)
|
||||
{
|
||||
var log = DataService.GetLog(id);
|
||||
return View(log);
|
||||
var logViewModel = new LogViewModel(log);
|
||||
return View(logViewModel);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ActionLog]
|
||||
public ActionResult Edit(Log log)
|
||||
public ActionResult Edit(LogViewModel viewModel)
|
||||
{
|
||||
RemoveModelStateErrors();
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var log = DataService.GetLog(viewModel.LogId);
|
||||
viewModel.UpdateLog(log);
|
||||
log.User = DataService.FindUserByFullName(viewModel.UserFullName);
|
||||
|
||||
DataService.UpdateLog(log);
|
||||
|
||||
TempData["StatusMessage"] = "Log updated";
|
||||
return RedirectToAction("Details", new{id = log.LogId});
|
||||
}
|
||||
return View(log);
|
||||
}
|
||||
|
||||
private void RemoveModelStateErrors()
|
||||
{
|
||||
ModelState.Remove("Source");
|
||||
ModelState.Remove("UserAgent");
|
||||
ModelState.Remove("UserHostAddress");
|
||||
ModelState.Remove("VehiclePreviousLogId");
|
||||
ModelState.Remove("VehiclePreviousLog");
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
public ActionResult Delete(int id)
|
||||
{
|
||||
var log = DataService.GetLog(id);
|
||||
return View(log);
|
||||
var viewModel = new LogViewModel(log);
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpPost, ActionName("Delete")]
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Security;
|
||||
using MileageTraker.Web.Email;
|
||||
using MileageTraker.Web.ViewModels;
|
||||
using MileageTraker.Web.ViewModels.Account;
|
||||
using MileageTraker.Web.ViewModels.User;
|
||||
|
||||
namespace MileageTraker.Web.Controllers
|
||||
@@ -21,6 +24,8 @@ namespace MileageTraker.Web.Controllers
|
||||
return View(users);
|
||||
}
|
||||
|
||||
//todo: add export
|
||||
|
||||
public ActionResult Details(Guid id)
|
||||
{
|
||||
var user = Membership.GetUser(id);
|
||||
@@ -48,17 +53,26 @@ namespace MileageTraker.Web.Controllers
|
||||
return Json(user == null, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
public JsonResult EmailAvailable(string email)
|
||||
public JsonResult FullNameAvailable(string fullname)
|
||||
{
|
||||
var user = DataService.FindUserByEmail(email);
|
||||
var user = DataService.FindUserByFullName(fullname);
|
||||
return Json(user == null, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
public JsonResult ExistsByFullName(string userFullName)
|
||||
{
|
||||
var user = DataService.FindUserByFullName(userFullName);
|
||||
return Json(user != null, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
public ActionResult Create()
|
||||
{
|
||||
var vm = new CreateUserViewModel
|
||||
{
|
||||
AvailableRoles = Roles.GetAllRoles()
|
||||
Roles = new CheckBoxViewModel{
|
||||
Available = Roles.GetAllRoles(),
|
||||
Selected = new []{"Driver"} // default driver
|
||||
}
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
@@ -73,7 +87,7 @@ namespace MileageTraker.Web.Controllers
|
||||
var membershipUser =
|
||||
Membership.CreateUser(
|
||||
viewModel.Username,
|
||||
viewModel.Password,
|
||||
"uninitialized_state",
|
||||
viewModel.Email,
|
||||
null,
|
||||
null,
|
||||
@@ -83,26 +97,36 @@ namespace MileageTraker.Web.Controllers
|
||||
if (membershipUser == null)
|
||||
{
|
||||
ModelState.AddModelError("", ErrorCodeToString(membershipCreateStatus));
|
||||
viewModel.AvailableRoles = Roles.GetAllRoles();
|
||||
viewModel.Roles.Available = Roles.GetAllRoles();
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
if (viewModel.Roles != null && viewModel.Roles.Any())
|
||||
if (viewModel.Roles.Selected != null && viewModel.Roles.Selected.Any())
|
||||
{
|
||||
Roles.AddUserToRoles(
|
||||
membershipUser.UserName,
|
||||
viewModel.Roles);
|
||||
viewModel.Roles.Selected);
|
||||
}
|
||||
|
||||
var user = DataService.GetUser((Guid) membershipUser.ProviderUserKey);
|
||||
user.FullName = viewModel.FullName;
|
||||
DataService.UpdateUserPersonalInfo(user);
|
||||
|
||||
if (viewModel.SetPassword)
|
||||
{
|
||||
TempData["StatusMessage"] = "User " + user.Username + " created";
|
||||
return RedirectToAction("SetPassword", new { id = user.UserId });
|
||||
}
|
||||
|
||||
var email = new EmailNotificationService();
|
||||
var resetPasswordUrl = ResetPassword(user);
|
||||
email.SendInitializePassword(user, resetPasswordUrl);
|
||||
|
||||
TempData["StatusMessage"] = "User " + user.Username + " created, invitation sent to " + user.Email;
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
viewModel.AvailableRoles = Roles.GetAllRoles();
|
||||
viewModel.Roles.Available = Roles.GetAllRoles();
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
@@ -116,8 +140,11 @@ namespace MileageTraker.Web.Controllers
|
||||
|
||||
var vm = new EditUserViewModel(user)
|
||||
{
|
||||
Roles = Roles.GetRolesForUser(user.Username),
|
||||
AvailableRoles = Roles.GetAllRoles()
|
||||
Roles = new CheckBoxViewModel
|
||||
{
|
||||
Selected = Roles.GetRolesForUser(user.Username),
|
||||
Available = Roles.GetAllRoles()
|
||||
}
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
@@ -133,11 +160,11 @@ namespace MileageTraker.Web.Controllers
|
||||
DataService.UpdateUserPersonalInfo(user);
|
||||
|
||||
Roles.RemoveUserFromRoles(user.Username, Roles.GetAllRoles());
|
||||
if (viewModel.Roles != null && viewModel.Roles.Any())
|
||||
if (viewModel.Roles != null)
|
||||
{
|
||||
Roles.AddUserToRoles(
|
||||
user.Username,
|
||||
viewModel.Roles);
|
||||
viewModel.Roles.Selected);
|
||||
}
|
||||
|
||||
TempData["StatusMessage"] = "Changes saved for " + user.Username;
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace MileageTraker.Web.Controllers
|
||||
return View(vehicle);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public JsonResult Exists(string vehicleId)
|
||||
{
|
||||
var vehicle = DataService.GetVehicle(vehicleId);
|
||||
|
||||
+20
-10
@@ -34,7 +34,11 @@ namespace MileageTraker.Web.DAL
|
||||
log.VehiclePreviousLog = previousLog;
|
||||
var nextLog = SearchNextLog(log);
|
||||
if (nextLog != null)
|
||||
{
|
||||
nextLog.VehiclePreviousLog = log;
|
||||
nextLog.User = _db.Users.Find(nextLog.User.UserId);
|
||||
_db.Entry(nextLog).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
_db.SaveChanges();
|
||||
|
||||
@@ -43,14 +47,7 @@ namespace MileageTraker.Web.DAL
|
||||
|
||||
public void UpdateLog(Log log)
|
||||
{
|
||||
_db.Logs.Attach(log);
|
||||
var original = _db.Logs.Find(log.LogId);
|
||||
|
||||
// ignore these properties while updating
|
||||
log.Created = original.Created;
|
||||
log.Source = original.Source;
|
||||
log.UserAgent = original.UserAgent;
|
||||
log.UserHostAddress = original.UserHostAddress;
|
||||
// note: assumes log is already attached to current context
|
||||
|
||||
// remove from the list
|
||||
var nextLog =
|
||||
@@ -58,7 +55,11 @@ namespace MileageTraker.Web.DAL
|
||||
where l.VehiclePreviousLogId == log.LogId
|
||||
select l).FirstOrDefault();
|
||||
if (nextLog != null)
|
||||
{
|
||||
nextLog.VehiclePreviousLogId = log.VehiclePreviousLogId;
|
||||
nextLog.User = _db.Users.Find(nextLog.User.UserId);
|
||||
_db.Entry(nextLog).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
log.VehiclePreviousLog = null;
|
||||
log.VehiclePreviousLogId = null;
|
||||
@@ -69,8 +70,13 @@ namespace MileageTraker.Web.DAL
|
||||
log.VehiclePreviousLogId = previousLog.LogId;
|
||||
var newNextLog = SearchNextLog(log);
|
||||
if (newNextLog != null)
|
||||
{
|
||||
newNextLog.VehiclePreviousLogId = log.LogId;
|
||||
newNextLog.User = _db.Users.Find(newNextLog.User.UserId);
|
||||
_db.Entry(newNextLog).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
log.User = _db.Users.Find(log.User.UserId);
|
||||
_db.Entry(log).State = EntityState.Modified;
|
||||
_db.SaveChanges();
|
||||
|
||||
@@ -83,7 +89,11 @@ namespace MileageTraker.Web.DAL
|
||||
|
||||
var nextLog = GetNextLog(log.LogId);
|
||||
if (nextLog != null)
|
||||
{
|
||||
nextLog.VehiclePreviousLog = log.VehiclePreviousLog;
|
||||
nextLog.User = _db.Users.Find(nextLog.User.UserId);
|
||||
_db.Entry(nextLog).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
_db.Logs.Remove(log);
|
||||
_db.SaveChanges();
|
||||
@@ -247,11 +257,11 @@ namespace MileageTraker.Web.DAL
|
||||
return logs;
|
||||
}
|
||||
|
||||
public IEnumerable<Log> GetRecentLogsByEmployee(string employeeName)
|
||||
public IEnumerable<Log> GetRecentLogsByUsername(string username)
|
||||
{
|
||||
return
|
||||
(from l in _db.Logs
|
||||
where l.User.FullName == employeeName
|
||||
where l.User.Username == username
|
||||
orderby l.Created descending
|
||||
select l)
|
||||
.Take(3)
|
||||
|
||||
@@ -18,11 +18,6 @@ namespace MileageTraker.Web.DAL
|
||||
ExcelWriter.WriteXls(vehicles, filename, WorksheetTitle, WorksheetName);
|
||||
}
|
||||
|
||||
public static void Export(IEnumerable<Vehicle> vehicles, Stream stream)
|
||||
{
|
||||
ExcelWriter.WriteXls(vehicles, stream, WorksheetTitle, WorksheetName);
|
||||
}
|
||||
|
||||
public static byte[] Export(IEnumerable<Vehicle> vehicles)
|
||||
{
|
||||
return ExcelWriter.WriteXls(vehicles, WorksheetTitle, WorksheetName);
|
||||
|
||||
@@ -12,11 +12,13 @@ namespace MileageTraker.Web.Email
|
||||
{
|
||||
//private static readonly ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private readonly string _resetPasswordBody;
|
||||
private readonly string _resetPasswordFromAddress;
|
||||
private readonly string _emaialFromAddress;
|
||||
private readonly string _resetPasswordSubject;
|
||||
private readonly string _resetPasswordBody;
|
||||
|
||||
private readonly SmtpClient _smtpClient;
|
||||
private string _initializePasswordSubject;
|
||||
private string _initializePasswordBody;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
|
||||
@@ -26,9 +28,13 @@ namespace MileageTraker.Web.Email
|
||||
{
|
||||
_smtpClient = new SmtpClient();
|
||||
|
||||
_resetPasswordBody = ConfigurationManager.AppSettings["ResetPasswordBody"];
|
||||
_resetPasswordFromAddress = ConfigurationManager.AppSettings["ResetPasswordFromAddress"];
|
||||
_emaialFromAddress = ConfigurationManager.AppSettings["EmailFromAddress"];
|
||||
|
||||
_resetPasswordSubject = ConfigurationManager.AppSettings["ResetPasswordSubject"];
|
||||
_resetPasswordBody = ConfigurationManager.AppSettings["ResetPasswordBody"];
|
||||
|
||||
_initializePasswordSubject = ConfigurationManager.AppSettings["InitializePasswordSubject"];
|
||||
_initializePasswordBody = ConfigurationManager.AppSettings["InitializetPasswordBody"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,10 +42,16 @@ namespace MileageTraker.Web.Email
|
||||
/// </summary>
|
||||
/// <param name="user">To this user.</param>
|
||||
/// <param name="url">Reset url</param>
|
||||
public void NotifyResetPassword(User user, string url)
|
||||
public void SendResetPassword(User user, string url)
|
||||
{
|
||||
var body = string.Format(_resetPasswordBody, url);
|
||||
_smtpClient.Send(new MailMessage(_resetPasswordFromAddress, user.Email, _resetPasswordSubject, body));
|
||||
_smtpClient.Send(new MailMessage(_emaialFromAddress, user.Email, _resetPasswordSubject, body));
|
||||
}
|
||||
|
||||
public void SendInitializePassword(User user, string url)
|
||||
{
|
||||
var body = string.Format(_initializePasswordBody, url, user.FullName);
|
||||
_smtpClient.Send(new MailMessage(_emaialFromAddress, user.Email, _initializePasswordSubject, body));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace MileageTraker.Web.Migrations
|
||||
,'{1}'
|
||||
,'{2}'
|
||||
,'{3}'
|
||||
,''
|
||||
,'uninitialized_state'
|
||||
,NULL
|
||||
,{4}
|
||||
,0
|
||||
|
||||
@@ -6,11 +6,15 @@ namespace MileageTraker.Web.Migrations
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
CreateIndex("User", "FullName", unique: true);
|
||||
DropIndex("User", "IX_Email");
|
||||
DropColumn("Log", "EmployeeName");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
DropIndex("User", "IX_FullName");
|
||||
CreateIndex("User", "Email", unique: true);
|
||||
AddColumn("Log", "EmployeeName", c => c.String(nullable: false));
|
||||
Sql(@"UPDATE [Log]
|
||||
SET [Log].EmployeeName = [User].FullName
|
||||
|
||||
+3
-30
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web.Mvc;
|
||||
using MileageTraker.Web.Attributes;
|
||||
using MileageTraker.Web.DAL;
|
||||
|
||||
namespace MileageTraker.Web.Models
|
||||
@@ -10,58 +9,33 @@ namespace MileageTraker.Web.Models
|
||||
public class Log : IValidatableObject
|
||||
{
|
||||
[Key]
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public int LogId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Remote("Exists", "Vehicle", ErrorMessage = "ID not found")]
|
||||
[StringLength(6, MinimumLength = 4, ErrorMessage = "Enter 4 digit number")]
|
||||
[Display(Name = "Vehicle ID")]
|
||||
[InputSize("mini")]
|
||||
[RegularExpression(@"\d+", ErrorMessage = "Enter only numbers")]
|
||||
public string VehicleId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Range(1, 500000, ErrorMessage = "Between 1 and 500k")]
|
||||
[Display(Name = "End Odometer")]
|
||||
[InputSize("small")]
|
||||
[Units("Miles")]
|
||||
public int EndOdometer { get; set; }
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Type")]
|
||||
[NoEditLabel]
|
||||
public MileageLogTypeWrapper LogType { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(64, MinimumLength = 3, ErrorMessage = "Minimum 3 characters")]
|
||||
[InputSize("medium")]
|
||||
public string CityName { get; set; }
|
||||
|
||||
//[Required]
|
||||
//[Display(Name = "Employee / Driver")]
|
||||
//[RegularExpression(@"[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Need complete name")]
|
||||
//[InputSize("medium")]
|
||||
//public string EmployeeName { get; set; }
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Employee / Driver")]
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public virtual User User { get; set; }
|
||||
|
||||
[Range(0, 50)]
|
||||
[DisplayFormat(DataFormatString = "{0:0.000}", ApplyFormatInEditMode = true)]
|
||||
[Units("Gallons")]
|
||||
[FormatHint("n.nnnn")]
|
||||
[InputSize("mini")]
|
||||
public double GasPurchased { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Date)]
|
||||
[DisplayFormat(DataFormatString = @"{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
|
||||
[DenyFutureDate(ErrorMessage = "Future date")]
|
||||
[FormatHint("mm/dd/yyyy")]
|
||||
[InputSize("small")]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[Display(Name = "Time Created")]
|
||||
@@ -72,13 +46,10 @@ namespace MileageTraker.Web.Models
|
||||
/// <summary>
|
||||
/// url route that was used to create this instance, ie 'CreateLog\Confirm' or 'Logs\Create'
|
||||
/// </summary>
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public string Source { get; set; }
|
||||
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public string UserHostAddress { get; set; }
|
||||
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public string UserAgent { get; set; }
|
||||
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
@@ -103,9 +74,11 @@ namespace MileageTraker.Web.Models
|
||||
ValidationResult result = null;
|
||||
try
|
||||
{
|
||||
var dataService = new DataService();
|
||||
using (var dataService = new DataService())
|
||||
{
|
||||
dataService.ValidateOdometerChronology(VehicleId, EndOdometer, Date);
|
||||
}
|
||||
}
|
||||
catch (ChronologicalOrderException ex)
|
||||
{
|
||||
result = new ValidationResult(ex.Message, new[] { "EndOdometer" });
|
||||
|
||||
+33
-68
@@ -1,24 +1,16 @@
|
||||
$(function () {
|
||||
$("tbody > tr:odd").addClass("odd");
|
||||
$("input:submit, .ui-button").button();
|
||||
$("input#Date").datepicker({ maxDate: '+0d' });
|
||||
$(".display-field-container")
|
||||
.filter(".source,.userHostAddress,.userAgent")
|
||||
.children(".display-label")
|
||||
.css('text-decoration', 'underline')
|
||||
.click(function () {
|
||||
$(this).parent().children('.display-field').toggle();
|
||||
})
|
||||
.end()
|
||||
.children('.display-field').hide();
|
||||
|
||||
$("input#CityName").autocomplete({
|
||||
source: "/City/Autocomplete",
|
||||
minLength: 2
|
||||
});
|
||||
$("input#EmployeeName, input#Assigned").autocomplete({
|
||||
|
||||
$("input#EmployeeName, input#Assigned, input#UserFullName").autocomplete({
|
||||
source: "/Employee/Autocomplete",
|
||||
minLength: 2
|
||||
});
|
||||
|
||||
$("form select#Year").change(function () {
|
||||
$.getJSON('/Log/GetValidLogMonths', { year: $(this).val() }, function (months) {
|
||||
var options = '<option>Select Month</option>';
|
||||
@@ -28,7 +20,9 @@ $(function () {
|
||||
$("form select#Month").html(options);
|
||||
});
|
||||
});
|
||||
|
||||
$("input#ModelYear,input#Price,input#VehicleId,input#EndOdometer,input#GasPurchased").numeric();
|
||||
|
||||
$(".report-miles").append(' <span class="muted">▴</span>').each(function () {
|
||||
var content = $(this).next('.report-calculation');
|
||||
$(this).qtip({
|
||||
@@ -57,58 +51,16 @@ $(function () {
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if ($("input#EmployeeName").filter(':not(:hidden)').filter(':not(.search-query)').length > 0) {
|
||||
|
||||
$("input#EmployeeName")
|
||||
.after('<span id="icon-employee-history" class="add-on"><i class="icon-search" /></span>')
|
||||
.add('#icon-employee-history')
|
||||
.wrapAll('<div class="input-append"></div>');
|
||||
|
||||
function employeeHistoryKeydownHandler(data) {
|
||||
employeeHistoryIconUpdate($(data.target), $("#icon-employee-history"));
|
||||
}
|
||||
|
||||
function employeeHistoryIconUpdate($input, $icon) {
|
||||
if (!$input.hasClass("input-validation-error") // not invalid
|
||||
&& $input.attr("value").length > 0) // has text
|
||||
$icon.removeClass("transparent");
|
||||
else
|
||||
$icon.addClass("transparent");
|
||||
}
|
||||
|
||||
$("input#EmployeeName").change(employeeHistoryKeydownHandler).keydown(employeeHistoryKeydownHandler);
|
||||
|
||||
employeeHistoryIconUpdate($("input#EmployeeName"), $("#icon-employee-history"));
|
||||
|
||||
$("#icon-employee-history").qtip({
|
||||
content: {
|
||||
text: "<p class=\"loading\">Recent Logs...</p>",
|
||||
},
|
||||
style: {
|
||||
width: 310,
|
||||
classes: 'qtip-light'
|
||||
},
|
||||
position: {
|
||||
my: "top right",
|
||||
at: "bottom right"
|
||||
},
|
||||
hide: {
|
||||
fixed: true,
|
||||
delay: 500
|
||||
},
|
||||
events: {
|
||||
show: function(event, api) {
|
||||
// add recent logs to a div if it exists
|
||||
$(function () {
|
||||
var $recentLogs = $("#RecentLogs");
|
||||
if ($recentLogs.length > 0) {
|
||||
$.ajax({
|
||||
url: "/CreateLog/RecentLogs",
|
||||
data: {
|
||||
employeeName: $("input#EmployeeName").val()
|
||||
},
|
||||
success: function(data) {
|
||||
api.set("content.text", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
success: function (data) {
|
||||
$recentLogs.append(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -119,16 +71,18 @@ $(function() {
|
||||
$('label[for]').each(function() {
|
||||
var $label = $(this);
|
||||
var $for = $label.attr('for');
|
||||
var $input = $('input#' + $for + ':checkbox');
|
||||
if ($input.length > 0) {
|
||||
if ($for.length > 0) {
|
||||
var $input = $('input#' + $for + ',input[name="' + $for + '"]');
|
||||
if ($input.filter(':checkbox').length > 0) {
|
||||
$input.prependTo($label);
|
||||
$label.addClass('checkbox');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add active class to nav
|
||||
$(function() {
|
||||
// Add active class to nav
|
||||
var idNavActiveRegex = {
|
||||
'log-nav': /\/log/i,
|
||||
'vehicle-nav': /\/vehicle/i,
|
||||
@@ -161,8 +115,8 @@ $(function () {
|
||||
.prepend('<i class="icon-user icon-white" /> ');
|
||||
});
|
||||
|
||||
// Convert MVC3 WebGrid paging to Bootstrap
|
||||
$(function() {
|
||||
// Convert MVC3 WebGrid paging to Bootstrap
|
||||
var $paging = $('table.table tfoot tr td');
|
||||
|
||||
var $currentPage =
|
||||
@@ -177,9 +131,20 @@ $(function() {
|
||||
}
|
||||
});
|
||||
|
||||
// auto complete email address based on username
|
||||
$(function () {
|
||||
$('input#Username').keyup(function (e) {
|
||||
$('input#Email').val($(this).val() + "@ethra.org");
|
||||
$('.create-user input#FullName').keyup(function () {
|
||||
var fullName = $(this).val();
|
||||
var names = fullName.split(' '); ///[a-z().]+(\s+[a-z().]+)+/i.test(fullName)
|
||||
if (names.length > 1) {
|
||||
var username = names[0][0] + names[names.length - 1];
|
||||
$('.create-user input#Username').val(username);
|
||||
$('.create-user input#Email').val(username + "@ethra.org");
|
||||
}
|
||||
});
|
||||
|
||||
$('.create-user input#Username').keyup(function () {
|
||||
$('.create-user input#Email').val($(this).val() + "@ethra.org");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -198,7 +163,7 @@ $(function() {
|
||||
ajax: {
|
||||
url: element.attr('href')
|
||||
},
|
||||
text: "<p class=\"loading\">Recent Logs...</p>"
|
||||
text: "<p class=\"loading\">...</p>"
|
||||
},
|
||||
hide: {
|
||||
fixed: true,
|
||||
|
||||
@@ -48,10 +48,13 @@ namespace MileageTraker.Web.Utility
|
||||
// write the data
|
||||
items.Zip(
|
||||
CustomExtensions.GetNumbers().Skip(3),
|
||||
(vehicle, r) =>
|
||||
(item, r) =>
|
||||
properties.Zip(CustomExtensions.GetNumbers(),
|
||||
(p, c) => {
|
||||
var value = p.GetValue(vehicle);
|
||||
// TODO: Fix this
|
||||
if (item is User)
|
||||
return (string)null;
|
||||
var value = p.GetValue(item);
|
||||
|
||||
string formatString = null;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace MileageTraker.Web.ViewModels.Account
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[StringLength(16, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MileageTraker.Web.ViewModels
|
||||
{
|
||||
public class CheckBoxViewModel
|
||||
{
|
||||
public IList<string> Available { get; set; }
|
||||
public string[] Selected { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ namespace MileageTraker.Web.ViewModels.CreateLog
|
||||
|
||||
DateHowLongAgo = (DateTime.Today - Date).ToVerboseStringHistoric();
|
||||
|
||||
var dataService = new DataService();
|
||||
|
||||
using (var dataService = new DataService())
|
||||
{
|
||||
var endOdometer = int.Parse(createLogViewModel.EndOdometer);
|
||||
var date = createLogViewModel.Date;
|
||||
|
||||
@@ -51,4 +51,5 @@ namespace MileageTraker.Web.ViewModels.CreateLog
|
||||
VehicleTagNumber = vehicle.TagNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,17 +62,6 @@ namespace MileageTraker.Web.ViewModels.CreateLog
|
||||
Mapper.CreateMap<string, double>().ConvertUsing(Convert.ToDouble);
|
||||
Mapper.CreateMap<string, DateTime>().ConvertUsing(new DateTimeTypeConverter());
|
||||
Mapper.CreateMap<CreateLogViewModel, Models.Log>();
|
||||
Mapper.CreateMap<Models.Log, CreateLogViewModel>()
|
||||
.ForMember(vm => vm.Date, opt => opt.MapFrom(m => m.Date.ToString("d")));
|
||||
}
|
||||
|
||||
public CreateLogViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public CreateLogViewModel(Models.Log log)
|
||||
{
|
||||
Mapper.Map(log, this);
|
||||
}
|
||||
|
||||
public Models.Log GetLog()
|
||||
@@ -94,9 +83,11 @@ namespace MileageTraker.Web.ViewModels.CreateLog
|
||||
ValidationResult result = null;
|
||||
try
|
||||
{
|
||||
var dataService = new DataService();
|
||||
using (var dataService = new DataService())
|
||||
{
|
||||
dataService.ValidateOdometerChronology(VehicleId, int.Parse(EndOdometer), Date);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = new ValidationResult(ex.Message, new [] {"EndOdometer"});
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web.Mvc;
|
||||
using AutoMapper;
|
||||
using MileageTraker.Web.Attributes;
|
||||
using MileageTraker.Web.DAL;
|
||||
using MileageTraker.Web.Models;
|
||||
using MileageTraker.Web.Utility;
|
||||
|
||||
namespace MileageTraker.Web.ViewModels.Log
|
||||
{
|
||||
public class LogViewModel : IValidatableObject
|
||||
{
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public int LogId { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Required")]
|
||||
[Remote("Exists", "Vehicle", ErrorMessage = "ID not found")]
|
||||
[StringLength(6, MinimumLength = 4, ErrorMessage = "Must be a 4 digit number")]
|
||||
[Display(Name = "Vehicle ID")]
|
||||
[InputSize("mini")]
|
||||
[RegularExpression(@"\d+", ErrorMessage = "Must be all numbers")]
|
||||
public string VehicleId { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Required")]
|
||||
[Range(1, 500000, ErrorMessage = "Between 1 and 500k")]
|
||||
[Display(Name = "End Odometer")]
|
||||
[Units("Miles")]
|
||||
[InputSize("small")]
|
||||
[RegularExpression(@"\d+", ErrorMessage = "Must be all numbers")]
|
||||
public string EndOdometer { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Required")]
|
||||
[Display(Name = "Type")]
|
||||
[NoEditLabel]
|
||||
public MileageLogTypeWrapper LogType { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Required")]
|
||||
[Remote("ExistsByFullName", "User", ErrorMessage = "User with this name doesn't exist")]
|
||||
[StringLength(128)]
|
||||
[Display(Name = "Driver Name")]
|
||||
[InputSize("medium")]
|
||||
public string UserFullName { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Required")]
|
||||
[Display(Name = "City Name")]
|
||||
[InputSize("medium")]
|
||||
[StringLength(64, MinimumLength = 3, ErrorMessage = "Minimum 3 characters")]
|
||||
public string CityName { get; set; }
|
||||
|
||||
[Display(Name = "Gas Purchased")]
|
||||
[DisplayFormat(DataFormatString = "{0:0.000}", ApplyFormatInEditMode = true)]
|
||||
[Units("Gallons")]
|
||||
[FormatHint("n.nnnn")]
|
||||
[InputSize("mini")]
|
||||
public string GasPurchased { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Required")]
|
||||
[DataType(DataType.Date)]
|
||||
[DisplayFormat(DataFormatString = @"{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
|
||||
[DenyFutureDate(ErrorMessage = "Future date")]
|
||||
[FormatHint("mm/dd/yyyy")]
|
||||
[InputSize("small")]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[Display(Name = "Time Created")]
|
||||
[DisplayFormat(DataFormatString = @"{0:MM/dd/yyyy h:mm tt}")]
|
||||
[HiddenInput]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
static LogViewModel()
|
||||
{
|
||||
Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32);
|
||||
Mapper.CreateMap<string, double>().ConvertUsing(Convert.ToDouble);
|
||||
Mapper.CreateMap<string, DateTime>().ConvertUsing(new DateTimeTypeConverter());
|
||||
Mapper.CreateMap<LogViewModel, Models.Log>();
|
||||
Mapper.CreateMap<Models.Log, LogViewModel>()
|
||||
.ForMember(vm => vm.Date, opt => opt.MapFrom(m => m.Date.ToString("d")));
|
||||
}
|
||||
|
||||
public LogViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public LogViewModel(Models.Log log)
|
||||
{
|
||||
Mapper.Map(log, this);
|
||||
}
|
||||
|
||||
public Models.Log GetLog()
|
||||
{
|
||||
var log = new Models.Log();
|
||||
Mapper.Map(this, log);
|
||||
return log;
|
||||
}
|
||||
|
||||
public void UpdateLog(Models.Log log)
|
||||
{
|
||||
Mapper.DynamicMap(this, log);
|
||||
//TODO use automapper somehow
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (LogType == MileageLogType.GasPurchase
|
||||
&& (string.IsNullOrEmpty(GasPurchased) || double.Parse(GasPurchased) == 0))
|
||||
yield return new ValidationResult("Enter amount of gas purchased", new [] {"GasPurchased"});
|
||||
|
||||
if (LogType.Value == 0)
|
||||
yield return new ValidationResult("Required", new[] { "LogType" });
|
||||
|
||||
ValidationResult chronologyResult = null;
|
||||
try
|
||||
{
|
||||
using (var dataService = new DataService())
|
||||
{
|
||||
dataService.ValidateOdometerChronology(VehicleId, int.Parse(EndOdometer), Date);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
chronologyResult = new ValidationResult(ex.Message, new [] {"EndOdometer"});
|
||||
}
|
||||
if (chronologyResult != null)
|
||||
yield return chronologyResult;
|
||||
|
||||
using (var dataService = new DataService())
|
||||
{
|
||||
var user = dataService.FindUserByFullName(UserFullName);
|
||||
if (user == null)
|
||||
{
|
||||
yield return new ValidationResult("User with this name doesn't exist", new[] { "UserFullName" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,15 @@ using MileageTraker.Web.Attributes;
|
||||
|
||||
namespace MileageTraker.Web.ViewModels.User
|
||||
{
|
||||
public class CreateUserViewModel : RolesViewModel
|
||||
public class CreateUserViewModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
[RegularExpression(@"[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Need complete name")]
|
||||
[InputSize("medium")]
|
||||
[Remote("FullNameAvailable", "User", ErrorMessage = "Name already in use")]
|
||||
public string FullName { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
[InputSize("small")]
|
||||
@@ -17,19 +24,14 @@ namespace MileageTraker.Web.ViewModels.User
|
||||
[DataType(DataType.EmailAddress)]
|
||||
[RegularExpression(@"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}", ErrorMessage = "Must be an email address")]
|
||||
[InputSize("large")]
|
||||
[Remote("EmailAvailable", "User", ErrorMessage = "Email already in use")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
[RegularExpression(@"[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Need complete name")]
|
||||
[InputSize("medium")]
|
||||
public string FullName { get; set; }
|
||||
public CheckBoxViewModel Roles { get; set; }
|
||||
|
||||
[Required, DataType(DataType.Password)]
|
||||
[StringLength(16, MinimumLength = 6)]
|
||||
[InputSize("medium")]
|
||||
public string Password { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Set password now instead of emailing ")]
|
||||
[NoEditLabel]
|
||||
public bool SetPassword { get; set; }
|
||||
|
||||
static CreateUserViewModel()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web.Mvc;
|
||||
using AutoMapper;
|
||||
@@ -7,11 +6,17 @@ using MileageTraker.Web.Attributes;
|
||||
|
||||
namespace MileageTraker.Web.ViewModels.User
|
||||
{
|
||||
public class EditUserViewModel : RolesViewModel
|
||||
public class EditUserViewModel
|
||||
{
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
[RegularExpression(@"[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Need complete name")]
|
||||
[InputSize("medium")]
|
||||
public string FullName { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
[InputSize("small")]
|
||||
@@ -23,11 +28,7 @@ namespace MileageTraker.Web.ViewModels.User
|
||||
[InputSize("large")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
[RegularExpression(@"[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Need complete name")]
|
||||
[InputSize("medium")]
|
||||
public string FullName { get; set; }
|
||||
public CheckBoxViewModel Roles { get; set; }
|
||||
|
||||
static EditUserViewModel()
|
||||
{
|
||||
@@ -46,9 +47,9 @@ namespace MileageTraker.Web.ViewModels.User
|
||||
|
||||
public void UpdateUser(Models.User user)
|
||||
{
|
||||
user.Username = this.Username;
|
||||
user.FullName = this.FullName;
|
||||
user.Email = this.Email;
|
||||
user.Username = Username;
|
||||
user.FullName = FullName;
|
||||
user.Email = Email;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MileageTraker.Web.ViewModels.User
|
||||
{
|
||||
public class RolesViewModel
|
||||
{
|
||||
public string[] Roles { get; set; }
|
||||
public IList<string> AvailableRoles { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,8 @@
|
||||
@Html.HiddenFor(model => model.LogType.Value)
|
||||
|
||||
@Html.DisplayFor(model => model.CityName)
|
||||
@Html.HiddenFor(model => model.CityName)
|
||||
@* // Html.HiddenFor(model => model.CityName) is losing capitalization *@
|
||||
<input type="hidden" name="CityName" value="@Model.CityName"/>
|
||||
|
||||
<dl class="dl-horizontal gas">
|
||||
<dt>
|
||||
@@ -84,7 +85,6 @@
|
||||
</dl>
|
||||
@Html.HiddenFor(model => model.GasPurchased)
|
||||
|
||||
|
||||
<dl class="dl-horizontal date">
|
||||
<dt>
|
||||
@Html.DisplayNameFor(m => m.Date)
|
||||
|
||||
@@ -19,3 +19,7 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
|
||||
<div id="RecentLogs" class="center-content well">
|
||||
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<h5>Recent Logs for @ViewData["employeeName"]</h5>
|
||||
<h5>Recent Logs for @ViewData["name"]</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -43,12 +43,5 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ViewData["employeeName"].ToString()))
|
||||
{
|
||||
<h5>Mileage history not found for employee "@ViewData["employeeName"]"</h5>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h5>Please enter employee name</h5>
|
||||
}
|
||||
<p>Mileage history not found for <strong>@ViewData["name"]</strong></p>
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@model MileageTraker.Web.Models.Log
|
||||
@model MileageTraker.Web.ViewModels.Log.LogViewModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Create Log";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@model MileageTraker.Web.Models.Log
|
||||
@model MileageTraker.Web.ViewModels.Log.LogViewModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Delete Log";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@model MileageTraker.Web.Models.Log
|
||||
@model MileageTraker.Web.ViewModels.Log.LogViewModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Log Details";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@model MileageTraker.Web.Models.Log
|
||||
@model MileageTraker.Web.ViewModels.Log.LogViewModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Edit Log";
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
grid.Column("GasPurchased", "Gas", item => item.GasPurchased > 0 ? String.Format("{0:0.000}", item.GasPurchased) : string.Empty),
|
||||
grid.Column("Date", format: item => item.Date.ToString("d")),
|
||||
grid.Column("CityName", "City Name"),
|
||||
grid.Column("EmployeeName", "Employee Name", item => item.User != null ? item.User.FullName : ""),
|
||||
grid.Column("Driver Name", format: item => item.User != null ? item.User.FullName : ""),
|
||||
grid.Column(format:
|
||||
@<div class='btn-group'>
|
||||
@Html.ActionLink("Edit", "Edit", new { id = item.LogId }, new { @class = "btn btn-mini" })
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
@model MileageTraker.Web.ViewModels.CheckBoxViewModel
|
||||
@{
|
||||
Layout = "~/Views/Shared/EditorTemplates/_FieldLayout.cshtml";
|
||||
ViewData.ModelMetadata.DisplayName = "Roles";
|
||||
}
|
||||
@Html.CheckBoxListFor(
|
||||
m => m.Selected,
|
||||
m => m.Available,
|
||||
m => m,
|
||||
m => m,
|
||||
m => m.Selected)
|
||||
@@ -11,9 +11,5 @@
|
||||
{
|
||||
@Html.Editor(prop.PropertyName)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,16 @@
|
||||
<span class="brand"></span>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
@if (Roles.IsUserInRole(User.Identity.Name, "Administrator")
|
||||
|| Roles.IsUserInRole(User.Identity.Name, "Developer"))
|
||||
@if (User.IsInRole("Driver") || User.IsInRole("Developer") || User.IsInRole("Administrator"))
|
||||
{
|
||||
<li>@Html.ActionLink("Enter Log", "Index", "CreateLog")</li>
|
||||
}
|
||||
@if (User.IsInRole("Developer") || User.IsInRole("Administrator"))
|
||||
{
|
||||
<li id="log-nav">@Html.ActionLink("Logs", "Index", "Log")</li>
|
||||
<li id="vehicle-nav">@Html.ActionLink("Vehicles", "Index", "Vehicle")</li>
|
||||
<li id="user-nav">@Html.ActionLink("Users", "Index", "User")</li>
|
||||
}
|
||||
<li>@Html.ActionLink("Enter Log", "Index", "CreateLog")</li>
|
||||
</ul>
|
||||
<section id="account-management">
|
||||
@Html.Partial("_NavAccountInfo")
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
|
||||
<h2 class="center-content">@ViewBag.Title</h2>
|
||||
|
||||
@using (Html.BeginForm("Create", "User", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
|
||||
@using (Html.BeginForm("Create", "User", FormMethod.Post, new { @class = "form-horizontal well center-content create-user" }))
|
||||
{
|
||||
@Html.Partial("_ValidationSummary")
|
||||
<fieldset>
|
||||
<legend></legend>
|
||||
@Html.EditorForModel()
|
||||
@Html.Partial("_Roles", Model)
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Create" class="btn btn-primary" />
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
<div class="center-content well">
|
||||
|
||||
@Html.DisplayFor(m => m.FullName)
|
||||
|
||||
<dl class="dl-horizontal username">
|
||||
<dt>
|
||||
@Html.DisplayNameFor(m => m.Username)
|
||||
@@ -25,8 +27,6 @@
|
||||
|
||||
@Html.DisplayFor(m => m.Email)
|
||||
|
||||
@Html.DisplayFor(m => m.FullName)
|
||||
|
||||
<dl class="dl-horizontal roles">
|
||||
<dt>
|
||||
@Html.DisplayNameFor(m => m.Roles)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<legend></legend>
|
||||
|
||||
@Html.EditorForModel()
|
||||
@Html.Partial("_Roles", Model)
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Save" class="btn btn-primary" />
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
@grid.GetHtml(columns:
|
||||
grid.Columns(
|
||||
grid.Column("FullName", "Full Name"),
|
||||
grid.Column("Username", format:
|
||||
@<text>
|
||||
<span title="@Html.Encode(item.Email)">
|
||||
@@ -37,7 +38,6 @@
|
||||
LastLockoutDate = item.LastLockoutDate,
|
||||
IsApproved = item.IsApproved})
|
||||
</text> ),
|
||||
grid.Column("FullName", "Full Name"),
|
||||
grid.Column("Roles", format:
|
||||
@<text>
|
||||
@{var roles = Roles.Provider.GetRolesForUser(item.Username);}
|
||||
@@ -50,7 +50,7 @@
|
||||
<span class='label label-warning'>No Role</span>
|
||||
}
|
||||
</text>),
|
||||
grid.Column("LastActivityDate", "Last Activity", format:
|
||||
grid.Column("LastActivityDate", "Last Activity",
|
||||
@<text>
|
||||
@Html.Partial("_LastActivity", (DateTime)item.LastActivityDate)
|
||||
</text>),
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
@model MileageTraker.Web.ViewModels.User.RolesViewModel
|
||||
@{
|
||||
Layout = "~/Views/Shared/EditorTemplates/_FieldLayout.cshtml";
|
||||
}
|
||||
@Html.CheckBoxListFor(
|
||||
m => m.Roles,
|
||||
m => m.AvailableRoles,
|
||||
m => m,
|
||||
m => m,
|
||||
m => m.Roles)
|
||||
+4
-2
@@ -13,9 +13,11 @@
|
||||
<add key="ClientValidationEnabled" value="true" />
|
||||
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
|
||||
<add key="enableSimpleMembership" value="false" />
|
||||
<add key="ResetPasswordBody" value="Please open this link to set a new password: {0}" />
|
||||
<add key="ResetPasswordFromAddress" value="Mileage Traker <noreply@ethra.org>" />
|
||||
<add key="EmailFromAddress" value="Mileage Traker <noreply@ethra.org>" />
|
||||
<add key="ResetPasswordSubject" value="New Password Request" />
|
||||
<add key="ResetPasswordBody" value="Please open this link to set a new password: {0}" />
|
||||
<add key="InitializePasswordSubject" value="Initialize Mileage Traker Account" />
|
||||
<add key="InitializetPasswordBody" value="Hello {1}, please open this link to set up your password on Mileage Traker: {0}" />
|
||||
</appSettings>
|
||||
<system.net>
|
||||
<mailSettings>
|
||||
|
||||
+3
-2
@@ -161,14 +161,15 @@
|
||||
<Compile Include="ViewModels\Account\LoginViewModel.cs" />
|
||||
<Compile Include="ViewModels\Account\NewPasswordViewModel.cs" />
|
||||
<Compile Include="ViewModels\Account\RegisterModel.cs" />
|
||||
<Compile Include="ViewModels\CheckBoxViewModel.cs" />
|
||||
<Compile Include="ViewModels\EmployeeMileageItem.cs" />
|
||||
<Compile Include="ViewModels\EmployeeMileageViewModel.cs" />
|
||||
<Compile Include="ViewModels\Log\LogViewModel.cs" />
|
||||
<Compile Include="ViewModels\Log\LogIndexViewModel.cs" />
|
||||
<Compile Include="ViewModels\Log\LogPartialDetails.cs" />
|
||||
<Compile Include="ViewModels\User\SetPasswordViewModel.cs" />
|
||||
<Compile Include="ViewModels\User\CreateUserViewModel.cs" />
|
||||
<Compile Include="ViewModels\User\EditUserViewModel.cs" />
|
||||
<Compile Include="ViewModels\User\RolesViewModel.cs" />
|
||||
<Compile Include="ViewModels\Vehicle\VehicleMileageViewModel.cs" />
|
||||
<Compile Include="ViewModels\Vehicle\VehiclePartialDetails.cs" />
|
||||
<Compile Include="ViewModels\Vehicle\VehicleMileageItem.cs" />
|
||||
@@ -295,13 +296,13 @@
|
||||
<Content Include="Views\Shared\EditorTemplates\EmailAddress.cshtml" />
|
||||
<Content Include="Views\Shared\DisplayTemplates\EmailAddress.cshtml" />
|
||||
<Content Include="Views\Shared\DisplayTemplates\Boolean.cshtml" />
|
||||
<Content Include="Views\User\_Roles.cshtml" />
|
||||
<Content Include="Views\User\SetPassword.cshtml" />
|
||||
<Content Include="Views\Shared\_StatusMessage.cshtml" />
|
||||
<Content Include="Views\User\_UserStatusLabels.cshtml" />
|
||||
<Content Include="Views\Account\ResetPassword.cshtml" />
|
||||
<Content Include="Views\Account\NewPassword.cshtml" />
|
||||
<Content Include="Views\User\_LastActivity.cshtml" />
|
||||
<Content Include="Views\Shared\EditorTemplates\CheckBoxViewModel.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config">
|
||||
|
||||
Reference in New Issue
Block a user