Merge branch 'Release0.2'

This commit is contained in:
chris.watts90@outlook.com 2020-02-25 15:14:36 +00:00
commit fe5bcd3256
92 changed files with 23644 additions and 22182 deletions

View File

@ -0,0 +1,5 @@
[program:CardReaderService]
command=mono-service CardReaderService.exe --no-daemon
directory=/DIRECTORYTOSET/CardReaderService
stdout_logfile=/DIRECTORYTOSET/CardReaderService/out.log
redirect_stderr=true

View File

@ -3,9 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25420.1 VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CardReaderService", "CardReaderService.csproj", "{5F30E8E4-5107-4C99-ADFF-38D735DC113D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CardReaderService", "CardReaderService\CardReaderService.csproj", "{5F30E8E4-5107-4C99-ADFF-38D735DC113D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CardReaderServiceHost", "..\CardReaderServiceHost\CardReaderServiceHost.csproj", "{6E48913F-9D8C-4132-93A7-C7B1C6DD5264}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CardReaderServiceHost", "CardReaderServiceHost\CardReaderServiceHost.csproj", "{6E48913F-9D8C-4132-93A7-C7B1C6DD5264}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logger", "Logger\Logger.csproj", "{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux Conf", "Linux Conf", "{F9053F37-761D-45EE-9153-46776B083DBA}"
ProjectSection(SolutionItems) = preProject
CardReaderService.conf = CardReaderService.conf
install.sh = install.sh
uninstall.sh = uninstall.sh
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -21,6 +30,10 @@ Global
{6E48913F-9D8C-4132-93A7-C7B1C6DD5264}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E48913F-9D8C-4132-93A7-C7B1C6DD5264}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E48913F-9D8C-4132-93A7-C7B1C6DD5264}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E48913F-9D8C-4132-93A7-C7B1C6DD5264}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E48913F-9D8C-4132-93A7-C7B1C6DD5264}.Release|Any CPU.Build.0 = Release|Any CPU {6E48913F-9D8C-4132-93A7-C7B1C6DD5264}.Release|Any CPU.Build.0 = Release|Any CPU
{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup> </startup>
<appSettings> <appSettings>
<add key="DataCenterServiceEndpoint" value="http://localhost:1234"/> <add key="DataCenterServiceEndpoint" value="http://localhost:1234"/>

View File

@ -0,0 +1,10 @@
using System;
namespace CardReaderService
{
public class CardDataPost
{
public DateTime UtcTimeStamp { get; set; }
public string CardUId { get; set; }
}
}

View File

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CardReaderService</RootNamespace> <RootNamespace>CardReaderService</RootNamespace>
<AssemblyName>CardReaderService</AssemblyName> <AssemblyName>CardReaderService</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -33,12 +34,12 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="pcsc-sharp, Version=3.6.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="PCSC, Version=4.2.0.0, Culture=neutral, PublicKeyToken=13b76e54a2ee80a7, processorArchitecture=MSIL">
<HintPath>packages\PCSC.3.6.0\lib\net40\pcsc-sharp.dll</HintPath> <HintPath>..\packages\PCSC.4.2.0\lib\net40\PCSC.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@ -53,6 +54,10 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CardDataPost.cs" />
<Compile Include="ConfigurationManager.cs" />
<Compile Include="ConfigurationProperty.cs" />
<Compile Include="ConfigurationType.cs" />
<Compile Include="ConfigureService.cs" /> <Compile Include="ConfigureService.cs" />
<Compile Include="DataCenterHelper.cs" /> <Compile Include="DataCenterHelper.cs" />
<Compile Include="Service1.cs"> <Compile Include="Service1.cs">
@ -68,6 +73,12 @@
<None Include="App.config" /> <None Include="App.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logger\Logger.csproj">
<Project>{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}</Project>
<Name>Logger</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,38 @@
using System;
using System.Configuration;
using System.Linq;
using System.Reflection;
namespace CardReaderService
{
public class ConfigurationManager
{
public ConfigurationProperty GetConfiguration(string configName)
{
var cfgManager = GetConfigManager();
if (cfgManager.AppSettings.Settings.AllKeys.Contains(configName))
{
var cfg = cfgManager.AppSettings.Settings[configName];
return new ConfigurationProperty
{
Group=string.Empty,
Id="NOTIMPLEMENTED",
Name = cfg.Key,
Value = cfg.Value,
ReadOnly = false,
Source = "CardReaderService",
Type = ConfigurationType.STRING
};
}
return null;
}
private Configuration GetConfigManager()
{
return System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigFilePath);
}
public string ConfigFilePath => new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath;
}
}

View File

@ -0,0 +1,55 @@
namespace CardReaderService
{
/// <summary>
/// An object used to detail an element that can be configured in the system.
/// </summary>
public class ConfigurationProperty
{
/// <summary>
/// Unique Id for the configuration property.
/// </summary>
public string Id { get; set; }
/// <summary>
/// The display name of the configuration
/// </summary>
public string Name { get; set; }
/// <summary>
/// The group under which this configuration falls (e.g.: TCP Settings - "TCP" or similar.)
/// </summary>
/// <remarks>Application Specific grouping.</remarks>
public string Group { get; set; }
/// <summary>
/// The actual value of the Configuration property
/// </summary>
public string Value { get; set; }
/// <summary>
/// The source of the configuration properties.
/// </summary>
/// <remarks>
/// Using this property allows aggregation of configuration settings
/// by any configuration managers
/// </remarks>
/// <example>{TYPE}.getType().Name (or other suitably unique identifier)</example>
public string Source { get; set; }
/// <summary>
/// The type of the configuration
/// </summary>
/// <remarks>
/// This can be used for UI proofing/validation to ensure a user
/// enters expected format (e.g.: enters a date time for a datetime type)
/// </remarks>
public ConfigurationType Type { get; set; }
/// <summary>
/// Specifies whether the configuration setting can be set by user or is readonly
/// </summary>
public bool ReadOnly { get; set; }
//TODO: in future, add access level, could go read level/edit level
//public UserAccessLevel UserLevel {get;set;}
}
}

View File

@ -0,0 +1,33 @@
namespace CardReaderService
{
/// <summary>
/// The type of the configuration.
/// </summary>
public enum ConfigurationType
{
/// <summary>
/// No Type - shouldnt be used, but default for enum
/// </summary>
NONE,
/// <summary>
/// a string type configuration (could also be used for complex objects, e.g.: a json string/object).
/// </summary>
STRING,
/// <summary>
/// A whole number configuration
/// </summary>
INT,
/// <summary>
/// A number config with a decimal component
/// </summary>
DECIMAL,
/// <summary>
/// A Date Time Configuration
/// </summary>
DATETIME,
/// <summary>
/// A Boolean/TrueFalse configuration property (e.g.: On Off)
/// </summary>
BOOL
}
}

View File

@ -1,36 +1,40 @@
using System; using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Logger;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace CardReaderService namespace CardReaderService
{ {
static class DataCenterHelper static class DataCenterHelper
{ {
static DataCenterHelper()
{
}
public static void Post(CardDataPost postObject, string url) public static void Post(CardDataPost postObject, string url)
{ {
var endpointConfig = ConfigurationManager.AppSettings["DataCenterServiceEndpoint"] ?? var cfgMgr = new ConfigurationManager();
"http://localhost:8800"; //using ConfigurationManager means we can change the value at runtime without having to restart the service.
var endpointConfig = cfgMgr.GetConfiguration("DataCenterServiceEndpoint")?.Value?? "http://localhost:8800";
MessageLogger.Log("Targeting Endpoint: " + endpointConfig);
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var jsonObject = JsonConvert.SerializeObject(postObject); var jsonObject = JsonConvert.SerializeObject(postObject);
var content = new StringContent(jsonObject, Encoding.UTF8, "application/json"); var content = new StringContent(jsonObject, Encoding.UTF8, "application/json");
try try
{ {
Console.WriteLine("Writing"); MessageLogger.Log("Writing uid: "+ postObject.CardUId + " to the server.");
var fullUrl = endpointConfig + url; var fullUrl = endpointConfig + url;
MessageLogger.Log("Writing to URL: " + fullUrl);
var response = client.PostAsync(fullUrl, content).Result; var response = client.PostAsync(fullUrl, content).Result;
Console.WriteLine("Written"); MessageLogger.Log("Successfully written to server");
} }
catch (Exception) catch (Exception)
{ {
Console.WriteLine("exceptioning"); MessageLogger.Log("Failed to send log to server.");
//ignore
} }
} }
} }
@ -40,9 +44,4 @@ namespace CardReaderService
return Task.Run(() => Post(postObject, url)); return Task.Run(() => Post(postObject, url));
} }
} }
public class CardDataPost
{
public string CardUId { get; set; }
}
} }

View File

@ -1,10 +1,13 @@
using PCSC; using PCSC;
using System; using System;
using System.CodeDom;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.ServiceProcess; using System.ServiceProcess;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Logger;
using PCSC.Monitoring;
namespace CardReaderService namespace CardReaderService
{ {
@ -12,14 +15,15 @@ namespace CardReaderService
{ {
private Thread _mainWorkThread; private Thread _mainWorkThread;
private bool _stopMainWorkerThread; private bool _stopMainWorkerThread;
private AutoResetEvent _mainWorkerTerminationSignal; private readonly ManualResetEvent _mre;
private string _readerName = "";
private string _readerName = string.Empty;
private SCardMonitor _cardMonitor; private SCardMonitor _cardMonitor;
private bool _initialised=false;
public Service1() public Service1()
{ {
InitializeComponent(); InitializeComponent();
_mre = new ManualResetEvent(false);
} }
public void Start() public void Start()
@ -27,44 +31,54 @@ namespace CardReaderService
OnStart(new string[] { }); OnStart(new string[] { });
} }
protected override void OnStart(string[] args) public new void Stop()
{
StartWorkerThread();
}
private bool WeHaveValidCardReader()
{
if (_cardMonitor == null)
{
return false;
}
Console.WriteLine(_cardMonitor.GetCurrentState(0));
if (_cardMonitor.GetCurrentState(0) == SCRState.Unknown
|| _cardMonitor.GetCurrentState(0) == SCRState.Unavailable
|| _cardMonitor.GetCurrentState(0) == (SCRState.Ignore | SCRState.Unavailable)
//|| _cardMonitor.GetCurrentState(0) == SCRState.Unaware //if we say this is an invalid state, we cause a memory leak where we create a duplicate card monitor, subscribe and overwrite.
)
{
return false;
}
return true;
}
public void Stop()
{ {
OnStop(); OnStop();
base.Stop();
}
protected override void OnStart(string[] args)
{
MessageLogger.Log("Starting.. ");
var ctxFactory = ContextFactory.Instance;
using (var context = ctxFactory.Establish(SCardScope.System))
{
MessageLogger.Log("Getting Card Readers");
var readerNames = context.GetReaders();
if (NoReaderAvailable(readerNames))
{
MessageLogger.Log("No Card Reader available, Exiting..");
}
else
{
MessageLogger.Log("Choosing first available reader: " + readerNames.First());
_readerName = readerNames.First();
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += CardMonitor_CardInserted;
_cardMonitor.StatusChanged += CardMonitorOnStatusChanged;
_cardMonitor.Start(_readerName);
}
StartWorkerThread();
}
} }
protected override void OnStop() protected override void OnStop()
{ {
MessageLogger.Log("Stopping..");
_stopMainWorkerThread = true; _stopMainWorkerThread = true;
_mainWorkerTerminationSignal.Set(); _mre.Set();
if (_mainWorkThread != null && _mainWorkThread.IsAlive) if (_mainWorkThread != null && _mainWorkThread.IsAlive)
{
_mainWorkThread.Join(3000);
if (_mainWorkThread.IsAlive)
{ {
_mainWorkThread.Interrupt(); _mainWorkThread.Interrupt();
} }
}
if (_cardMonitor != null) if (_cardMonitor != null)
{ {
_cardMonitor.CardInserted -= CardMonitor_CardInserted;
_cardMonitor.StatusChanged -= CardMonitorOnStatusChanged;
_cardMonitor.Cancel(); _cardMonitor.Cancel();
_cardMonitor.Dispose(); _cardMonitor.Dispose();
_cardMonitor = null; _cardMonitor = null;
@ -79,96 +93,110 @@ namespace CardReaderService
Name = "CardServiceMainThread", Name = "CardServiceMainThread",
IsBackground = false IsBackground = false
}; };
_mainWorkerTerminationSignal = new AutoResetEvent(false);
_mainWorkThread.Start(); _mainWorkThread.Start();
} }
private void _cardMonitor_CardInserted(object sender, CardStatusEventArgs e) private void CardMonitor_CardInserted(object sender, CardStatusEventArgs e)
{
try
{ {
var ctxFac = ContextFactory.Instance; var ctxFac = ContextFactory.Instance;
using (var ctx = ctxFac.Establish(SCardScope.System)) using (var ctx = ctxFac.Establish(SCardScope.System))
{ {
var reader = new SCardReader(ctx); var reader = new SCardReader(ctx);
var pioSendPci = new IntPtr();
byte[] rcvBuffer = new byte[256]; byte[] rcvBuffer = new byte[256];
if (_readerName == string.Empty) if (_readerName == string.Empty)
{ {
Console.WriteLine("Reader name is somehow empty... WTF!"); MessageLogger.Log("Reader name is somehow empty... WTF!");
_stopMainWorkerThread = true; _stopMainWorkerThread = true;
return; return;
} }
var err = reader.Connect(_readerName, SCardShareMode.Shared, SCardProtocol.T0 | SCardProtocol.T1); var err = reader.Connect(_readerName, SCardShareMode.Shared, SCardProtocol.Any);
if (err == SCardError.Success) if (err == SCardError.Success)
{ {
var uIdcmd = new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x00 }; var pci = SCardPCI.GetPci(reader.ActiveProtocol);
err = reader.Transmit(pci, CardCommands.GetUid, ref rcvBuffer);
err = reader.Transmit(pioSendPci, uIdcmd, ref rcvBuffer);
if (err == SCardError.Success) if (err == SCardError.Success)
{ {
var uid = ConvertByteUIDToString(rcvBuffer); var uid = ConvertByteUIDToString(rcvBuffer);
var atrString = ConvertByteUIDToString(e.Atr); var atrString = ConvertByteUIDToString(e.Atr);
Console.WriteLine("Card Inserted, ATR: " + atrString + ", and UID is: " + uid);
CardDataPost postObj = new CardDataPost { CardUId = uid }; MessageLogger.Log("Card Inserted, ATR: " + atrString + ", and UID is: " + uid);
CardDataPost postObj = new CardDataPost { CardUId = uid, UtcTimeStamp = DateTime.UtcNow };
DataCenterHelper.PostAsync(postObj, "/api/swipedata"); DataCenterHelper.PostAsync(postObj, "/api/swipedata");
Console.WriteLine("Posted to Server"); MessageLogger.Log("Posted to Server");
}
else
{
MessageLogger.Log("Failed to Read UID, Error: " + err);
} }
} }
else else
{ {
Console.WriteLine("Reader failed to connect, error: " + Enum.GetName(typeof(SCardError), err)); MessageLogger.Log("Reader failed to connect, error: " + Enum.GetName(typeof(SCardError), err));
} }
}
}
catch (Exception ex)
{
MessageLogger.Log("Exception has occurred in Card Inserted Event handler. Exception: "+ ex);
throw;
} }
} }
private void MainWorkerThread() private void MainWorkerThread()
{ {
var minimumLoopTimemS = 5000;
while (!_stopMainWorkerThread) while (!_stopMainWorkerThread)
{ {
if (!WeHaveValidCardReader()) if (_cardMonitor == null)
{ //only do this if we don't have a valid card reader
if (_initialised)
{ {
//card reader no longer available, tidy up.
_cardMonitor.Cancel();
_cardMonitor.CardInserted -= _cardMonitor_CardInserted;
_cardMonitor.Dispose();
_cardMonitor = null;
_initialised = false;
}
Console.WriteLine("Starting.. Getting available readers");
var ctxFactory = ContextFactory.Instance; var ctxFactory = ContextFactory.Instance;
using (var context = ctxFactory.Establish(SCardScope.System)) using (var context = ctxFactory.Establish(SCardScope.System))
{ {
var readerNames = context.GetReaders(); var readerNames = context.GetReaders();
if (!NoReaderAvailable(readerNames)) if (NoReaderAvailable(readerNames))
{ {
//we have a reader available, so initialise! MessageLogger.Log("No Card Reader available, Waiting..");
Console.WriteLine("Choosing first available reader: " + readerNames.First());
_readerName = readerNames.First();
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += _cardMonitor_CardInserted;
_cardMonitor.Start(_readerName);
_initialised = true;
} }
else else
{ {
Console.WriteLine("No Card Reader is available.."); MessageLogger.Log("Choosing first available reader: " + readerNames.First());
_readerName = readerNames.First();
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += CardMonitor_CardInserted;
_cardMonitor.StatusChanged += CardMonitorOnStatusChanged;
_cardMonitor.Start(_readerName);
} }
} }
} }
_mainWorkerTerminationSignal.WaitOne(3000); //dont actually need to do anything.. but cannot exit right away?
_mre.WaitOne(minimumLoopTimemS);
}
}
private void CardMonitorOnStatusChanged(object sender, StatusChangeEventArgs e)
{
if ((e.NewState & SCRState.Unavailable) == SCRState.Unavailable && (e.NewState & SCRState.Ignore) == SCRState.Ignore)
{
Console.WriteLine("Card Reader Disconnected");
_cardMonitor.CardInserted -= CardMonitor_CardInserted;
_cardMonitor.Cancel();
_cardMonitor.Dispose();
_cardMonitor = null;
_readerName = string.Empty;
} }
} }
private string ConvertByteUIDToString(byte[] uid) private string ConvertByteUIDToString(byte[] uid)
{ {
var sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
foreach (var b in uid) foreach (var b in uid)
{ {
@ -183,4 +211,9 @@ namespace CardReaderService
return readerNames == null || readerNames.Count < 1; return readerNames == null || readerNames.Count < 1;
} }
} }
internal class CardCommands
{
public static readonly byte[] GetUid = {0xFF, 0xCA, 0x00, 0x00, 0x08};
}
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net45" />
<package id="PCSC" version="3.6.0" targetFramework="net452" /> <package id="PCSC" version="4.2.0" targetFramework="net45" />
</packages> </packages>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup> </startup>
</configuration> </configuration>

View File

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CardReaderServiceHost</RootNamespace> <RootNamespace>CardReaderServiceHost</RootNamespace>
<AssemblyName>CardReaderServiceHost</AssemblyName> <AssemblyName>CardReaderServiceHost</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -33,8 +34,8 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\CardReaderService\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@ -52,6 +53,18 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\CardReaderService.conf">
<Link>CardReaderService.conf</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\install.sh">
<Link>install.sh</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\uninstall.sh">
<Link>uninstall.sh</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="App.config" /> <None Include="App.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>

View File

@ -9,7 +9,7 @@ namespace CardReaderServiceHost
{ {
Service1 service1 = new Service1(); Service1 service1 = new Service1();
service1.Start(); service1.Start();
Console.WriteLine("Service Running..."); Console.WriteLine("Service Running... Enter To Stop");
Console.ReadLine(); Console.ReadLine();
service1.Stop(); service1.Stop();
} }

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net45" />
</packages> </packages>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{42EFE386-DC2E-455A-BA81-5FC9CEE45D02}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Logger</RootNamespace>
<AssemblyName>Logger</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MessageLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Logger
{
public class MessageLogger
{
private static string _logPath = "log.txt";
public static void Log(string message)
{
Console.WriteLine(message);
File.AppendAllText(_logPath, string.Format("{0} | {1}{2}", DateTime.Now, message, Environment.NewLine));
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Logger")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Logger")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("42efe386-dc2e-455a-ba81-5fc9cee45d02")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,7 @@
install:
- pcscd
- supervisor
- mono-complete
copy CardReaderService.conf to /home/osboxes/CardReaderService

View File

@ -0,0 +1,25 @@
#!/bin/sh
if [ -z "$1" ]
then
echo "No arguments supplied, supply the base path"
fi
echo "Installing dependencies"
apt-get install mono-complete pcscd supervisor -y
echo "Modifying supervisor conf file"
sed -i "s|/DIRECTORYTOSET|$1|g" CardReaderService.conf
echo "Installing service configuration file"
cp CardReaderService.conf /etc/supervisor/conf.d/
echo "Installing applicaton"
mkdir $1/CardReaderService
cp ./*.* $1/CardReaderService
cd $1/CardReaderService
rm *.pdb
rm *.vshost.exe
rm *.vshost.exe.config
rm *.vshost.exe.manifest
echo "Starting application........."
service supervisor restart

View File

@ -0,0 +1,12 @@
#!/bin/sh
if [ -z "$1" ]
then
echo "No arguments supplied, supply the base path"
fi
echo "Stopping service...."
service supervisor stop
rm -f /etc/supervisor/conf.d/CardReaderService.conf
echo "Removing Application.."
rm -r -f $1/CardReaderService
echo "Removing application dependencies.."
apt-get remove pcscd supervisor mono-complete -y

View File

@ -1,10 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup> </startup>
<appSettings> <appSettings>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml"/> <add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DataCenterServiceEndpoint" value="http://localhost:8800"/> <add key="DataCenterServiceEndpoint" value="http://localhost:8800" />
</appSettings> </appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Ninject" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.4.0" newVersion="3.3.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> </configuration>

View File

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CardReaderService</RootNamespace> <RootNamespace>CardReaderService</RootNamespace>
<AssemblyName>CardReaderService</AssemblyName> <AssemblyName>CardReaderService</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -57,12 +58,12 @@
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject, Version=3.3.4.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll</HintPath> <HintPath>..\packages\Ninject.3.3.4\lib\net45\Ninject.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Extensions.Xml, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Extensions.Xml, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.Xml.3.2.0.0\lib\net45-full\Ninject.Extensions.Xml.dll</HintPath> <HintPath>..\packages\Ninject.Extensions.Xml.3.3.0\lib\net45\Ninject.Extensions.Xml.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="pcsc-sharp, Version=3.6.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="pcsc-sharp, Version=3.6.0.0, Culture=neutral, processorArchitecture=MSIL">
@ -85,6 +86,7 @@
<Compile Include="ConfigureService.cs" /> <Compile Include="ConfigureService.cs" />
<Compile Include="DataCenterHelper.cs" /> <Compile Include="DataCenterHelper.cs" />
<Compile Include="DefaultComponents\DefaultLogger.cs" /> <Compile Include="DefaultComponents\DefaultLogger.cs" />
<Compile Include="DefaultComponents\LogFileLogger.cs" />
<Compile Include="NinjectHelper.cs" /> <Compile Include="NinjectHelper.cs" />
<Compile Include="CardReaderService.cs"> <Compile Include="CardReaderService.cs">
<SubType>Component</SubType> <SubType>Component</SubType>

View File

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Interfaces;
namespace CardReaderService.DefaultComponents
{
class LogFileLogger: ILogger
{
public bool IsDebugEnabled => true;
public bool IsErrorEnabled => true;
public bool IsFatalEnabled => true;
public bool IsInfoEnabled => true;
public bool IsTraceEnabled => true;
public bool IsWarnEnabled => true;
public void Debug(Exception exception)
{
Log(exception.ToString());
}
public void Debug(string format, params object[] args)
{
Log(string.Format(format, args));
}
public void Debug(Exception exception, string format, params object[] args)
{
Log(exception.ToString());
Log(string.Format(format, args));
}
public void Error(Exception exception)
{
Log(exception.ToString());
}
public void Error(string format, params object[] args)
{
Log(string.Format(format, args));
}
public void Error(Exception exception, string format, params object[] args)
{
Log(exception.ToString());
Log(string.Format(format, args));
}
public void Fatal(Exception exception)
{
Log(exception.ToString());
}
public void Fatal(string format, params object[] args)
{
Log(string.Format(format, args));
}
public void Fatal(Exception exception, string format, params object[] args)
{
Log(exception.ToString());
Log(string.Format(format, args));
}
public void Info(Exception exception)
{
Log(exception.ToString());
}
public void Info(string format, params object[] args)
{
Log(string.Format(format, args));
}
public void Info(Exception exception, string format, params object[] args)
{
Log(exception.ToString());
Log(string.Format(format, args));
}
public void Trace(Exception exception)
{
Log(exception.ToString());
}
public void Trace(string format, params object[] args)
{
Log(string.Format(format, args));
}
public void Trace(Exception exception, string format, params object[] args)
{
Log(exception.ToString());
Log(string.Format(format, args));
}
public void Warn(Exception exception)
{
Log(exception.ToString());
}
public void Warn(string format, params object[] args)
{
Log(string.Format(format, args));
}
public void Warn(Exception exception, string format, params object[] args)
{
Log(exception.ToString());
Log(string.Format(format, args));
}
private static string _logPath = "log.txt";
public static void Log(string message)
{
System.IO.File.WriteAllText(_logPath, string.Format("{0} | {1}", DateTime.Now, message));
}
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<module name="NinjectAssemblies"> <module name="NinjectAssemblies">
<bind service="Interfaces.ILogger, Interfaces" <bind service="Interfaces.ILogger, Interfaces"
to="NLogLogger.NLogger, NLogLogger" scope="singleton" /> to="CardReaderService.DefaultComponents.LogFileLogger, CardReaderService" scope="singleton" />
</module> </module>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
<package id="Ninject" version="3.2.2.0" targetFramework="net452" /> <package id="Ninject" version="3.3.4" targetFramework="net45" />
<package id="Ninject.Extensions.Xml" version="3.2.0.0" targetFramework="net452" /> <package id="Ninject.Extensions.Xml" version="3.3.0" targetFramework="net45" />
<package id="PCSC" version="3.6.0" targetFramework="net452" /> <package id="PCSC" version="3.6.0" targetFramework="net45" />
</packages> </packages>

View File

@ -1,10 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup> </startup>
<appSettings> <appSettings>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml"/> <add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DataCenterServiceEndpoint" value="http://localhost:1234"/> <add key="DataCenterServiceEndpoint" value="http://localhost:1234" />
</appSettings> </appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Ninject" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.4.0" newVersion="3.3.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> </configuration>

View File

@ -54,7 +54,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\CardReaderService\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />

View File

@ -30,8 +30,18 @@
<Component Id="SmartCardServiceConfig" Guid="{73D2E31D-F256-457C-AFD5-EC456CDAD7E8}" KeyPath="yes"> <Component Id="SmartCardServiceConfig" Guid="{73D2E31D-F256-457C-AFD5-EC456CDAD7E8}" KeyPath="yes">
<ServiceControl Id="SmartCardServiceStarter" <ServiceControl Id="SmartCardServiceStarter"
Start="install" Start="install"
Stop="install"
Name="SCardSvr" Name="SCardSvr"
Wait="yes" /> Wait="yes" />
<ServiceConfig Id="SmartCardServiceSetToAuto"
DelayedAutoStart="0" OnInstall="yes" ServiceName="SCardSvr"/>
</Component>
<Component Id="SCardAutoStart" Permanent="yes" KeyPath="yes" Guid="{75582D3C-C985-4BC9-A0A8-621A52C7E95E}">
<RegistryKey Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\SCardSvr"
Action="create">
<RegistryValue Type="integer" Name="Start" Value="2"/>
</RegistryKey>
</Component> </Component>
<Component Id="CardReaderServiceExe" Guid="{24C9D834-21E2-476D-8302-EF35730D0BA8}"> <Component Id="CardReaderServiceExe" Guid="{24C9D834-21E2-476D-8302-EF35730D0BA8}">
<File Id="CardReaderService.exe" <File Id="CardReaderService.exe"
@ -61,6 +71,7 @@
ThirdFailureActionType="restart" ThirdFailureActionType="restart"
ResetPeriodInDays="1" ResetPeriodInDays="1"
RestartServiceDelayInSeconds="10" /> RestartServiceDelayInSeconds="10" />
<ServiceDependency Id="SCardSvr" Group="no"/>
</ServiceInstall> </ServiceInstall>
</Component> </Component>
<Component Id="CardReaderServiceExeConfig" Guid="{E20D23BC-C8E7-49F8-962C-DE856A84258E}"> <Component Id="CardReaderServiceExeConfig" Guid="{E20D23BC-C8E7-49F8-962C-DE856A84258E}">

View File

@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ConfigurationHandler</RootNamespace> <RootNamespace>ConfigurationHandler</RootNamespace>
<AssemblyName>ConfigurationHandler</AssemblyName> <AssemblyName>ConfigurationHandler</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>

View File

@ -12,7 +12,7 @@ namespace Interfaces
/// Returns <see cref="UserList"/> with full list of users, /// Returns <see cref="UserList"/> with full list of users,
/// plus a total user count. Pagination options are supported. /// plus a total user count. Pagination options are supported.
/// </returns> /// </returns>
UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1); UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1, SortOptions sort = SortOptions.None);
/// <summary> /// <summary>
/// Search the user list for the following string /// Search the user list for the following string
/// </summary> /// </summary>
@ -96,10 +96,12 @@ namespace Interfaces
/// <see cref="Identifier"/> object with the Unique Id triggering the event /// <see cref="Identifier"/> object with the Unique Id triggering the event
/// </param> /// </param>
/// <param name="logId">The resultant Id of the inserted TimeLog</param> /// <param name="logId">The resultant Id of the inserted TimeLog</param>
/// <param name="logTime">Optional - To set the log time of the swipe.
/// <remarks>Particularly useful for buffering logs in nodes if they cannot contact server for whatever reason to minimise data loss</remarks></param>
/// <returns> /// <returns>
/// <see cref="OperationResponse"/> to indicate procedure status. /// <see cref="OperationResponse"/> to indicate procedure status.
/// </returns> /// </returns>
LogEventResponse LogEventTime(Identifier identifier, out int logId); LogEventResponse LogEventTime(Identifier identifier, out int logId, DateTime logTime = default(DateTime));
OperationResponse CreateGroup(Group group, out int groupId); OperationResponse CreateGroup(Group group, out int groupId);
List<Group> GetGroups(int userId = -1); List<Group> GetGroups(int userId = -1);
@ -110,5 +112,9 @@ namespace Interfaces
OperationResponse DeleteLog(TimeLog log); OperationResponse DeleteLog(TimeLog log);
OperationResponse CreateLog(TimeLog log); OperationResponse CreateLog(TimeLog log);
OperationResponse UpdateLog(TimeLog log); OperationResponse UpdateLog(TimeLog log);
Policy GetPolicy();
void SavePolicy(Policy policy);
} }
} }

View File

@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Interfaces</RootNamespace> <RootNamespace>Interfaces</RootNamespace>
<AssemblyName>Interfaces</AssemblyName> <AssemblyName>Interfaces</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -69,7 +70,9 @@
<Compile Include="IdentifierList.cs" /> <Compile Include="IdentifierList.cs" />
<Compile Include="ManualLog.cs" /> <Compile Include="ManualLog.cs" />
<Compile Include="OperationResponse.cs" /> <Compile Include="OperationResponse.cs" />
<Compile Include="Policy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SortOptions.cs" />
<Compile Include="TimeLog.cs" /> <Compile Include="TimeLog.cs" />
<Compile Include="TimeLogList.cs" /> <Compile Include="TimeLogList.cs" />
<Compile Include="User.cs" /> <Compile Include="User.cs" />

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Interfaces
{
public class Policy
{
public DateTime ChangeDate { get; set; }
public string ChangeDescription { get; set; }
public string ChangeAuthor { get; set; }
public string Version { get; set; }
public string Markdown { get; set; }
public string Html { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Interfaces
{
public enum SortOptions
{
None,
FirstNameAscending,
FirstNameDescending,
LastNameAscending,
LastNameDescending,
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace Interfaces namespace Interfaces
{ {
@ -14,13 +15,15 @@ namespace Interfaces
public int TotalUserCount { get; set; } public int TotalUserCount { get; set; }
public List<User> Users { get; set; } public List<User> Users { get; set; }
public SortOptions SelectedSortOption { get; set; }
public int PageCount public int PageCount
{ {
get get
{ {
if (TotalUserCount < PageSize) if (TotalUserCount < PageSize)
return 1; return 1;
return (TotalUserCount / PageSize); return (int)Math.Ceiling(Convert.ToDouble(TotalUserCount) / Convert.ToDouble(PageSize));
} }
} }
public Group GroupFilter { get; set; } public Group GroupFilter { get; set; }

View File

@ -0,0 +1,29 @@
using Interfaces;
namespace SQLiteRepository.Converters
{
static class IdentifierConverter
{
public static Identifier ConvertToIdentifierDto(CardUniqueId ident)
{
return new Identifier
{
Id = ident.Id,
UniqueId = ident.CardUId,
IsAssociatedToUser = ident.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID,
LastUsed = ident.LastUsed.DateTime
};
}
public static CardUniqueId ConvertFromIdentifierDto(Identifier ident, int userId)
{
return new CardUniqueId
{
CardUId = ident.UniqueId,
Id = ident.Id,
UserId_FK = userId,
LastUsed = ident.LastUsed
};
}
}
}

View File

@ -0,0 +1,51 @@
using Interfaces;
namespace SQLiteRepository.Converters
{
static class LogDirectionConverter
{
public static LogDirection ConvertToLogDirectionDto(LogDirectionDb direction)
{
switch (direction)
{
case LogDirectionDb.IN:
return LogDirection.IN;
case LogDirectionDb.OUT:
return LogDirection.OUT;
default:
return LogDirection.UNKNOWN;
}
}
public static LogDirectionDb ConvertFromLogDirectionDto(LogDirection direction)
{
switch (direction)
{
case LogDirection.IN:
return LogDirectionDb.IN;
case LogDirection.OUT:
return LogDirectionDb.OUT;
default:
return LogDirectionDb.UNKNOWN;
}
}
public static LogDirectionDb InvertLogDirectionDb(LogDirectionDb direction)
{
return (LogDirectionDb)(int)InvertLogDirection((LogDirection)(int)direction);
}
public static LogDirection InvertLogDirection(LogDirection direction)
{
switch (direction)
{
case LogDirection.IN:
return LogDirection.OUT;
case LogDirection.OUT:
return LogDirection.IN;
default:
return LogDirection.UNKNOWN;
}
}
}
}

View File

@ -0,0 +1,37 @@
using Interfaces;
namespace SQLiteRepository.Converters
{
static class LogSourceConverter
{
public static LogSource ConvertToLogSourceDto(LogSourceDb source)
{
switch (source)
{
case LogSourceDb.IDENTIFIER:
return LogSource.IDENTIFIER;
case LogSourceDb.TRAYAPP:
return LogSource.TRAYAPP;
case LogSourceDb.UI:
return LogSource.UI;
default:
return LogSource.UNKNOWN;
}
}
public static LogSourceDb ConvertFromLogSourceDto(LogSource source)
{
switch (source)
{
case LogSource.IDENTIFIER:
return LogSourceDb.IDENTIFIER;
case LogSource.TRAYAPP:
return LogSourceDb.TRAYAPP;
case LogSource.UI:
return LogSourceDb.UI;
default:
return LogSourceDb.UNKNOWN;
}
}
}
}

View File

@ -0,0 +1,35 @@
using Interfaces;
namespace SQLiteRepository.Converters
{
static class PolicyConverter
{
public static Policy ConvertToPolicyDto(PolicyDb policyDb)
{
if (policyDb == null) return null;
return new Policy
{
ChangeDescription = policyDb.ChangeDescription,
ChangeDate = policyDb.ChangeDate.UtcDateTime,
Html = policyDb.Html,
Version = policyDb.Version,
ChangeAuthor = policyDb.ChangeAuthor,
Markdown = policyDb.Markdown
};
}
public static PolicyDb ConvertFromPolicyDto(Policy policy)
{
if (policy == null) return null;
return new PolicyDb
{
ChangeDescription = policy.ChangeDescription,
ChangeDate = policy.ChangeDate,
Html = policy.Html,
Version = policy.Version,
ChangeAuthor = policy.ChangeAuthor,
Markdown = policy.Markdown
};
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Interfaces;
namespace SQLiteRepository.Converters
{
static class TimeLogConverter
{
public static TimeLog ConvertToTimeLogDto(TimeLogDb log)
{
return new TimeLog
{
CalendarWeek = log.CalendarWeek,
Direction = LogDirectionConverter.ConvertToLogDirectionDto(log.Direction),
EventTime = log.SwipeEventDateTime,
Id = log.Id,
IdentifierId = log.IdentifierId,
UserId = log.UserId_FK,
Source = LogSourceConverter.ConvertToLogSourceDto(log.Source),
Year = log.Year
};
}
public static TimeLogDb ConvertFromTimeLogDto(TimeLog log)
{
return new TimeLogDb
{
CalendarWeek = log.CalendarWeek,
Year = log.Year,
UserId_FK = log.UserId,
IdentifierId = log.IdentifierId,
Direction = LogDirectionConverter.ConvertFromLogDirectionDto(log.Direction),
Id = log.Id,
Source = LogSourceConverter.ConvertFromLogSourceDto(log.Source),
SwipeEventDateTime = log.EventTime
};
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using Interfaces;
namespace SQLiteRepository.Converters
{
static class UserConverter
{
public static User ConvertToUserDto(UserIdentity user)
{
return new User
{
UserId = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
IsContractor = user.IsContractor,
HoursPerWeek = user.HoursPerWeek,
};
}
public static UserIdentity ConvertFromUserDto(User user)
{
return new UserIdentity
{
Id = user.UserId,
FirstName = user.FirstName,
LastName = user.LastName,
HoursPerWeek = user.HoursPerWeek,
IsContractor = user.IsContractor
};
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SQLiteRepository.Extensions
{
public static class EnumerableExtensions
{
public static IEnumerable<TSource> Exclude<TSource, TKey>(this IEnumerable<TSource> source,
IEnumerable<TSource> exclude, Func<TSource, TKey> keySelector)
{
var excludedSet = new HashSet<TKey>(exclude.Select(keySelector));
return source.Where(item => !excludedSet.Contains(keySelector(item)));
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SQLite.Net.Attributes;
namespace SQLiteRepository
{
internal class PolicyDb
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public DateTimeOffset ChangeDate { get; set; }
public string ChangeDescription { get; set; }
public string ChangeAuthor { get; set; }
public string Version { get; set; }
public string Markdown { get; set; }
public string Html { get; set; }
}
}

View File

@ -1,9 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
namespace SQLiteRepository namespace SQLiteRepository
{ {
internal static class SQLiteProcedures internal static class SQLiteProcedures
{ {
public const string GET_LOGS_IN_LAST_X_MINUTES = public const string GET_LOGS_IN_LAST_X_MINUTES =
"select * from TimeLogDb where " + nameof(TimeLogDb.SwipeEventDateTime) + " > ? AND " + "select * from " + nameof(TimeLogDb) + " where " + nameof(TimeLogDb.SwipeEventDateTime) + " > ? AND " +
nameof(TimeLogDb.UserId_FK) + "=?"; nameof(TimeLogDb.UserId_FK) + "=?";
public const string GET_TIMELOGS = public const string GET_TIMELOGS =
@ -22,9 +27,7 @@ namespace SQLiteRepository
+ "select " + nameof(GroupDb.GroupId) + "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb) + " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )" + " where " + nameof(GroupDb.GroupName) + " = 'Archived') )"
+ "order by " + "order by ? collate nocase ?, ? collate nocase ?";
+ nameof(UserIdentity.LastName) + " collate nocase, "
+ nameof(UserIdentity.FirstName) + " collate nocase";
public const string GET_ALL_USERS_PAGINATE = public const string GET_ALL_USERS_PAGINATE =
"select * from " + nameof(UserIdentity) + " ut " "select * from " + nameof(UserIdentity) + " ut "
@ -38,18 +41,18 @@ namespace SQLiteRepository
+ "select " + nameof(GroupDb.GroupId) + "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb) + " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )" + " where " + nameof(GroupDb.GroupName) + " = 'Archived') )"
+ "order by " + " order by "
+ nameof(UserIdentity.LastName) + " collate nocase, " + "{0} collate nocase {1}, "
+ nameof(UserIdentity.FirstName) + " collate nocase " + "{2} collate nocase {3} "
+ "limit ? offset ?"; + "limit {4} offset {5}";
public const string GET_ALL_USERS_BY_GROUP = public const string GET_ALL_USERS_BY_GROUP =
"select u." + nameof(UserIdentity.Id) + ", u." + nameof(UserIdentity.FirstName) + ", u." + "select u." + nameof(UserIdentity.Id) + ", u." + nameof(UserIdentity.FirstName) + ", u." +
nameof(UserIdentity.LastName) + ", u." + nameof(UserIdentity.HoursPerWeek) + ", u." + nameof(UserIdentity.LastName) + ", u." + nameof(UserIdentity.HoursPerWeek) + ", u." +
nameof(UserIdentity.IsContractor) + " from " + nameof(UserIdentity) + " u left join " + nameof(UserIdentity.IsContractor) + " from " + nameof(UserIdentity) + " u left join " +
nameof(UserGroupJoinDb) + " ugj on ugj." + nameof(UserGroupJoinDb.UserId_FK) + " = u." + nameof(UserGroupJoinDb) + " ugj on ugj." + nameof(UserGroupJoinDb.UserId_FK) + " = u." +
nameof(UserIdentity.Id) + " where ugj." + nameof(UserGroupJoinDb.GroupId_FK) + "=? order by u." + nameof(UserIdentity.Id) + " where ugj." + nameof(UserGroupJoinDb.GroupId_FK) + "= {0} " +
nameof(UserIdentity.LastName) + " collate nocase, u." + nameof(UserIdentity.LastName) + " collate nocase"; "order by u.{1} collate nocase {2}, u.{3} collate nocase {4}";
public const string GET_USER_BY_ID = public const string GET_USER_BY_ID =
"select * from " + nameof(UserIdentity) + " where " + nameof(UserIdentity.Id) + "=?"; "select * from " + nameof(UserIdentity) + " where " + nameof(UserIdentity.Id) + "=?";
@ -60,6 +63,9 @@ namespace SQLiteRepository
public const string GET_CARDS_BY_UNIQUE_ID = public const string GET_CARDS_BY_UNIQUE_ID =
"select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.CardUId) + "=?"; "select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.CardUId) + "=?";
public const string GET_CARDS_BY_UNIQUE_ID_LIST =
"select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.CardUId) + " in (#IN#)";
public const string GET_UNASSIGNED_CARD_LIST = public const string GET_UNASSIGNED_CARD_LIST =
"select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.UserId_FK) + "=?"; "select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.UserId_FK) + "=?";
@ -88,9 +94,82 @@ namespace SQLiteRepository
nameof(TimeLogDb.SwipeEventDateTime) + " desc LIMIT 1"; nameof(TimeLogDb.SwipeEventDateTime) + " desc LIMIT 1";
public const string GET_TOTAL_USER_COUNT = public const string GET_TOTAL_USER_COUNT =
"select Max(" + nameof(UserIdentity.Id) + ") from " + nameof(UserIdentity); "select count(1) from " + nameof(UserIdentity) + " ut "
+ "where "
+ "EXISTS( select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') "
+ "AND NOT EXISTS( select * from " + nameof(UserGroupJoinDb) + " ugp where "
+ nameof(UserGroupJoinDb.UserId_FK) + " = ut.Id"
+ " and " + nameof(UserGroupJoinDb.GroupId_FK) + " = ( "
+ "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )";
//"select Max(" + nameof(UserIdentity.Id) + ") from " + nameof(UserIdentity);
public const string GET_USER_CONTRACTED_HOURS = public const string GET_USER_CONTRACTED_HOURS =
"select " + nameof(UserIdentity.HoursPerWeek) + " From UserIdentity where " + nameof(UserIdentity.Id) + "=?"; "select " + nameof(UserIdentity.HoursPerWeek) + " From UserIdentity where " + nameof(UserIdentity.Id) + "=?";
public const string GET_GROUPS = "select gp."+ nameof(GroupDb.GroupId)+ ", gp."+nameof(GroupDb.GroupName)+", " +
"sum(case when gp." + nameof(GroupDb.GroupId) + " = ujdb." + nameof(UserGroupJoinDb.GroupId_FK) + " then 1 else 0 end) as " + nameof(GroupDb.AssignedUserCount) +
" from " + nameof(GroupDb) + " gp" +
" left join " + nameof(UserGroupJoinDb) + " ujdb " +
"on ujdb." + nameof(UserGroupJoinDb.GroupId_FK) + " = gp." + nameof(GroupDb.GroupId) +
" group by gp." + nameof(GroupDb.GroupId);
public const string GET_GROUPS_FOR_USER = "select gdb." + nameof(GroupDb.GroupId) + ", gdb." + nameof(GroupDb.GroupName) + ", gdb." + nameof(GroupDb.AssignedUserCount) + "" +
" from " + nameof(GroupDb) + " gdb" +
" left join " + nameof(UserGroupJoinDb) + " ujdb" +
" on gdb." + nameof(GroupDb.GroupId) + " = ujdb." + nameof(UserGroupJoinDb.GroupId_FK) +
" where ujdb." + nameof(UserGroupJoinDb.UserId_FK) + " = ?";
public const string GET_GROUP_BY_ID =
"select * from " + nameof(GroupDb) + " where " + nameof(GroupDb.GroupId) + " =?";
public const string UPDATE_GROUP = "update " + nameof(GroupDb) + " set " + nameof(GroupDb.GroupName) +
"=? where " + nameof(GroupDb.GroupId) + "=?";
public const string GET_GROUP_BY_NAME =
"select 1 from " + nameof(GroupDb)+" where "+nameof(GroupDb.GroupName)+"= ?";
public const string GET_TIMELOG_ENTRY =
"select * from " + nameof(TimeLogDb) + " where " + nameof(TimeLogDb.Id) + "=?";
public const string DELETE_TIMELOG_ENTRY =
"delete from " + nameof(TimeLogDb) + " where " + nameof(TimeLogDb.Id) + "=?";
public const string DELETE_TIMELOG_ENTRIES =
"delete from " + nameof(TimeLogDb) + " where " + nameof(TimeLogDb.Id) + "in (#IN#)";
public const string UPDATE_TIMELOG_ENTRY = "update " + nameof(TimeLogDb)
+ " set " + nameof(TimeLogDb.UserId_FK) + "=?, "
+ nameof(TimeLogDb.Direction) + "=?,"
+ nameof(TimeLogDb.SwipeEventDateTime) + "=?,"
+ nameof(TimeLogDb.CalendarWeek) + "=?,"
+ nameof(TimeLogDb.Year) + "=?,"
+ nameof(TimeLogDb.Source) + "=? "
+ "where " + nameof(TimeLogDb.Id) + "=?";
public const string GET_DB_VERSION = "select * from " + nameof(DbVersion);
/// <summary>
/// This works on the tokenisation of the query string.
/// where the list of params is to be inserted, the query should have #IN#
/// </summary>
/// <param name="query">the query to add the list of parameters to</param>
/// <param name="args">the parameters to add to the query.</param>
/// <returns></returns>
public static string FormatInQuery(string query, List<string> args)
{
if (!query.Contains("#IN#"))
{
throw new ArgumentException("query doesnt contain any #IN# tokenisation");
}
//Convert to a comma separated list e.g.: val1,val2,val3
//but we need to use string.. so.. enclose in single quotes 'val1','val2'..etc
var argsFormatted = string.Join(",", args.Select(x=>$"'{x}'"));
return query.Replace("#IN#", argsFormatted);
}
} }
} }

View File

@ -8,6 +8,8 @@ using System.Reflection;
using Interfaces; using Interfaces;
using SQLite.Net; using SQLite.Net;
using SQLite.Net.Platform.Win32; using SQLite.Net.Platform.Win32;
using SQLiteRepository.Converters;
using SQLiteRepository.Extensions;
using SQLiteRepository.Properties; using SQLiteRepository.Properties;
namespace SQLiteRepository namespace SQLiteRepository
@ -16,12 +18,14 @@ namespace SQLiteRepository
{ {
private readonly SQLiteConnection _connection; private readonly SQLiteConnection _connection;
private readonly ILogger _logger; private readonly ILogger _logger;
private string _path = "flexitimedb.db"; private readonly string _path;
private const string DATABASE_NAME = "flexitimedb.db";
private const string UPGRADE_SCRIPT_PREFIX = "SQLiteRepository.UpgradeScripts.";
public SQLiteRepository(ILogger logger) public SQLiteRepository(ILogger logger)
{ {
_path = _path =
new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), "flexitimedb.db")) new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), DATABASE_NAME))
.LocalPath; .LocalPath;
if (logger == null) throw new ArgumentNullException(nameof(logger)); if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger; _logger = logger;
@ -34,96 +38,20 @@ namespace SQLiteRepository
_connection.CreateTable<GroupDb>(); _connection.CreateTable<GroupDb>();
_connection.CreateTable<UserGroupJoinDb>(); _connection.CreateTable<UserGroupJoinDb>();
_connection.CreateTable<DbVersion>(); _connection.CreateTable<DbVersion>();
_connection.CreateTable<PolicyDb>();
_logger.Trace("Initialised SQLite Repository"); _logger.Trace("Initialised SQLite Repository");
_logger.Trace("Checking For Upgrades"); _logger.Trace("Checking For Upgrades");
CheckForDbUpgrade(); CheckForDbUpgrade();
} }
private void CheckForDbUpgrade() public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1, SortOptions sort = SortOptions.None)
{
var data = _connection.Query<DbVersion>("select * from DbVersion");
if (!data.Any())
{
//Pre-Upgrade database, need upgrading
_logger.Trace("Pre version 0.2 RC database found, performing update..");
ExecuteUpgradeFromVersion("0.1"); //execute 0.2 upgrade scripts onwards.
}
else
{
var installedVersion = new Version(data.First().VersionNumber);
var currentVersion = new Version(AssemblyInfo.ASSEMBLY_VERSION);
if (currentVersion.CompareTo(installedVersion) > 0) //greater than 0 - current version is newer
{
_logger.Trace("Installed Database Version: {0} is older than current version {1}");
ExecuteUpgradeFromVersion(installedVersion.ToString());
}
}
}
private void ExecuteUpgradeFromVersion(string installedVersion)
{
const string PREFIX = "SQLiteRepository.UpgradeScripts.";
var instVers = new Version(installedVersion);
//so we have established that each script for upgrade will be <version>.sql, so now start at lowest version and work up!
var assembly = Assembly.GetExecutingAssembly();
var upgradeScripts = assembly.GetManifestResourceNames()
.Where(x=>x.StartsWith(PREFIX))
.OrderBy(x=>new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, ""))))
.Where(x =>
{
var scriptVersion = new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, ""))).CompareTo(instVers);
return scriptVersion > 0;
}
)
.ToList();
//now have an ordered list of upgrade script files, so execute in order!
foreach (var upgradeScript in upgradeScripts)
{
using (var stream = assembly.GetManifestResourceStream(upgradeScript))
using(var str = new StreamReader(stream))
{
var script = str.ReadToEnd();
_logger.Trace("Executing upgrade script with name: {0}", upgradeScript);
_connection.Execute(script);
}
}
SetDbVersion(AssemblyInfo.ASSEMBLY_VERSION);
}
private void SetDbVersion(string vers)
{
_connection.DeleteAll<DbVersion>();
_connection.Insert(new DbVersion {VersionNumber = vers});
_logger.Trace("Set Database version to: {0}", vers);
}
public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1)
{ {
var ret = new UserList(); var ret = new UserList();
List<UserIdentity> users; List<UserIdentity> users = GetUserList(groupId, pageSize, pageNumber, sort);
int userCount;
if (pageNumber != -1 && pageSize != -1) var userCount = GetUserCount();
{
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS_PAGINATE,
pageSize, (pageNumber - 1) * pageSize);
userCount = _connection.ExecuteScalar<int>(SQLiteProcedures.GET_TOTAL_USER_COUNT);
}
else if (groupId != -1)
{
users =
_connection.Query<UserIdentity>(
SQLiteProcedures.GET_ALL_USERS_BY_GROUP,
groupId);
userCount = users.Count;
}
else
{
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS);
userCount = users.Count;
}
if (!users.Any())
{
if (pageNumber == -1 && pageSize == -1) if (pageNumber == -1 && pageSize == -1)
{ {
ret.PageNumber = 1; ret.PageNumber = 1;
@ -134,12 +62,15 @@ namespace SQLiteRepository
ret.PageNumber = pageNumber; ret.PageNumber = pageNumber;
ret.PageSize = pageSize; ret.PageSize = pageSize;
} }
if (!users.Any())
{
return ret; return ret;
} }
foreach (var user in users) foreach (var user in users)
{ {
var userObj = ChangeToUserObject(user); var userObj = UserConverter.ConvertToUserDto(user);
userObj.AssociatedIdentifiers = GetAssociatedIdentifiers(user.Id); userObj.AssociatedIdentifiers = GetAssociatedIdentifiers(user.Id);
userObj.State = GetUserState(GetLogDirection(user.Id)); userObj.State = GetUserState(GetLogDirection(user.Id));
@ -147,16 +78,7 @@ namespace SQLiteRepository
userObj.Groups = GetGroups(user.Id); userObj.Groups = GetGroups(user.Id);
ret.Users.Add(userObj); ret.Users.Add(userObj);
} }
if (pageNumber == -1 && pageSize == -1)
{
ret.PageSize = 10;
ret.PageNumber = 1;
}
else
{
ret.PageSize = pageSize;
ret.PageNumber = pageNumber;
}
ret.TotalUserCount = userCount; ret.TotalUserCount = userCount;
return ret; return ret;
} }
@ -180,26 +102,20 @@ namespace SQLiteRepository
foreach (var user in users) foreach (var user in users)
{ {
var userObj = ChangeToUserObject(user); var userObj = UserConverter.ConvertToUserDto(user);
var cards = _connection.Query<CardUniqueId>( var cards = _connection.Query<CardUniqueId>(
SQLiteProcedures.GET_CARDS_BY_USER_ID, SQLiteProcedures.GET_CARDS_BY_USER_ID,
user.Id); user.Id);
foreach (var card in cards) foreach (var card in cards)
{ {
userObj.AssociatedIdentifiers.Add(new Identifier() userObj.AssociatedIdentifiers.Add(IdentifierConverter.ConvertToIdentifierDto(card));
{
UniqueId = card.CardUId,
IsAssociatedToUser = true,
Id = card.Id
});
} }
userObj.State = GetUserState(GetLogDirection(user.Id)); userObj.State = GetUserState(GetLogDirection(user.Id));
ret.Users.Add(userObj); ret.Users.Add(userObj);
} }
//TODO: figure out paging here. - should there be any?
ret.PageSize = 20; ret.PageSize = 20;
ret.PageNumber = 1; ret.PageNumber = (int)Math.Ceiling((double)ret.Users.Count / (double)ret.PageSize);
return ret; return ret;
} }
@ -217,7 +133,7 @@ namespace SQLiteRepository
if (!users.Any()) return ret; if (!users.Any()) return ret;
var user = users.First(); var user = users.First();
ret = ChangeToUserObject(user); ret = UserConverter.ConvertToUserDto(user);
ret.Groups = GetGroups(); ret.Groups = GetGroups();
var usersGroups = GetGroups(user.Id); var usersGroups = GetGroups(user.Id);
foreach (var group in usersGroups) foreach (var group in usersGroups)
@ -230,7 +146,7 @@ namespace SQLiteRepository
foreach (var card in cards) foreach (var card in cards)
{ {
ret.AssociatedIdentifiers.Add(new Identifier { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id }); ret.AssociatedIdentifiers.Add(IdentifierConverter.ConvertToIdentifierDto(card));
} }
} }
catch (Exception ex) catch (Exception ex)
@ -298,86 +214,53 @@ namespace SQLiteRepository
foreach (var card in cardQuery) foreach (var card in cardQuery)
{ {
ret.data.Add(new Identifier ret.data.Add(IdentifierConverter.ConvertToIdentifierDto(card));
{ // new Identifier
Id = card.Id, //{
IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID, // Id = card.Id,
UniqueId = card.CardUId, // IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID,
LastUsed = card.LastUsed.DateTime // UniqueId = card.CardUId,
}); // LastUsed = card.LastUsed.DateTime
//});
} }
return ret; return ret;
} }
public void ClearUnassignedIdentifiers() public void ClearUnassignedIdentifiers()
{ {
var unassignedIdentifiers = _connection.Query<CardUniqueId>(SQLiteProcedures.GET_UNASSIGNED_CARD_LIST);
//remove the logs from the timelog db
_connection.Query<TimeLogDb>(
SQLiteProcedures.FormatInQuery(SQLiteProcedures.DELETE_TIMELOG_ENTRIES,
unassignedIdentifiers.Select(x => x.Id.ToString()).ToList()));
//now remove the unassigned identifiers/cards
_connection.Execute( _connection.Execute(
SQLiteProcedures.CLEAR_UNASSIGNED_CARDS, SQLiteProcedures.CLEAR_UNASSIGNED_CARDS,
Constants.UNASSIGNED_CARD_USER_ID); Constants.UNASSIGNED_CARD_USER_ID);
} }
//TODO: Check time logs table on update to ensure associated cards/unique identifiers are removed/added as appropriate.
public OperationResponse UpdateUser(User user, out int userIdResult) public OperationResponse UpdateUser(User user, out int userIdResult)
{ {
//if(user.UserId <=0) return OperationResponse.FAILED;
var ret = OperationResponse.NONE; var ret = OperationResponse.NONE;
var cardIds = new List<int>();
//Get a list of current associated identifiers, convert into a list of Identifier Objects.. //Get a list of current associated identifiers, convert into a list of Identifier Objects..
var currentCards = var currentCards =
_connection.Query<CardUniqueId>(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId) _connection.Query<CardUniqueId>(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId)
.Select(x => new Identifier { Id = x.Id, IsAssociatedToUser = true, UniqueId = x.CardUId }); .Select(IdentifierConverter.ConvertToIdentifierDto).ToList();
var cardsToRemove = currentCards.Except(user.AssociatedIdentifiers).Select(x => x.Id).ToList(); var cardsToRemove = currentCards.Exclude(user.AssociatedIdentifiers, x => x.Id).Select(x => x.Id).ToList();
#region GetUnique Identifiers
foreach (var card in user.AssociatedIdentifiers)
{
var existingCard = _connection.Query<CardUniqueId>(
SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, card.UniqueId);
if (!existingCard.Any())
{
var cardInsert = new CardUniqueId { CardUId = card.UniqueId, UserId_FK = -1 };
_connection.Insert(cardInsert);
cardIds.Add(cardInsert.Id);
if (ret < OperationResponse.CREATED)
ret = OperationResponse.CREATED; //only change it if my status supercedes.
}
else
{
cardIds.Add(existingCard.First().Id);
if (ret < OperationResponse.UPDATED)
ret = OperationResponse.UPDATED;
}
}
#endregion
#region Update/Create User #region Update/Create User
int userId; int userId;
if (user.UserId != -1) if (user.UserId != -1)
{ {
//edit.. UpdateUserDetails(user.FirstName, user.LastName, user.HoursPerWeek, user.IsContractor, user.UserId);
_connection.Query<UserIdentity>(
SQLiteProcedures.UPDATE_USER_DETAILS,
user.FirstName,
user.LastName,
user.HoursPerWeek,
user.IsContractor,
user.UserId
);
userId = user.UserId; userId = user.UserId;
} }
else else
{ {
var userInsert = new UserIdentity var userInsert = UserConverter.ConvertFromUserDto(user);
{
FirstName = user.FirstName,
LastName = user.LastName,
HoursPerWeek = user.HoursPerWeek,
IsContractor = user.IsContractor
};
_connection.Insert(userInsert); _connection.Insert(userInsert);
userId = userInsert.Id; userId = userInsert.Id;
if (ret < OperationResponse.CREATED) if (ret < OperationResponse.CREATED)
@ -385,19 +268,32 @@ namespace SQLiteRepository
} }
#endregion #endregion
#region Update Card/Unique Id entries. #region GetUnique Identifiers
foreach (var cardId in cardIds)
var existingCards = _connection.Query<CardUniqueId>(SQLiteProcedures.FormatInQuery(
SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID_LIST,
user.AssociatedIdentifiers.Select(x => x.UniqueId.ToString()).ToList()));
foreach (var card in user.AssociatedIdentifiers)
{ {
_connection.Query<CardUniqueId>( if (existingCards.All(x => x.CardUId != card.UniqueId))
SQLiteProcedures.UPDATE_CARD_USER_ID,
userId, cardId);
}
foreach (var card in cardsToRemove)
{ {
_connection.Query<CardUniqueId>( //this card is not currently in the system..
SQLiteProcedures.UPDATE_CARD_USER_ID, var cardInsert = IdentifierConverter.ConvertFromIdentifierDto(card, user.UserId);
-1, card); _connection.Insert(cardInsert);
existingCards.Add(cardInsert);
if (ret < OperationResponse.CREATED)
ret = OperationResponse.CREATED; //only change it if my status supercedes.
} }
}
#endregion
#region Update Card/Unique Id entries/associations
//make sure all identifiers are associated to the right user
UpdateIdentifierUserId(existingCards.Select(x => x.Id).ToList(), userId);
//make sure to remove all identifiers that have been deselected in edit are removed from user
UpdateIdentifierUserId(cardsToRemove, Constants.UNASSIGNED_CARD_USER_ID);
#endregion #endregion
#region Update Group Associations #region Update Group Associations
@ -410,8 +306,20 @@ namespace SQLiteRepository
return ret; return ret;
} }
public LogEventResponse LogEventTime(Identifier identifier, out int logId) public LogEventResponse LogEventTime(Identifier identifier, out int logId, DateTime logTime = default(DateTime))
{ {
#region Set the LogTime before we start querying anything.
if (logTime == default(DateTime))
{
logTime = DateTime.UtcNow;
_logger.Debug("Using own log time: {0}", logTime.ToString("o"));
}
else
{
_logger.Debug("Using supplied log time: {0}", logTime.ToString("o"));
}
#endregion
var ret = new LogEventResponse(); var ret = new LogEventResponse();
var cardIdQuery = _connection.Query<CardUniqueId>( var cardIdQuery = _connection.Query<CardUniqueId>(
SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID,
@ -428,15 +336,15 @@ namespace SQLiteRepository
UpdateIdentifierLastUsed(DateTimeOffset.UtcNow, ident.Id); UpdateIdentifierLastUsed(DateTimeOffset.UtcNow, ident.Id);
logId = -1; logId = -1;
//dont try to log any timelogs against this card, as it is unassigned to a user. //dont try to log any timelogs against this card, as it is unassigned to a user.
ret.ProcessResponse=OperationResponse.SUCCESS; ret.ProcessResponse = OperationResponse.SUCCESS;
ret.Direction = LogDirection.UNKNOWN; ret.Direction = LogDirection.UNKNOWN;
return ret; return ret;
} }
else
{
//TODO: log when more than one comes back. should NEVER happen but....
ident = cardIdQuery.First(); ident = cardIdQuery.First();
} _logger.Warn("More than 1 Identifier returned with ID {0}, card ids returned: {2}",
ident.CardUId,
string.Join(",", cardIdQuery.Select(x => x.CardUId).ToList()));
#endregion #endregion
// Get The User Direction (are they going in or out)? // Get The User Direction (are they going in or out)?
@ -465,7 +373,7 @@ namespace SQLiteRepository
#endregion #endregion
#region Get the current time (for swiping). and calendar week/year to help recall the data. #region Get the current time (for swiping). and calendar week/year to help recall the data.
var logTime = DateTime.UtcNow;
var calendarWeek = GetIso8601CalendarWeek(logTime); var calendarWeek = GetIso8601CalendarWeek(logTime);
var year = logTime.Year; var year = logTime.Year;
#endregion #endregion
@ -490,10 +398,14 @@ namespace SQLiteRepository
return ret; return ret;
} }
/*Groups*/
//TODO: check group name can only be entered once.
public OperationResponse CreateGroup(Group group, out int groupId) public OperationResponse CreateGroup(Group group, out int groupId)
{ {
var query = _connection.Query<GroupDb>(SQLiteProcedures.GET_GROUP_BY_NAME, group.Name);
if (query.Any())
{
groupId = query[0].GroupId;
return OperationResponse.NONE;
}
var groupDb = new GroupDb { GroupName = group.Name }; var groupDb = new GroupDb { GroupName = group.Name };
var resp = _connection.Insert(groupDb); var resp = _connection.Insert(groupDb);
groupId = groupDb.GroupId; groupId = groupDb.GroupId;
@ -506,22 +418,12 @@ namespace SQLiteRepository
List<GroupDb> query; List<GroupDb> query;
if (userId == -1) if (userId == -1)
{ {
query = _connection.Query<GroupDb>("select gp.GroupId, gp.GroupName, " + query = _connection.Query<GroupDb>(SQLiteProcedures.GET_GROUPS);
"sum(case when gp.GroupId = ujdb.GroupId_FK then 1 else 0 end) as AssignedUserCount " +
"from GroupDb gp " +
"left join UserGroupJoinDb ujdb " +
"on ujdb.GroupId_FK = gp.GroupId " +
"group by gp.GroupId");
} }
else else
{ {
query = query =
_connection.Query<GroupDb>( _connection.Query<GroupDb>(SQLiteProcedures.GET_GROUPS_FOR_USER,
"select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount" +
" from GroupDb gdb" +
" left join UserGroupJoinDb ujdb" +
" on gdb.GroupId = ujdb.GroupId_FK" +
" where ujdb.UserId_FK = ?",
userId); userId);
} }
foreach (var group in query) foreach (var group in query)
@ -538,7 +440,7 @@ namespace SQLiteRepository
public Group GetGroup(int groupId) public Group GetGroup(int groupId)
{ {
var query = _connection.Query<GroupDb>("select * from GroupDb where GroupId = ?", groupId); var query = _connection.Query<GroupDb>(SQLiteProcedures.GET_GROUP_BY_ID, groupId);
if (query.Any()) if (query.Any())
{ {
var group = query.First(); var group = query.First();
@ -554,8 +456,7 @@ namespace SQLiteRepository
public OperationResponse UpdateGroup(Group group) public OperationResponse UpdateGroup(Group group)
{ {
//TODO: I would probably prefer to do this manually.... _connection.Query<GroupDb>(SQLiteProcedures.UPDATE_GROUP, @group.Name, @group.Id);
var resp = _connection.Query<GroupDb>("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id);
return OperationResponse.UPDATED; return OperationResponse.UPDATED;
} }
@ -569,34 +470,26 @@ namespace SQLiteRepository
public OperationResponse DeleteLog(TimeLog log) public OperationResponse DeleteLog(TimeLog log)
{ {
var query = _connection.Query<TimeLogDb>( var query = _connection.Query<TimeLogDb>(
"select * from TimeLogDb where Id=?", log.Id); SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id);
if (!query.Any()) if (!query.Any())
return OperationResponse.FAILED; return OperationResponse.FAILED;
UpdateExistingLogDirections(log); UpdateExistingLogDirections(log);
_connection.ExecuteScalar<TimeLogDb>("delete from TimeLogDb where Id=?", log.Id); _connection.ExecuteScalar<TimeLogDb>(SQLiteProcedures.DELETE_TIMELOG_ENTRY, log.Id);
return OperationResponse.DELETED; return OperationResponse.DELETED;
} }
public OperationResponse CreateLog(TimeLog log) public OperationResponse CreateLog(TimeLog log)
{ {
var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
var year = log.EventTime.Year; log.Year = log.EventTime.Year;
log.CalendarWeek = calendarWeek;
log.Year = year; var dbLog = TimeLogConverter.ConvertFromTimeLogDto(log);
var dbLog = new TimeLogDb dbLog.IdentifierId = ConvertSourceToIdentifierId(log.Source);
{
SwipeEventDateTime = log.EventTime,
Direction = (LogDirectionDb)(int)log.Direction,
Year = year,
CalendarWeek = calendarWeek,
Source = (LogSourceDb)(int)log.Source,
UserId_FK = log.UserId,
IdentifierId = ConvertSourceToIdentifierId(log.Source),
};
#region update in/out directions for manual logs. #region update in/out directions for manual logs.
UpdateExistingLogDirections(log); UpdateExistingLogDirections(log);
#endregion #endregion
@ -608,8 +501,9 @@ namespace SQLiteRepository
public OperationResponse UpdateLog(TimeLog log) public OperationResponse UpdateLog(TimeLog log)
{ {
var query = _connection.Query<TimeLogDb>( var query = _connection.Query<TimeLogDb>(
"select * from TimeLogDb where Id=?", log.Id); SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id);
if(!query.Any())
if (!query.Any())
return OperationResponse.FAILED; return OperationResponse.FAILED;
if (log.CalendarWeek > 52 || log.CalendarWeek < 1) if (log.CalendarWeek > 52 || log.CalendarWeek < 1)
@ -621,13 +515,160 @@ namespace SQLiteRepository
log.Year = log.EventTime.Year; log.Year = log.EventTime.Year;
} }
_connection.ExecuteScalar<TimeLogDb>( _connection.ExecuteScalar<TimeLogDb>(
"update TimeLogDb set UserId_FK=?, Direction=?,SwipeEventDateTime=?,CalendarWeek=?,Year=?,Source=? where Id=?", SQLiteProcedures.UPDATE_TIMELOG_ENTRY,
log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year, log.UserId, (LogDirectionDb)(int)log.Direction, log.EventTime, log.CalendarWeek, log.Year,
(LogSourceDb) (int) log.Source, log.Id); (LogSourceDb)(int)log.Source, log.Id);
return OperationResponse.UPDATED; return OperationResponse.UPDATED;
} }
public Policy GetPolicy()
{
var query = $"select * from {nameof(PolicyDb)}";
var policies = _connection.Query<PolicyDb>(query);
return PolicyConverter.ConvertToPolicyDto(policies.OrderByDescending(x => x.Version).FirstOrDefault());
}
public void SavePolicy(Policy policy)
{
if (string.IsNullOrEmpty(policy.Version))
{
policy.Version = (GetNextPolicyVersion() + 1).ToString();
}
var policyDb = PolicyConverter.ConvertFromPolicyDto(policy);
_connection.Insert(policyDb);
}
private int GetNextPolicyVersion()
{
var query = $"select Max({nameof(PolicyDb.Id)}) from {nameof(PolicyDb)}";
var id = _connection.ExecuteScalar<int>(query);
return id;
}
private int GetUserCount()
{
return _connection.ExecuteScalar<int>(SQLiteProcedures.GET_TOTAL_USER_COUNT);
}
private List<UserIdentity> GetUserList(int groupId = -1, int pageSize = -1, int pageNumber = -1, SortOptions sort = SortOptions.None)
{
List<UserIdentity> users;
var orderByFirst = nameof(UserIdentity.LastName);
var firstOrderDir = "ASC";
var orderBySecond = nameof(UserIdentity.FirstName);
var secondOrderDir = "ASC";
if (sort != SortOptions.None)
{
switch (sort)
{
case SortOptions.FirstNameAscending:
orderByFirst = nameof(UserIdentity.FirstName);
firstOrderDir = "ASC";
orderBySecond = nameof(UserIdentity.LastName);
secondOrderDir = "ASC";
break;
case SortOptions.FirstNameDescending:
orderByFirst = nameof(UserIdentity.FirstName);
firstOrderDir = "DESC";
orderBySecond = nameof(UserIdentity.LastName);
secondOrderDir = "ASC";
break;
case SortOptions.LastNameDescending:
firstOrderDir = "DESC";
break;
}
}
if (pageNumber != -1 && pageSize != -1)
{
var qString = string.Format(SQLiteProcedures.GET_ALL_USERS_PAGINATE, orderByFirst, firstOrderDir,
orderBySecond, secondOrderDir, pageSize, (pageNumber - 1) * pageSize);
users = _connection.Query<UserIdentity>(qString);
}
else if (groupId != -1)
{
var qString = string.Format(SQLiteProcedures.GET_ALL_USERS_BY_GROUP, groupId,
orderByFirst, firstOrderDir,
orderBySecond, secondOrderDir);
users =
_connection.Query<UserIdentity>(qString);
}
else
{
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS,
orderByFirst, firstOrderDir,
orderBySecond, secondOrderDir);
}
return users;
}
private void CheckForDbUpgrade()
{
var data = _connection.Query<DbVersion>(SQLiteProcedures.GET_DB_VERSION);
if (!data.Any())
{
//Pre-Upgrade database, need upgrading
_logger.Trace("Pre version 0.2 RC database found, performing update..");
ExecuteUpgradeFromVersion("0.1"); //execute 0.2 upgrade scripts onwards.
}
else
{
var installedVersion = new Version(data.First().VersionNumber);
var currentVersion = new Version(AssemblyInfo.ASSEMBLY_VERSION);
if (currentVersion.CompareTo(installedVersion) > 0) //greater than 0 - current version is newer
{
_logger.Trace("Installed Database Version: {0} is older than current version {1}");
ExecuteUpgradeFromVersion(installedVersion.ToString());
}
}
}
private void ExecuteUpgradeFromVersion(string installedVersion)
{
var instVers = new Version(installedVersion);
//so we have established that each script for upgrade will be <version>.sql, so now start at lowest version and work up!
var assembly = Assembly.GetExecutingAssembly();
var upgradeScripts = assembly.GetManifestResourceNames()
.Where(x => x.StartsWith(UPGRADE_SCRIPT_PREFIX))
.OrderBy(x =>
new Version(Path.GetFileNameWithoutExtension(x.Replace(UPGRADE_SCRIPT_PREFIX, string.Empty))))
.Where(x =>
{
var scriptVersion =
new Version(Path.GetFileNameWithoutExtension(x.Replace(UPGRADE_SCRIPT_PREFIX, string.Empty)))
.CompareTo(instVers);
return scriptVersion > 0;
})
.ToList();
//now have an ordered list of upgrade script files, so execute in order!
foreach (var upgradeScript in upgradeScripts)
{
using (var stream = assembly.GetManifestResourceStream(upgradeScript))
using (var str = new StreamReader(stream))
{
var script = str.ReadToEnd();
_logger.Trace("Executing upgrade script with name: {0}", upgradeScript);
_connection.Execute(script);
}
}
SetDbVersion(AssemblyInfo.ASSEMBLY_VERSION);
}
private void SetDbVersion(string vers)
{
_connection.DeleteAll<DbVersion>();
_connection.Insert(new DbVersion { VersionNumber = vers });
_logger.Trace("Set Database version to: {0}", vers);
}
private DateTime GetLastLogDateTime(TimeLogDb timeLog) private DateTime GetLastLogDateTime(TimeLogDb timeLog)
{ {
if (timeLog != null) if (timeLog != null)
@ -705,7 +746,7 @@ namespace SQLiteRepository
EventTime = x.SwipeEventDateTime, EventTime = x.SwipeEventDateTime,
UserId = x.UserId_FK, UserId = x.UserId_FK,
Year = x.Year Year = x.Year
}).OrderBy(x=>x.EventTime.UtcDateTime).ToList(); }).OrderBy(x => x.EventTime.UtcDateTime).ToList();
var dict = new Dictionary<DayOfWeek, DailyLogs>(); var dict = new Dictionary<DayOfWeek, DailyLogs>();
var logList = new List<DailyLogs>(); var logList = new List<DailyLogs>();
@ -717,7 +758,7 @@ namespace SQLiteRepository
} }
//add the logs to the respective day of the week. //add the logs to the respective day of the week.
foreach (var log in timeLogs.OrderBy(x=>x.EventTime)) foreach (var log in timeLogs.OrderBy(x => x.EventTime))
{ {
dict[log.EventTime.DayOfWeek].Logs.Add(log); dict[log.EventTime.DayOfWeek].Logs.Add(log);
} }
@ -797,7 +838,7 @@ namespace SQLiteRepository
else else
{ {
// we have a time log from today already, so just do the opposite of what we last did! // we have a time log from today already, so just do the opposite of what we last did!
logDirection = InvertLogDirectionDb(lastEntry.Direction); logDirection = LogDirectionConverter.InvertLogDirectionDb(lastEntry.Direction);
} }
} }
else else
@ -823,7 +864,7 @@ namespace SQLiteRepository
private void UpdateIdentifierLastUsed(DateTimeOffset dt, int cardId) private void UpdateIdentifierLastUsed(DateTimeOffset dt, int cardId)
{ {
var res = _connection.ExecuteScalar<CardUniqueId>(SQLiteProcedures.UPDATE_CARD_LAST_USED, _connection.ExecuteScalar<CardUniqueId>(SQLiteProcedures.UPDATE_CARD_LAST_USED,
dt, dt,
cardId); cardId);
} }
@ -872,18 +913,6 @@ namespace SQLiteRepository
return dt.Date.CompareTo(DateTime.Today.Date) < 0; return dt.Date.CompareTo(DateTime.Today.Date) < 0;
} }
private User ChangeToUserObject(UserIdentity user)
{
return new User
{
UserId = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
HoursPerWeek = user.HoursPerWeek,
IsContractor = user.IsContractor
};
}
private void UpdateExistingLogDirections(TimeLog log) private void UpdateExistingLogDirections(TimeLog log)
{ {
//need to make this generic so that both create and delete will update the log directions.. but ARGH. //need to make this generic so that both create and delete will update the log directions.. but ARGH.
@ -901,29 +930,34 @@ namespace SQLiteRepository
var currentlogDirection = log.Direction; var currentlogDirection = log.Direction;
for (var i = 0; i < logs.Count; i++) for (var i = 0; i < logs.Count; i++)
{ {
logs[i].Direction = InvertLogDirection(currentlogDirection); logs[i].Direction = LogDirectionConverter.InvertLogDirection(currentlogDirection);
UpdateLog(logs[i]); UpdateLog(logs[i]);
currentlogDirection = logs[i].Direction; currentlogDirection = logs[i].Direction;
} }
} }
} }
private LogDirectionDb InvertLogDirectionDb(LogDirectionDb direction) private void UpdateIdentifierUserId(List<int> cardsToUpdate, int userId)
{ {
return (LogDirectionDb) (int) InvertLogDirection((LogDirection) (int) direction); foreach (var card in cardsToUpdate)
{
_connection.Query<CardUniqueId>(
SQLiteProcedures.UPDATE_CARD_USER_ID,
userId, card);
}
} }
private LogDirection InvertLogDirection(LogDirection direction) private void UpdateUserDetails(string firstName, string lastName, float hoursPerWeek, bool isContractor,
int userId)
{ {
switch (direction) _connection.Query<UserIdentity>(
{ SQLiteProcedures.UPDATE_USER_DETAILS,
case LogDirection.IN: firstName,
return LogDirection.OUT; lastName,
case LogDirection.OUT: hoursPerWeek,
return LogDirection.IN; isContractor,
default: userId
return LogDirection.UNKNOWN; );
}
} }
} }
} }

View File

@ -78,9 +78,17 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CardUniqueId.cs" /> <Compile Include="CardUniqueId.cs" />
<Compile Include="Converters\IdentifierConverter.cs" />
<Compile Include="Converters\LogDirectionConverter.cs" />
<Compile Include="Converters\LogSourceConverter.cs" />
<Compile Include="Converters\PolicyConverter.cs" />
<Compile Include="Converters\TimeLogConverter.cs" />
<Compile Include="Converters\UserConverter.cs" />
<Compile Include="DBVersion.cs" /> <Compile Include="DBVersion.cs" />
<Compile Include="Extensions\Extensions.cs" />
<Compile Include="GroupDb.cs" /> <Compile Include="GroupDb.cs" />
<Compile Include="LogSourceDb.cs" /> <Compile Include="LogSourceDb.cs" />
<Compile Include="PolicyDb.cs" />
<Compile Include="SQLiteRepository.cs" /> <Compile Include="SQLiteRepository.cs" />
<Compile Include="Constants.cs" /> <Compile Include="Constants.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@ -101,10 +109,10 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <EmbeddedResource Include="UpgradeScripts\0.2.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UpgradeScripts\0.2.sql" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" /> <Import Project="..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" />

View File

@ -1 +1,3 @@
insert into GroupDb values ((select max(groupId) from GroupDb)+1,'Archived',0) insert into GroupDb(GroupId, GroupName, AssignedUserCount)
select (select max(GroupId) from GroupDb)+1, 'Archived', 0
WHERE NOT EXISTS(select * from GroupDb where GroupName='Archived');

View File

@ -3,9 +3,9 @@
<appSettings> <appSettings>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" /> <add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DefaultPageSize" value="20" /> <add key="DefaultPageSize" value="20" />
<add key="WebsiteHttpPort" value="8800"/> <add key="WebsiteHttpPort" value="8800" />
<add key="SwipeTimeGap" value="3" /> <add key="SwipeTimeGap" value="3" />
<add key="BugSubmissionEmailAddress" value="incoming+WattsC/FlexiTimeTrackerTool@incoming.gitlab.com"/> <add key="BugSubmissionEmailAddress" value="incoming+WattsC/FlexiTimeTrackerTool+24qrefn8e1urhl4iqct7we2jl@gitlab.com" />
</appSettings> </appSettings>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
@ -18,7 +18,7 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" /> <bindingRedirect oldVersion="0.0.0.0-3.1.0.0" newVersion="3.1.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
@ -36,6 +36,18 @@
<assemblyIdentity name="System.Web.Http.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.Web.Http.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Ninject" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.4.0" newVersion="3.3.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Ninject.Web.Common" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.1.0" newVersion="3.3.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Ninject.Web.Common.OwinHost" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.1.0" newVersion="3.3.1.0" />
</dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>

View File

@ -0,0 +1,66 @@
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(WindowsDataCenter.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(WindowsDataCenter.App_Start.NinjectWebCommon), "Stop")]
namespace WindowsDataCenter.App_Start
{
using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Ninject;
using Ninject.Web.Common;
using Ninject.Web.Common.WebHost;
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
}
}
}

View File

@ -1,7 +1,10 @@
namespace WindowsDataCenter using System;
namespace WindowsDataCenter
{ {
public class CardData public class CardData
{ {
public DateTime? UtcTimeStamp { get; set; }
public string CardUId { get; set; } public string CardUId { get; set; }
} }
} }

View File

@ -1,5 +1,6 @@
using System.Reflection; using System.Reflection;
using System.Web.Http; using System.Web.Http;
using WindowsDataCenter.Helpers;
using Interfaces; using Interfaces;
namespace WindowsDataCenter namespace WindowsDataCenter
@ -7,6 +8,12 @@ namespace WindowsDataCenter
[RoutePrefix("api/app")] [RoutePrefix("api/app")]
public class ApplicationController:ApiController public class ApplicationController:ApiController
{ {
private IRepository _repo;
public ApplicationController(IRepository repo)
{
_repo = repo;
}
[Route("")] [Route("")]
public IHttpActionResult GetAppDetails() public IHttpActionResult GetAppDetails()
{ {
@ -22,5 +29,23 @@ namespace WindowsDataCenter
return Ok(appDetails); return Ok(appDetails);
} }
[Route("policy")]
[HttpGet]
[CacheControl(MaxAge = 0)]
public IHttpActionResult GetPolicy()
{
Policy policy = _repo.GetPolicy();
return Json(policy);
}
[Route("policy")]
[HttpPost]
[CacheControl(MaxAge = 0)]
public IHttpActionResult SavePolicy(Policy policy)
{
_repo.SavePolicy(policy);
return Ok(new {}); //empty to ensure ajax triggers Success..
}
} }
} }

View File

@ -38,7 +38,13 @@ namespace WindowsDataCenter
public IHttpActionResult PostData([FromBody] CardData cData) public IHttpActionResult PostData([FromBody] CardData cData)
{ {
int logId; int logId;
var resp = _repo.LogEventTime(new Identifier {UniqueId = cData.CardUId}, out logId);
var id = new Identifier {UniqueId = cData.CardUId};
var resp = cData.UtcTimeStamp.HasValue
? _repo.LogEventTime(id, out logId, cData.UtcTimeStamp.Value)
: _repo.LogEventTime(id, out logId);
_logger.Trace("Received new \"Swipe Event\" for UId: {0} at {1}, direction is : {2}", cData.CardUId, _logger.Trace("Received new \"Swipe Event\" for UId: {0} at {1}, direction is : {2}", cData.CardUId,
DateTime.UtcNow, resp.Direction); DateTime.UtcNow, resp.Direction);
return Ok(new {Id = logId, resp.Direction}); return Ok(new {Id = logId, resp.Direction});

View File

@ -30,17 +30,18 @@ namespace WindowsDataCenter
public IHttpActionResult GetUsers([FromUri] string query = "" public IHttpActionResult GetUsers([FromUri] string query = ""
, [FromUri] int pageSize = -1 , [FromUri] int pageSize = -1
, [FromUri] int pageNumber = -1 , [FromUri] int pageNumber = -1
, [FromUri] int groupId = -1) , [FromUri] int groupId = -1
, [FromUri] SortOptions sort = SortOptions.None)
{ {
_logger.Trace("GetUsers called with arguments >> query: {0}, pageSize: {1}, pageNumber: {2}, groupFilter: {3}", query, pageSize, pageNumber, groupId); _logger.Trace("GetUsers called with arguments >> query: {0}, pageSize: {1}, pageNumber: {2}, groupFilter: {3}, sort: {4}", query, pageSize, pageNumber, groupId, sort);
pageNumber = pageNumber == -1 ? 1 : pageNumber; pageNumber = pageNumber == -1 ? 1 : pageNumber;
pageSize = GetPageSize(pageSize); pageSize = GetPageSize(pageSize);
var userList = query != string.Empty var userList = query != string.Empty
? _repo.Search(query) ? _repo.Search(query)
: groupId != -1 : groupId != -1
? _repo.GetUsers(groupId: groupId) ? _repo.GetUsers(groupId: groupId, sort:sort)
: _repo.GetUsers(pageNumber, pageSize); : _repo.GetUsers(pageNumber, pageSize, sort:sort);
_logger.Trace("Got UserList from Repository, UserCount: {0}", userList.UserCount); _logger.Trace("Got UserList from Repository, UserCount: {0}", userList.UserCount);
if (query != string.Empty) if (query != string.Empty)
{ {
@ -57,6 +58,7 @@ namespace WindowsDataCenter
userList.PageNumber = pageNumber; userList.PageNumber = pageNumber;
userList.PageSize = pageSize; userList.PageSize = pageSize;
userList.SelectedSortOption = sort;
_logger.Trace("Returning UserList from GetUsers."); _logger.Trace("Returning UserList from GetUsers.");
var msg = Request.CreateResponse(HttpStatusCode.OK, userList); var msg = Request.CreateResponse(HttpStatusCode.OK, userList);
@ -93,8 +95,7 @@ namespace WindowsDataCenter
{ {
int userId; int userId;
_repo.UpdateUser(user, out userId); _repo.UpdateUser(user, out userId);
//TODO return ID. return ResponseMessage(new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent(userId.ToString())});
return ResponseMessage(new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("unknownIdTODO")});
} }
[HttpPost] [HttpPost]

View File

@ -55,14 +55,19 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<DocumentationFile>bin\ReleaseInstallers\WindowsDataCenter.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.2.0\lib\net45\Castle.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="DalSoft.WebApi.HelpPage, Version=0.0.7.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="DalSoft.WebApi.HelpPage, Version=0.0.7.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DalSoft.WebApi.HelpPage.0.0.7.0\lib\net451\DalSoft.WebApi.HelpPage.dll</HintPath> <HintPath>..\packages\DalSoft.WebApi.HelpPage.0.0.7.0\lib\net451\DalSoft.WebApi.HelpPage.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.3.1.0\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Microsoft.Owin.FileSystems, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Owin.FileSystems, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@ -89,36 +94,44 @@
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject, Version=3.3.4.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll</HintPath> <HintPath>..\packages\Ninject.3.3.4\lib\net45\Ninject.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Extensions.ContextPreservation, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Extensions.ContextPreservation, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.ContextPreservation.3.2.0.0\lib\net45-full\Ninject.Extensions.ContextPreservation.dll</HintPath> <HintPath>..\packages\Ninject.Extensions.ContextPreservation.3.3.1\lib\net45\Ninject.Extensions.ContextPreservation.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Extensions.NamedScope, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Extensions.Factory, Version=3.3.2.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.NamedScope.3.2.0.0\lib\net45-full\Ninject.Extensions.NamedScope.dll</HintPath> <HintPath>..\packages\Ninject.Extensions.Factory.3.3.2\lib\net45\Ninject.Extensions.Factory.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Extensions.Xml, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Extensions.NamedScope, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.Xml.3.2.0.0\lib\net45-full\Ninject.Extensions.Xml.dll</HintPath> <HintPath>..\packages\Ninject.Extensions.NamedScope.3.3.0\lib\net45\Ninject.Extensions.NamedScope.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Web.Common, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Extensions.Xml, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.Common.3.2.3.0\lib\net45-full\Ninject.Web.Common.dll</HintPath> <HintPath>..\packages\Ninject.Extensions.Xml.3.3.0\lib\net45\Ninject.Extensions.Xml.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Web.Common.OwinHost, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Web.Common, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.Common.OwinHost.3.2.3.0\lib\net45-full\Ninject.Web.Common.OwinHost.dll</HintPath> <HintPath>..\packages\Ninject.Web.Common.3.3.1\lib\net45\Ninject.Web.Common.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Web.WebApi, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Web.Common.OwinHost, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.WebApi.3.2.4.0\lib\net45-full\Ninject.Web.WebApi.dll</HintPath> <HintPath>..\packages\Ninject.Web.Common.OwinHost.3.3.1\lib\net45\Ninject.Web.Common.OwinHost.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ninject.Web.WebApi.OwinHost, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL"> <Reference Include="Ninject.Web.Common.WebHost, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.WebApi.OwinHost.3.2.4.0\lib\net45-full\Ninject.Web.WebApi.OwinHost.dll</HintPath> <HintPath>..\packages\Ninject.Web.Common.WebHost.3.3.1\lib\net45\Ninject.Web.Common.WebHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Web.WebApi, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.WebApi.3.3.0\lib\net45\Ninject.Web.WebApi.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Web.WebApi.OwinHost, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.WebApi.OwinHost.3.3.0\lib\net45\Ninject.Web.WebApi.OwinHost.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL"> <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
@ -154,6 +167,10 @@
<HintPath>..\packages\Microsoft.AspNet.WebApi.WebHost.5.0.0\lib\net45\System.Web.Http.WebHost.dll</HintPath> <HintPath>..\packages\Microsoft.AspNet.WebApi.WebHost.5.0.0\lib\net45\System.Web.Http.WebHost.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.Razor.3.0.0\lib\net45\System.Web.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -162,11 +179,12 @@
<Reference Include="System.ServiceProcess" /> <Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="WebActivatorEx, Version=2.0.0.0, Culture=neutral, PublicKeyToken=7b26dc2a43f6a0d4, processorArchitecture=MSIL"> <Reference Include="WebActivatorEx, Version=2.0.0.0, Culture=neutral, PublicKeyToken=7b26dc2a43f6a0d4, processorArchitecture=MSIL">
<HintPath>..\packages\WebActivatorEx.2.0\lib\net40\WebActivatorEx.dll</HintPath> <HintPath>..\packages\WebActivatorEx.2.2.0\lib\net40\WebActivatorEx.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="App_Start\Ninject.Web.Common.cs" />
<Compile Include="CardData.cs" /> <Compile Include="CardData.cs" />
<Compile Include="Controllers\ApplicationController.cs" /> <Compile Include="Controllers\ApplicationController.cs" />
<Compile Include="Controllers\CardsController.cs" /> <Compile Include="Controllers\CardsController.cs" />
@ -196,6 +214,9 @@
<Compile Include="Controllers\ValuesController.cs" /> <Compile Include="Controllers\ValuesController.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Help\DalSoft.WebApi.HelpPage.Views\HelpPage.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Minifier\MinifierConfig.xml" /> <Content Include="Minifier\MinifierConfig.xml" />
<Content Include="NinjectConfig.xml"> <Content Include="NinjectConfig.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
@ -221,6 +242,9 @@
<Content Include="www\css\bootstrap.min.css"> <Content Include="www\css\bootstrap.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="www\css\highlightjs.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\css\knockout.contextmenu.css"> <Content Include="www\css\knockout.contextmenu.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
@ -245,9 +269,27 @@
<Content Include="www\js\bootstrap.min.js"> <Content Include="www\js\bootstrap.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="www\css\easymde.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\js\easymde.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\js\highlightjs.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\js\htmlparser.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\js\knockout.contextmenu.js"> <Content Include="www\js\knockout.contextmenu.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="www\js\marked.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\js\PolicyObject.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="www\spa.css"> <Content Include="www\spa.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
@ -309,9 +351,6 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Help\DalSoft.WebApi.HelpPage.Views\HelpPage.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="sqlite3.def" /> <Content Include="sqlite3.def" />
<Content Include="sqlite3.dll" /> <Content Include="sqlite3.dll" />
</ItemGroup> </ItemGroup>
@ -330,6 +369,9 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>call "$(ProjectDir)Minifier\Minify.bat" "$(SolutionDir)"</PreBuildEvent>
</PropertyGroup>
<Import Project="..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" /> <Import Project="..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
@ -337,9 +379,6 @@
</PropertyGroup> </PropertyGroup>
<Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets'))" /> <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets'))" />
</Target> </Target>
<PropertyGroup>
<PreBuildEvent>call "$(ProjectDir)Minifier\Minify.bat" "$(SolutionDir)"</PreBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Castle.Core" version="4.2.0" targetFramework="net452" />
<package id="DalSoft.WebApi.HelpPage" version="0.0.7.0" targetFramework="net452" /> <package id="DalSoft.WebApi.HelpPage" version="0.0.7.0" targetFramework="net452" />
<package id="Microsoft.AspNet.Razor" version="3.0.0" targetFramework="net452" /> <package id="Microsoft.AspNet.Razor" version="3.0.0" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi" version="5.0.0" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi" version="5.0.0" targetFramework="net452" />
@ -8,23 +9,25 @@
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.0.0" targetFramework="net452" /> <package id="Microsoft.AspNet.WebApi.WebHost" version="5.0.0" targetFramework="net452" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin" version="3.1.0" targetFramework="net452" />
<package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net452" /> <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="Ninject" version="3.2.2.0" targetFramework="net452" /> <package id="Ninject" version="3.3.4" targetFramework="net452" />
<package id="Ninject.Extensions.ContextPreservation" version="3.2.0.0" targetFramework="net452" /> <package id="Ninject.Extensions.ContextPreservation" version="3.3.1" targetFramework="net452" />
<package id="Ninject.Extensions.NamedScope" version="3.2.0.0" targetFramework="net452" /> <package id="Ninject.Extensions.Factory" version="3.3.2" targetFramework="net452" />
<package id="Ninject.Extensions.Xml" version="3.2.0.0" targetFramework="net452" /> <package id="Ninject.Extensions.NamedScope" version="3.3.0" targetFramework="net452" />
<package id="Ninject.Web.Common" version="3.2.3.0" targetFramework="net452" /> <package id="Ninject.Extensions.Xml" version="3.3.0" targetFramework="net452" />
<package id="Ninject.Web.Common.OwinHost" version="3.2.3.0" targetFramework="net452" /> <package id="Ninject.Web.Common" version="3.3.1" targetFramework="net452" />
<package id="Ninject.Web.WebApi" version="3.2.4.0" targetFramework="net452" /> <package id="Ninject.Web.Common.OwinHost" version="3.3.1" targetFramework="net452" />
<package id="Ninject.Web.WebApi.OwinHost" version="3.2.4.0" targetFramework="net452" /> <package id="Ninject.Web.Common.WebHost" version="3.3.1" targetFramework="net452" />
<package id="Ninject.Web.WebApi" version="3.3.0" targetFramework="net452" />
<package id="Ninject.Web.WebApi.OwinHost" version="3.3.0" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" /> <package id="Owin" version="1.0" targetFramework="net452" />
<package id="RazorEngine" version="3.7.2" targetFramework="net452" /> <package id="RazorEngine" version="3.7.2" targetFramework="net452" />
<package id="System.Data.SQLite.Core" version="1.0.104.0" targetFramework="net452" /> <package id="System.Data.SQLite.Core" version="1.0.104.0" targetFramework="net452" />
<package id="WebActivatorEx" version="2.0" targetFramework="net452" /> <package id="WebActivatorEx" version="2.2.0" targetFramework="net452" />
</packages> </packages>

View File

@ -9,8 +9,11 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/momentjs/2.10.6/moment.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sammy.js/0.7.6/sammy.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/sammy.js/0.7.6/sammy.js" type="text/javascript"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="css/easymde.min.css" />
<link rel="stylesheet" href="css/highlightjs.min.css" />
</head> </head>
<body> <body>
<nav class="navbar navbar-default"> <nav class="navbar navbar-default">
@ -37,14 +40,14 @@
</div> </div>
</div> </div>
</nav> </nav>
<div id="GroupAdminPage" class="container"> <div id="GroupAdminPage" class="container">
<div class="row"> <div class="row">
<h2 class="col-md-4">Groups</h2> <h2 class="col-md-4">Groups</h2>
<button class="col-md-1 btn btn-default pull-right" style="margin-top: 20px;" data-bind="click: $root.newGroupForm"> <button class="col-md-1 btn btn-default pull-right" style="margin-top: 20px;" data-bind="click: $root.newGroupForm">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
</button> </button>
</div> </div>
<hr/> <hr />
<div class="row"> <div class="row">
<div data-bind="with: groupsList, css:{'col-md-8': !$root.groupFormHidden(), 'col-md-12':$root.groupFormHidden()}"> <div data-bind="with: groupsList, css:{'col-md-8': !$root.groupFormHidden(), 'col-md-12':$root.groupFormHidden()}">
<table class="table table-striped"> <table class="table table-striped">
@ -68,15 +71,17 @@
<input type="hidden" name="id" data-bind="value: Id"> <input type="hidden" name="id" data-bind="value: Id">
<div class="form-group"> <div class="form-group">
<label for="groupNameEdit">Group Name</label> <label for="groupNameEdit">Group Name</label>
<input for="Name" type="text" class="form-control" id="groupNameEdit" placeholder="Group Name" data-bind="value: Name"/> <input for="Name" type="text" class="form-control" id="groupNameEdit" placeholder="Group Name" data-bind="value: Name" />
</div> </div>
<button pageDestination="Users" class="btn btn-secondary" type="button" <button pageDestination="Users" class="btn btn-secondary" type="button"
data-bind="click: $root.hideGroupForm">Cancel</button> data-bind="click: $root.hideGroupForm">
Cancel
</button>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
</form> </form>
</div> </div>
</div> </div>
<div id="CardManagement" class="container"> <div id="CardManagement" class="container">
<div class="row"> <div class="row">
<h2 class="col-md-4">Unassigned Cards</h2> <h2 class="col-md-4">Unassigned Cards</h2>
@ -103,7 +108,64 @@
</div> </div>
</div> </div>
</div> </div>
<div id="PolicyManagement" class="container">
<div id="row">
<ul class="nav nav-tabs">
<li class="active"><a href="#edit" data-toggle="tab">Edit</a></li>
<li><a href="#preview" data-toggle="tab">Preview</a></li>
</ul>
<div class="tab-content" style="max-height: 500px; min-height: 400px;overflow-y: auto">
<div id="edit" class="tab-pane fade in active">
<textarea id="policyEditor"></textarea>
</div>
<div id="preview" class="tab-pane fade in">
<div data-bind="html: policyData.html" class="well-lg"></div>
</div>
</div>
<button class="btn btn-primary pull-right" data-toggle="modal" data-target="#saveDialog">Save</button><!--data-bind="click: $root.policySave"-->
<button class="btn btn-secondary pull-right" data-bind="click: $root.policyReload">Cancel</button>
</div>
</div>
<div id="saveDialog" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4>Change Details</h4>
</div>
<div class="modal-body">
<table class="table">
<tr>
<th>Change Author</th>
<td><input class="form-control" id="changeAuthorInput" type="text" data-bind="value: policyData.ChangeAuthor" /></td>
</tr>
<tr>
<th>Change Date</th>
<td><input class="form-control" id="changeDatePicker" type="datetime-local" data-bind="value: policyData.ChangeDate" /></td>
</tr>
<tr>
<th>Version</th>
<td><input class="form-control" id="versionInput" type="text" data-bind="value: policyData.Version" readonly /></td>
</tr>
<tr>
<th>Change Description</th>
<td><input class="form-control" id="changeDescription" type="text" data-bind="value: policyData.ChangeDescription" /></td>
</tr>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-primary pull-right" data-bind="click: $root.policySave">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script src="js/PolicyObject.js" type="text/javascript"></script>
<script src="Helpers.min.js" type="text/javascript"></script> <script src="Helpers.min.js" type="text/javascript"></script>
<script src="js/easymde.min.js" type="text/javascript"></script>
<script src="js/highlightjs.min.js" type="text/javascript"></script>
<script src="js/htmlparser.min.js" type="text/javascript"></script>
<script src="js/marked.min.js"></script>
<script src="admin.js" type="text/javascript"></script> <script src="admin.js" type="text/javascript"></script>
</body> </body>
</html> </html>

View File

@ -4,6 +4,70 @@
self.groupEditItem = ko.observable(null); self.groupEditItem = ko.observable(null);
self.unassignedCardList = ko.observable(null); self.unassignedCardList = ko.observable(null);
self.helpers = new Helpers(); self.helpers = new Helpers();
self.policyMarkdown = "";
self.policyData = new policy();
self.renderer = new marked.Renderer();
self.renderer.blockquote = function(quote) {
return "<blockquote class=\"blockquote\">" + quote + "</blockquote>";
};
self.renderer.heading = function (text, level) {
var parserHandler = new Tautologistics.NodeHtmlParser.DefaultHandler(function (error) {
if (error)
throw new Error("Cannot parse \"" + text + "\" in markdown file.");
});
var parser = new Tautologistics.NodeHtmlParser.Parser(parserHandler);
parser.parseComplete(text);
var escaped = "unknown";
if (parserHandler.dom.length > 0) {
escaped = parserHandler.dom[0].raw.toLowerCase().trim().replace(/ /g, "-");
}
return "<h" + level + " id=\"" + escaped + "\">"
// NOTE: We're setting display none INLINE, so you have
// to override with !important if you want it to show.
+ " <a class=\"heading-anchor\" style=\"display:none;\" href=\"#" + escaped + '">'
+ " <i class=\"oi oi-link-intact\"></i>"
+ " </a>"
+ text
+ "</h" + level + ">";
};
self.renderer.table = function(header, body) {
return "<table class=\"table\">" +
"<thead class=\"thead-default\">" +
header +
"</thead>" +
"<tbody>" +
body +
"</tbody>" +
"</table>";
};
/*
* Adds highlight.js classes to `code` blocks
*/
self.renderer.code = function(code, language) {
var valid = !!(language && hljs.getLanguage(language));
var highlighted = valid ? hljs.highlight(language, code).value : code
return "<pre><code class=\"hljs lang-" + language + "\">" + highlighted + "</code></pre>";
};
self.editor = new EasyMDE({
element: document.getElementById("policyEditor"),
showIcons: ["bold", "italic", "strikethrough", "heading", "heading-smaller", "heading-bigger", "heading-1", "heading-2", "heading-3", "code", "quote", "unordered-list", "ordered-list", "clean-block", "link", "table", "horizontal-rule", "guide", "table"],
hideIcons: ["preview", "side-by-side", "fullscreen"],
renderingConfig: {
markedOptions: {
renderer: self.renderer
}
}
});
self.editor.codemirror.on("changes",
function () {
self.policyData.html(self.editor.options.previewRender(self.editor.value()));
});
self.uiPages = { self.uiPages = {
overview: "overview", overview: "overview",
group: "groups", group: "groups",
@ -14,7 +78,9 @@
getGroups: "/api/groups", getGroups: "/api/groups",
editGroup: "/api/groups/edit", editGroup: "/api/groups/edit",
getUnassignedCards: "/api/cards/unassigned", getUnassignedCards: "/api/cards/unassigned",
clearUnassignedCards: "/api/cards/unassigned" clearUnassignedCards: "/api/cards/unassigned",
getPolicy:"/api/app/policy",
savePolicy:"/api/app/policy"
}; };
self.clearGroupForm = function () { self.clearGroupForm = function () {
self.helpers.goToMenuOption(self.uiPages.group); self.helpers.goToMenuOption(self.uiPages.group);
@ -50,7 +116,7 @@
false); false);
$.ajax({ $.ajax({
url: url, url: url,
type: 'DELETE', type: "DELETE",
success: function () { success: function () {
console.log("deleted " + groupId); console.log("deleted " + groupId);
self.hideGroupForm(); self.hideGroupForm();
@ -100,6 +166,34 @@
self.padNumber = function (number) { self.padNumber = function (number) {
return (number < 10 ? "0" : "") + number; return (number < 10 ? "0" : "") + number;
}; };
self.policySave = function () {
var url = self.helpers.createRequestUrl(self.apiEndpoints.savePolicy, null, false);
self.policyData.Markdown(self.editor.value()); //make sure we update it, as it doesnt push the value back into the variable
//console.log(self.policyData());
$.post(url, self.policyData(), function() {
}, "json") //todo: check this serialisation as the object is now complex.
.done(function() {
self.policyReload();
}).fail(function(resp, status, error) {
var errorObj = self.helpers.processRequestFailure(resp, status, error);
console.log(errorObj);
});
};
self.policyReload = function() {
var url = self.helpers.createRequestUrl(self.apiEndpoints.getPolicy,
[], false, false);
$.getJSON(url, function (res) {
//console.log(res);
self.editor.value(res.Markdown);
res.Version = (parseInt(res.Version) + 1).toString();
self.policyData.update(res);
$('#saveDialog').modal('hide');
}).fail(function (resp, status, error) {
console.log("error - policyReload");
var errObj = self.helpers.processRequestFailure(resp, status, error);
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "policyReload");
});
};
self.convertToDisplayDateTime = function (dateValue) { self.convertToDisplayDateTime = function (dateValue) {
var date = new Date(dateValue); // dd MM YY HH:mm:ss e.g.: 01 Mar 17 17:34:02 var date = new Date(dateValue); // dd MM YY HH:mm:ss e.g.: 01 Mar 17 17:34:02
return date.getDate() + " " return date.getDate() + " "
@ -114,6 +208,7 @@
this.get("#overview", function () { this.get("#overview", function () {
self.getGroups(); self.getGroups();
self.getUnassignedCardData(); self.getUnassignedCardData();
self.policyReload();
}); });
this.post("#editgroup", function () { this.post("#editgroup", function () {
self.submitGroupEdit(self.groupEditItem()); self.submitGroupEdit(self.groupEditItem());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}

View File

@ -86,8 +86,23 @@
<thead> <thead>
<tr> <tr>
<th class="col-md-1"></th> <th class="col-md-1"></th>
<th class="col-md-3">First Name</th> <th class="col-md-3">
<th class="col-md-3">Last Name</th> <div class="col-md-1">
<a href="#users?sort=firstAsc"><span data-bind="css:{ 'text-warning': $root.sortIsActive('firstAsc')}" class="glyphicon glyphicon-chevron-up"></span></a>
</div>
<div class="col-md-1">
<a href="#users?sort=firstDesc"><span data-bind="css:{ 'text-warning': $root.sortIsActive('firstDesc')}" class="glyphicon glyphicon-chevron-down"></span></a>
</div>
<span class="col-md-9">First Name</span>
</th>
<th class="col-md-3">
<div class="col-md-1">
<a href="#users?sort=lastAsc"><span data-bind="css:{ 'text-warning': $root.sortIsActive('lastAsc')}" class="glyphicon glyphicon-chevron-up"></span></a>
</div>
<div class="col-md-1">
<a href="#users?sort=lastDesc"><span data-bind="css:{ 'text-warning': $root.sortIsActive('lastDesc')}"class="glyphicon glyphicon-chevron-down"></span></a>
</div>
<span class="col-md-9">Last Name</span></th>
<th class="col-md-1 text-center">Contractor</th> <th class="col-md-1 text-center">Contractor</th>
<th/> <th/>
<th/> <th/>
@ -303,6 +318,11 @@
</menu>--> </menu>-->
</div> </div>
<div class="container" data-bind="with: policyData">
<div class="row" data-bind="html:Html">
</div>
</div>
<div id="aboutDialog" class="modal fade" role="dialog" data-bind="with: appDetails"> <div id="aboutDialog" class="modal fade" role="dialog" data-bind="with: appDetails">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -330,6 +350,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Dialog to create a manual time entry-->
<div id="manualLogDialog" class="modal fade" role="dialog" data-bind="with: manualLog"> <div id="manualLogDialog" class="modal fade" role="dialog" data-bind="with: manualLog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -350,7 +371,8 @@
optionsText: function(item) { return item.Text }, optionsText: function(item) { return item.Text },
optionsValue: function(item){ return item.value }, optionsValue: function(item){ return item.value },
value: Direction, value: Direction,
optionsCaption: 'Choose...'"></select> optionsCaption: 'Choose...'"
required></select>
</div> </div>
</div> </div>
<br/> <br/>

View File

@ -0,0 +1,30 @@
function policy(data) {
var self = this;
if (typeof data === "undefined") {
data = createDefaultPolicy();
}
self.changeDate = ko.observable(data.changeDate);
self.description = ko.observable(data.description);
self.changeAuthor = ko.observable(data.changeAuthor);
self.version = ko.observable(data.version);
self.markdown = ko.observable(data.markdown);
self.html = ko.observable(data.html);
function createDefaultPolicy() {
return {
changeDate: moment().format(),
description: "",
changeAuthor: "",
version: -1,
markdown: "",
html: ""
};
}
self.update = function(data) {
self.changeDate(data.changeDate);
self.description(data.description);
self.changeAuthor(data.changeAuthor);
self.version(data.version);
self.markdown(data.markdown);
self.html(data.html);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,14 @@
function DataVM() { const SortOptions = {
LastAsc: "LastNameAscending",
LastDesc: "LastNameDescending",
FirstAsc: "FirstNameAscending",
FirstDesc: "FirstNameDescending"
};
function DataVM() {
"use strict"; "use strict";
var self = this; var self = this;
self.helpers = new Helpers(); self.helpers = new Helpers();
self.menuOptions = ["Home"]; self.menuOptions = ["Home", "Policy"];
self.possibleLogDirections = ko.observableArray([ self.possibleLogDirections = ko.observableArray([
{ Text: "In", value: 1 }, { Text: "In", value: 1 },
{ Text: "Out", value: 2 } { Text: "Out", value: 2 }
@ -19,6 +25,11 @@
self.selectedCalendarWeek = ko.observable(0); self.selectedCalendarWeek = ko.observable(0);
self.errorData = ko.observable(null); self.errorData = ko.observable(null);
self.manualLog = ko.observable(null); self.manualLog = ko.observable(null);
self.policyData = ko.observable(null);
self.policyChangeDate = ko.observable(null);
self.policyChangeAuthor = ko.observable(null);
self.policyVersion = ko.observable(null);
self.selectedSort = ko.observable(null);
self.apiEndpoints = { self.apiEndpoints = {
root: "http://localhost:8800", root: "http://localhost:8800",
getUserList: "/api/users", getUserList: "/api/users",
@ -29,7 +40,8 @@
getGroups: "/api/groups", getGroups: "/api/groups",
getAppDetails: "/api/app", getAppDetails: "/api/app",
manualLogsCreate: "/api/logs/create", manualLogsCreate: "/api/logs/create",
manualLogsDelete: "/api/logs/delete" manualLogsDelete: "/api/logs/delete",
getPolicy: "/api/app/policy"
}; };
self.uiPages = { self.uiPages = {
users: "users", users: "users",
@ -216,25 +228,32 @@
}); });
} }
}; };
self.getUserList = function (pageSize, pageNumber, groupId) { self.getUserList = function (pageSize, pageNumber, groupId, sort) {
var args = null; var args = [];
if (pageSize && pageNumber) { if (pageSize && pageNumber) {
args = [ args.push(
{ {
key: "pageSize", key: "pageSize",
value: pageSize value: pageSize
}, });
args.push(
{ {
key: "pageNumber", key: "pageNumber",
value: pageNumber value: pageNumber
});
} }
]; if (groupId) {
} else if(groupId) { args.push(
args = [
{ {
key: "groupId", key: "groupId",
value: groupId value: groupId
}]; });
}
if (sort) {
args.push({
key: "sort",
value: sort
});
} }
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList, args, false); var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList, args, false);
$.getJSON(url, function (res) { $.getJSON(url, function (res) {
@ -256,9 +275,9 @@
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUserList"); self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUserList");
}); });
}; };
self.searchUsers = function(query) { self.searchUsers = function(query, sort) {
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList, var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList,
[{ key: "query", value: query }], false, false); [{ key: "query", value: query }, {key:"sort",value:sort}], false, false);
$.getJSON(url, $.getJSON(url,
function(res) { function(res) {
self.userList(res); self.userList(res);
@ -336,13 +355,25 @@
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getGroups"); self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getGroups");
}); });
}; };
self.getPolicyData = function () {
var url = self.helpers.createRequestUrl(self.apiEndpoints.getPolicy, null, false);
$.getJSON(url, function (res) {
console.log(res);
self.policyData(res);
}).fail(function (resp, status, error) {
console.log("error - getPolicyData");
var errObj = self.helpers.processRequestFailure(resp, status, error);
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getPolicyData");
});
};
self.createManualLog = function(newLog) { self.createManualLog = function(newLog) {
var url = self.helpers.createRequestUrl(self.apiEndpoints.manualLogsCreate, null, false, false); var url = self.helpers.createRequestUrl(self.apiEndpoints.manualLogsCreate, null, false, false);
$.post(url, newLog, function () { $.post(url, newLog, function () {
}, "json") }, "json")
.done(function () { .done(function () {
self.manualLog(null); self.manualLog(null);
$('#manualLogDialog').modal("hide"); $("#manualLogDialog").modal("hide");
location.reload(); //stay on this users logs page, but just reload the timelogs. location.reload(); //stay on this users logs page, but just reload the timelogs.
}) })
.fail(function (resp, status, error) { .fail(function (resp, status, error) {
@ -379,17 +410,18 @@
self.assignUpdateHandler(); self.assignUpdateHandler();
}; };
function createlog(data, event) { function createlog(data, event) {
var logDateInitVal = moment(self.selectedTimeLogDate()).add((data.Day - 1), 'days').toISOString();
self.manualLog({ self.manualLog({
CalendarWeek:-1, CalendarWeek:-1,
Direction:-1, Direction:-1,
EventTime: new Date().toISOString(), EventTime: logDateInitVal,
Id: -1, Id: -1,
IdentifierId: -1, IdentifierId: -1,
UserId: self.chosenTimeLogUserId, UserId: self.chosenTimeLogUserId,
Year: 0 Year: 0
}); });
$('#manualLogDialog').modal("show"); $('#manualLogDialog').modal("show");
self.initialiseManualLogDateTimePicker(self.selectedTimeLogDate()); self.initialiseManualLogDateTimePicker(logDateInitVal);
self.assignUpdateHandler(); self.assignUpdateHandler();
}; };
function deleteLog(data) { function deleteLog(data) {
@ -408,28 +440,63 @@
maxDate: moment(date).endOf("week") maxDate: moment(date).endOf("week")
}); });
}; };
function convertSortOption(opt) {
if (!opt) {
return null
}
if (opt === "firstAsc") {
return SortOptions.FirstAsc;
}
if (opt === "firstDesc") {
return SortOptions.FirstDesc;
}
if (opt === "lastAsc") {
return SortOptions.LastAsc;
}
if (opt === "lastDesc") {
return SortOptions.LastDesc;
}
};
self.sortIsActive = function (option) {
if (option === "firstAsc" && self.userList().SelectedSortOption === SortOptions.FirstAsc) {
return true;
}
if (option === "firstDesc" && self.userList().SelectedSortOption === SortOptions.FirstDesc) {
return true;
}
if (option === "lastAsc" && self.userList().SelectedSortOption === SortOptions.LastAsc) {
return true;
}
if (option === "lastDesc" && self.userList().SelectedSortOption === SortOptions.LastDesc) {
return true;
}
return false;
};
Sammy(function () { Sammy(function () {
this.get("#users", function () { this.get("#users", function () {
var query = this.params.query; var query = this.params.query;
var pageSize = this.params.pageSize; var pageSize = this.params.pageSize;
var pageNumber = this.params.pageNumber; var pageNumber = this.params.pageNumber;
var groupId = this.params.groupId; var groupId = this.params.groupId;
var sort = convertSortOption(this.params.sort);
console.log(sort);
self.chosenMenuItemId("Home"); self.chosenMenuItemId("Home");
self.groupsList(null); self.groupsList(null);
self.chosenUserDetails(null); self.chosenUserDetails(null);
self.userList(null); self.userList(null);
self.userTimeLogData(null); self.userTimeLogData(null);
self.policyData(null);
self.manualLog(null); self.manualLog(null);
if (self.appDetails() === null) { if (self.appDetails() === null) {
self.getAppDetails(); self.getAppDetails();
} }
self.getGroups(function (data) { self.groupsList(data); }); self.getGroups(function (data) { self.groupsList(data); });
if (query) if (query)
self.searchUsers(query); self.searchUsers(query, sort);
else if (groupId && groupId > 0) else if (groupId && groupId > 0)
self.getUserList(null, null, groupId); self.getUserList(null, null, groupId, sort);
else else
self.getUserList(pageSize, pageNumber); self.getUserList(pageSize, pageNumber, null,sort);
}); });
this.get("#userData/:userId", function () { this.get("#userData/:userId", function () {
self.chosenMenuItemId("Data"); self.chosenMenuItemId("Data");
@ -437,6 +504,7 @@
self.chosenUserDetails(null); self.chosenUserDetails(null);
self.userList(null); self.userList(null);
self.userTimeLogData(null); self.userTimeLogData(null);
self.policyData(null);
self.manualLog(null); self.manualLog(null);
self.getUserDetails(this.params.userId); self.getUserDetails(this.params.userId);
self.getUnassignedCardData(); self.getUnassignedCardData();
@ -454,12 +522,14 @@
self.userList(null); self.userList(null);
self.userTimeLogData(null); self.userTimeLogData(null);
self.manualLog(null); self.manualLog(null);
self.policyData(null);
self.getTimeLogData(this.params.userId, self.selectedTimeLogDate()); self.getTimeLogData(this.params.userId, self.selectedTimeLogDate());
}); });
this.get("#newUser", function () { this.get("#newUser", function () {
self.chosenMenuItemId("newUser"); self.chosenMenuItemId("newUser");
self.userList(null); self.userList(null);
self.userTimeLogData(null); self.userTimeLogData(null);
self.policyData(null);
self.chosenUserDetails({ self.chosenUserDetails({
"UserId": -1, "UserId": -1,
"FirstName": null, "FirstName": null,
@ -478,6 +548,16 @@
this.get("#stats", function () { this.get("#stats", function () {
self.goToMenuOption("users"); self.goToMenuOption("users");
}); });
this.get("#Policy",
function () {
self.groupsList(null);
self.chosenUserDetails(null);
self.userList(null);
self.userTimeLogData(null);
self.manualLog(null);
self.getPolicyData();
});
this.post("#edituser", function () { this.post("#edituser", function () {
$.each(self.chosenUserDetails().AssociatedIdentifiers, $.each(self.chosenUserDetails().AssociatedIdentifiers,
function (k, v) { function (k, v) {
@ -496,7 +576,6 @@
this.post("#manualLog", this.post("#manualLog",
function() { function() {
self.createManualLog(self.manualLog()); self.createManualLog(self.manualLog());
$('#manualLogDialog').modal("hide"); $('#manualLogDialog').modal("hide");
//self.goToTimeLogs(self.chosenTimeLogUserId, null, [{ key: "selectedDate", value: self.selectedTimeLogDate() }]); //self.goToTimeLogs(self.chosenTimeLogUserId, null, [{ key: "selectedDate", value: self.selectedTimeLogDate() }]);
}); });

View File

@ -4,7 +4,7 @@
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" /> <add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DefaultPageSize" value="20" /> <add key="DefaultPageSize" value="20" />
<add key="SwipeTimeGap" value="3" /> <add key="SwipeTimeGap" value="3" />
<add key="BugSubmissionEmailAddress" value="incoming+WattsC/FlexiTimeTrackerTool+24qrefn8e1urhl4iqct7we2jl@gitlab.com"/> <add key="BugSubmissionEmailAddress" value="incoming+WattsC/FlexiTimeTrackerTool+24qrefn8e1urhl4iqct7we2jl@gitlab.com" />
</appSettings> </appSettings>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
@ -31,6 +31,18 @@
<assemblyIdentity name="System.Web.Http.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.Web.Http.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Ninject" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.4.0" newVersion="3.3.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Ninject.Web.Common" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.1.0" newVersion="3.3.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Ninject.Web.Common.OwinHost" publicKeyToken="c7192dc5380945e7" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.1.0" newVersion="3.3.1.0" />
</dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>

View File

@ -57,6 +57,10 @@
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Microsoft.Owin.FileSystems, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
<Private>True</Private> <Private>True</Private>

BIN
Tools/Sentinel.zip Normal file

Binary file not shown.