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
VisualStudioVersion = 14.0.25420.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
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
Global
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
<appSettings>
<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>
<RootNamespace>CardReaderService</RootNamespace>
<AssemblyName>CardReaderService</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -33,12 +34,12 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="pcsc-sharp, Version=3.6.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\PCSC.3.6.0\lib\net40\pcsc-sharp.dll</HintPath>
<Reference Include="PCSC, Version=4.2.0.0, Culture=neutral, PublicKeyToken=13b76e54a2ee80a7, processorArchitecture=MSIL">
<HintPath>..\packages\PCSC.4.2.0\lib\net40\PCSC.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@ -53,6 +54,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CardDataPost.cs" />
<Compile Include="ConfigurationManager.cs" />
<Compile Include="ConfigurationProperty.cs" />
<Compile Include="ConfigurationType.cs" />
<Compile Include="ConfigureService.cs" />
<Compile Include="DataCenterHelper.cs" />
<Compile Include="Service1.cs">
@ -68,6 +73,12 @@
<None Include="App.config" />
<None Include="packages.config" />
</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" />
<!-- 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.

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

View File

@ -1,10 +1,13 @@
using PCSC;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using Logger;
using PCSC.Monitoring;
namespace CardReaderService
{
@ -12,14 +15,15 @@ namespace CardReaderService
{
private Thread _mainWorkThread;
private bool _stopMainWorkerThread;
private AutoResetEvent _mainWorkerTerminationSignal;
private string _readerName = "";
private readonly ManualResetEvent _mre;
private string _readerName = string.Empty;
private SCardMonitor _cardMonitor;
private bool _initialised=false;
public Service1()
{
InitializeComponent();
_mre = new ManualResetEvent(false);
}
public void Start()
@ -27,44 +31,54 @@ namespace CardReaderService
OnStart(new string[] { });
}
protected override void OnStart(string[] args)
{
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()
public new void Stop()
{
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()
{
MessageLogger.Log("Stopping..");
_stopMainWorkerThread = true;
_mainWorkerTerminationSignal.Set();
_mre.Set();
if (_mainWorkThread != null && _mainWorkThread.IsAlive)
{
_mainWorkThread.Join(3000);
if (_mainWorkThread.IsAlive)
{
_mainWorkThread.Interrupt();
}
}
if (_cardMonitor != null)
{
_cardMonitor.CardInserted -= CardMonitor_CardInserted;
_cardMonitor.StatusChanged -= CardMonitorOnStatusChanged;
_cardMonitor.Cancel();
_cardMonitor.Dispose();
_cardMonitor = null;
@ -79,96 +93,110 @@ namespace CardReaderService
Name = "CardServiceMainThread",
IsBackground = false
};
_mainWorkerTerminationSignal = new AutoResetEvent(false);
_mainWorkThread.Start();
}
private void _cardMonitor_CardInserted(object sender, CardStatusEventArgs e)
private void CardMonitor_CardInserted(object sender, CardStatusEventArgs e)
{
try
{
var ctxFac = ContextFactory.Instance;
using (var ctx = ctxFac.Establish(SCardScope.System))
{
var reader = new SCardReader(ctx);
var pioSendPci = new IntPtr();
byte[] rcvBuffer = new byte[256];
if (_readerName == string.Empty)
{
Console.WriteLine("Reader name is somehow empty... WTF!");
MessageLogger.Log("Reader name is somehow empty... WTF!");
_stopMainWorkerThread = true;
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)
{
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)
{
var uid = ConvertByteUIDToString(rcvBuffer);
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");
Console.WriteLine("Posted to Server");
MessageLogger.Log("Posted to Server");
}
else
{
MessageLogger.Log("Failed to Read UID, Error: " + err);
}
}
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()
{
var minimumLoopTimemS = 5000;
while (!_stopMainWorkerThread)
{
if (!WeHaveValidCardReader())
{ //only do this if we don't have a valid card reader
if (_initialised)
if (_cardMonitor == null)
{
//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;
using (var context = ctxFactory.Establish(SCardScope.System))
{
var readerNames = context.GetReaders();
if (!NoReaderAvailable(readerNames))
if (NoReaderAvailable(readerNames))
{
//we have a reader available, so initialise!
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;
MessageLogger.Log("No Card Reader available, Waiting..");
}
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)
{
var sb = new StringBuilder();
StringBuilder sb = new StringBuilder();
foreach (var b in uid)
{
@ -183,4 +211,9 @@ namespace CardReaderService
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"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="PCSC" version="3.6.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net45" />
<package id="PCSC" version="4.2.0" targetFramework="net45" />
</packages>

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net45" />
</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>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml"/>
<add key="DataCenterServiceEndpoint" value="http://localhost:8800"/>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DataCenterServiceEndpoint" value="http://localhost:8800" />
</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>

View File

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

View File

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

View File

@ -1,10 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<appSettings>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml"/>
<add key="DataCenterServiceEndpoint" value="http://localhost:1234"/>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DataCenterServiceEndpoint" value="http://localhost:1234" />
</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>

View File

@ -54,7 +54,7 @@
</PropertyGroup>
<ItemGroup>
<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>
</Reference>
<Reference Include="System" />

View File

@ -30,8 +30,18 @@
<Component Id="SmartCardServiceConfig" Guid="{73D2E31D-F256-457C-AFD5-EC456CDAD7E8}" KeyPath="yes">
<ServiceControl Id="SmartCardServiceStarter"
Start="install"
Stop="install"
Name="SCardSvr"
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 Id="CardReaderServiceExe" Guid="{24C9D834-21E2-476D-8302-EF35730D0BA8}">
<File Id="CardReaderService.exe"
@ -61,6 +71,7 @@
ThirdFailureActionType="restart"
ResetPeriodInDays="1"
RestartServiceDelayInSeconds="10" />
<ServiceDependency Id="SCardSvr" Group="no"/>
</ServiceInstall>
</Component>
<Component Id="CardReaderServiceExeConfig" Guid="{E20D23BC-C8E7-49F8-962C-DE856A84258E}">

View File

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

View File

@ -12,7 +12,7 @@ namespace Interfaces
/// Returns <see cref="UserList"/> with full list of users,
/// plus a total user count. Pagination options are supported.
/// </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>
/// Search the user list for the following string
/// </summary>
@ -96,10 +96,12 @@ namespace Interfaces
/// <see cref="Identifier"/> object with the Unique Id triggering the event
/// </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>
/// <see cref="OperationResponse"/> to indicate procedure status.
/// </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);
List<Group> GetGroups(int userId = -1);
@ -110,5 +112,9 @@ namespace Interfaces
OperationResponse DeleteLog(TimeLog log);
OperationResponse CreateLog(TimeLog log);
OperationResponse UpdateLog(TimeLog log);
Policy GetPolicy();
void SavePolicy(Policy policy);
}
}

View File

@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Interfaces</RootNamespace>
<AssemblyName>Interfaces</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -69,7 +70,9 @@
<Compile Include="IdentifierList.cs" />
<Compile Include="ManualLog.cs" />
<Compile Include="OperationResponse.cs" />
<Compile Include="Policy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SortOptions.cs" />
<Compile Include="TimeLog.cs" />
<Compile Include="TimeLogList.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
{
@ -14,13 +15,15 @@ namespace Interfaces
public int TotalUserCount { get; set; }
public List<User> Users { get; set; }
public SortOptions SelectedSortOption { get; set; }
public int PageCount
{
get
{
if (TotalUserCount < PageSize)
return 1;
return (TotalUserCount / PageSize);
return (int)Math.Ceiling(Convert.ToDouble(TotalUserCount) / Convert.ToDouble(PageSize));
}
}
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
{
internal static class SQLiteProcedures
{
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) + "=?";
public const string GET_TIMELOGS =
@ -22,9 +27,7 @@ namespace SQLiteRepository
+ "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )"
+ "order by "
+ nameof(UserIdentity.LastName) + " collate nocase, "
+ nameof(UserIdentity.FirstName) + " collate nocase";
+ "order by ? collate nocase ?, ? collate nocase ?";
public const string GET_ALL_USERS_PAGINATE =
"select * from " + nameof(UserIdentity) + " ut "
@ -38,18 +41,18 @@ namespace SQLiteRepository
+ "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )"
+ "order by "
+ nameof(UserIdentity.LastName) + " collate nocase, "
+ nameof(UserIdentity.FirstName) + " collate nocase "
+ "limit ? offset ?";
+ " order by "
+ "{0} collate nocase {1}, "
+ "{2} collate nocase {3} "
+ "limit {4} offset {5}";
public const string GET_ALL_USERS_BY_GROUP =
"select u." + nameof(UserIdentity.Id) + ", u." + nameof(UserIdentity.FirstName) + ", u." +
nameof(UserIdentity.LastName) + ", u." + nameof(UserIdentity.HoursPerWeek) + ", u." +
nameof(UserIdentity.IsContractor) + " from " + nameof(UserIdentity) + " u left join " +
nameof(UserGroupJoinDb) + " ugj on ugj." + nameof(UserGroupJoinDb.UserId_FK) + " = u." +
nameof(UserIdentity.Id) + " where ugj." + nameof(UserGroupJoinDb.GroupId_FK) + "=? order by u." +
nameof(UserIdentity.LastName) + " collate nocase, u." + nameof(UserIdentity.LastName) + " collate nocase";
nameof(UserIdentity.Id) + " where ugj." + nameof(UserGroupJoinDb.GroupId_FK) + "= {0} " +
"order by u.{1} collate nocase {2}, u.{3} collate nocase {4}";
public const string GET_USER_BY_ID =
"select * from " + nameof(UserIdentity) + " where " + nameof(UserIdentity.Id) + "=?";
@ -60,6 +63,9 @@ namespace SQLiteRepository
public const string GET_CARDS_BY_UNIQUE_ID =
"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 =
"select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.UserId_FK) + "=?";
@ -88,9 +94,82 @@ namespace SQLiteRepository
nameof(TimeLogDb.SwipeEventDateTime) + " desc LIMIT 1";
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 =
"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 SQLite.Net;
using SQLite.Net.Platform.Win32;
using SQLiteRepository.Converters;
using SQLiteRepository.Extensions;
using SQLiteRepository.Properties;
namespace SQLiteRepository
@ -16,12 +18,14 @@ namespace SQLiteRepository
{
private readonly SQLiteConnection _connection;
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)
{
_path =
new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), "flexitimedb.db"))
new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), DATABASE_NAME))
.LocalPath;
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger;
@ -34,96 +38,20 @@ namespace SQLiteRepository
_connection.CreateTable<GroupDb>();
_connection.CreateTable<UserGroupJoinDb>();
_connection.CreateTable<DbVersion>();
_connection.CreateTable<PolicyDb>();
_logger.Trace("Initialised SQLite Repository");
_logger.Trace("Checking For Upgrades");
CheckForDbUpgrade();
}
private void CheckForDbUpgrade()
{
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)
public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1, SortOptions sort = SortOptions.None)
{
var ret = new UserList();
List<UserIdentity> users;
int userCount;
if (pageNumber != -1 && pageSize != -1)
{
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;
}
List<UserIdentity> users = GetUserList(groupId, pageSize, pageNumber, sort);
var userCount = GetUserCount();
if (!users.Any())
{
if (pageNumber == -1 && pageSize == -1)
{
ret.PageNumber = 1;
@ -134,12 +62,15 @@ namespace SQLiteRepository
ret.PageNumber = pageNumber;
ret.PageSize = pageSize;
}
if (!users.Any())
{
return ret;
}
foreach (var user in users)
{
var userObj = ChangeToUserObject(user);
var userObj = UserConverter.ConvertToUserDto(user);
userObj.AssociatedIdentifiers = GetAssociatedIdentifiers(user.Id);
userObj.State = GetUserState(GetLogDirection(user.Id));
@ -147,16 +78,7 @@ namespace SQLiteRepository
userObj.Groups = GetGroups(user.Id);
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;
return ret;
}
@ -180,26 +102,20 @@ namespace SQLiteRepository
foreach (var user in users)
{
var userObj = ChangeToUserObject(user);
var userObj = UserConverter.ConvertToUserDto(user);
var cards = _connection.Query<CardUniqueId>(
SQLiteProcedures.GET_CARDS_BY_USER_ID,
user.Id);
foreach (var card in cards)
{
userObj.AssociatedIdentifiers.Add(new Identifier()
{
UniqueId = card.CardUId,
IsAssociatedToUser = true,
Id = card.Id
});
userObj.AssociatedIdentifiers.Add(IdentifierConverter.ConvertToIdentifierDto(card));
}
userObj.State = GetUserState(GetLogDirection(user.Id));
ret.Users.Add(userObj);
}
//TODO: figure out paging here. - should there be any?
ret.PageSize = 20;
ret.PageNumber = 1;
ret.PageNumber = (int)Math.Ceiling((double)ret.Users.Count / (double)ret.PageSize);
return ret;
}
@ -217,7 +133,7 @@ namespace SQLiteRepository
if (!users.Any()) return ret;
var user = users.First();
ret = ChangeToUserObject(user);
ret = UserConverter.ConvertToUserDto(user);
ret.Groups = GetGroups();
var usersGroups = GetGroups(user.Id);
foreach (var group in usersGroups)
@ -230,7 +146,7 @@ namespace SQLiteRepository
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)
@ -298,86 +214,53 @@ namespace SQLiteRepository
foreach (var card in cardQuery)
{
ret.data.Add(new Identifier
{
Id = card.Id,
IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID,
UniqueId = card.CardUId,
LastUsed = card.LastUsed.DateTime
});
ret.data.Add(IdentifierConverter.ConvertToIdentifierDto(card));
// new Identifier
//{
// Id = card.Id,
// IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID,
// UniqueId = card.CardUId,
// LastUsed = card.LastUsed.DateTime
//});
}
return ret;
}
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(
SQLiteProcedures.CLEAR_UNASSIGNED_CARDS,
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)
{
//if(user.UserId <=0) return OperationResponse.FAILED;
var ret = OperationResponse.NONE;
var cardIds = new List<int>();
//Get a list of current associated identifiers, convert into a list of Identifier Objects..
var currentCards =
_connection.Query<CardUniqueId>(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId)
.Select(x => new Identifier { Id = x.Id, IsAssociatedToUser = true, UniqueId = x.CardUId });
var cardsToRemove = currentCards.Except(user.AssociatedIdentifiers).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
.Select(IdentifierConverter.ConvertToIdentifierDto).ToList();
var cardsToRemove = currentCards.Exclude(user.AssociatedIdentifiers, x => x.Id).Select(x => x.Id).ToList();
#region Update/Create User
int userId;
if (user.UserId != -1)
{
//edit..
_connection.Query<UserIdentity>(
SQLiteProcedures.UPDATE_USER_DETAILS,
user.FirstName,
user.LastName,
user.HoursPerWeek,
user.IsContractor,
user.UserId
);
UpdateUserDetails(user.FirstName, user.LastName, user.HoursPerWeek, user.IsContractor, user.UserId);
userId = user.UserId;
}
else
{
var userInsert = new UserIdentity
{
FirstName = user.FirstName,
LastName = user.LastName,
HoursPerWeek = user.HoursPerWeek,
IsContractor = user.IsContractor
};
var userInsert = UserConverter.ConvertFromUserDto(user);
_connection.Insert(userInsert);
userId = userInsert.Id;
if (ret < OperationResponse.CREATED)
@ -385,19 +268,32 @@ namespace SQLiteRepository
}
#endregion
#region Update Card/Unique Id entries.
foreach (var cardId in cardIds)
#region GetUnique Identifiers
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>(
SQLiteProcedures.UPDATE_CARD_USER_ID,
userId, cardId);
}
foreach (var card in cardsToRemove)
if (existingCards.All(x => x.CardUId != card.UniqueId))
{
_connection.Query<CardUniqueId>(
SQLiteProcedures.UPDATE_CARD_USER_ID,
-1, card);
//this card is not currently in the system..
var cardInsert = IdentifierConverter.ConvertFromIdentifierDto(card, user.UserId);
_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
#region Update Group Associations
@ -410,8 +306,20 @@ namespace SQLiteRepository
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 cardIdQuery = _connection.Query<CardUniqueId>(
SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID,
@ -428,15 +336,15 @@ namespace SQLiteRepository
UpdateIdentifierLastUsed(DateTimeOffset.UtcNow, ident.Id);
logId = -1;
//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;
return ret;
}
else
{
//TODO: log when more than one comes back. should NEVER happen but....
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
// Get The User Direction (are they going in or out)?
@ -465,7 +373,7 @@ namespace SQLiteRepository
#endregion
#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 year = logTime.Year;
#endregion
@ -490,10 +398,14 @@ namespace SQLiteRepository
return ret;
}
/*Groups*/
//TODO: check group name can only be entered once.
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 resp = _connection.Insert(groupDb);
groupId = groupDb.GroupId;
@ -506,22 +418,12 @@ namespace SQLiteRepository
List<GroupDb> query;
if (userId == -1)
{
query = _connection.Query<GroupDb>("select gp.GroupId, gp.GroupName, " +
"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");
query = _connection.Query<GroupDb>(SQLiteProcedures.GET_GROUPS);
}
else
{
query =
_connection.Query<GroupDb>(
"select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount" +
" from GroupDb gdb" +
" left join UserGroupJoinDb ujdb" +
" on gdb.GroupId = ujdb.GroupId_FK" +
" where ujdb.UserId_FK = ?",
_connection.Query<GroupDb>(SQLiteProcedures.GET_GROUPS_FOR_USER,
userId);
}
foreach (var group in query)
@ -538,7 +440,7 @@ namespace SQLiteRepository
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())
{
var group = query.First();
@ -554,8 +456,7 @@ namespace SQLiteRepository
public OperationResponse UpdateGroup(Group group)
{
//TODO: I would probably prefer to do this manually....
var resp = _connection.Query<GroupDb>("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id);
_connection.Query<GroupDb>(SQLiteProcedures.UPDATE_GROUP, @group.Name, @group.Id);
return OperationResponse.UPDATED;
}
@ -569,34 +470,26 @@ namespace SQLiteRepository
public OperationResponse DeleteLog(TimeLog log)
{
var query = _connection.Query<TimeLogDb>(
"select * from TimeLogDb where Id=?", log.Id);
SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id);
if (!query.Any())
return OperationResponse.FAILED;
UpdateExistingLogDirections(log);
_connection.ExecuteScalar<TimeLogDb>("delete from TimeLogDb where Id=?", log.Id);
_connection.ExecuteScalar<TimeLogDb>(SQLiteProcedures.DELETE_TIMELOG_ENTRY, log.Id);
return OperationResponse.DELETED;
}
public OperationResponse CreateLog(TimeLog log)
{
var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
var year = log.EventTime.Year;
log.CalendarWeek = calendarWeek;
log.Year = year;
var dbLog = new TimeLogDb
{
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),
};
log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
log.Year = log.EventTime.Year;
var dbLog = TimeLogConverter.ConvertFromTimeLogDto(log);
dbLog.IdentifierId = ConvertSourceToIdentifierId(log.Source);
#region update in/out directions for manual logs.
UpdateExistingLogDirections(log);
#endregion
@ -608,8 +501,9 @@ namespace SQLiteRepository
public OperationResponse UpdateLog(TimeLog log)
{
var query = _connection.Query<TimeLogDb>(
"select * from TimeLogDb where Id=?", log.Id);
if(!query.Any())
SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id);
if (!query.Any())
return OperationResponse.FAILED;
if (log.CalendarWeek > 52 || log.CalendarWeek < 1)
@ -621,13 +515,160 @@ namespace SQLiteRepository
log.Year = log.EventTime.Year;
}
_connection.ExecuteScalar<TimeLogDb>(
"update TimeLogDb set UserId_FK=?, Direction=?,SwipeEventDateTime=?,CalendarWeek=?,Year=?,Source=? where Id=?",
log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year,
(LogSourceDb) (int) log.Source, log.Id);
SQLiteProcedures.UPDATE_TIMELOG_ENTRY,
log.UserId, (LogDirectionDb)(int)log.Direction, log.EventTime, log.CalendarWeek, log.Year,
(LogSourceDb)(int)log.Source, log.Id);
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)
{
if (timeLog != null)
@ -705,7 +746,7 @@ namespace SQLiteRepository
EventTime = x.SwipeEventDateTime,
UserId = x.UserId_FK,
Year = x.Year
}).OrderBy(x=>x.EventTime.UtcDateTime).ToList();
}).OrderBy(x => x.EventTime.UtcDateTime).ToList();
var dict = new Dictionary<DayOfWeek, DailyLogs>();
var logList = new List<DailyLogs>();
@ -717,7 +758,7 @@ namespace SQLiteRepository
}
//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);
}
@ -797,7 +838,7 @@ namespace SQLiteRepository
else
{
// 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
@ -823,7 +864,7 @@ namespace SQLiteRepository
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,
cardId);
}
@ -872,18 +913,6 @@ namespace SQLiteRepository
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)
{
//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;
for (var i = 0; i < logs.Count; i++)
{
logs[i].Direction = InvertLogDirection(currentlogDirection);
logs[i].Direction = LogDirectionConverter.InvertLogDirection(currentlogDirection);
UpdateLog(logs[i]);
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)
{
case LogDirection.IN:
return LogDirection.OUT;
case LogDirection.OUT:
return LogDirection.IN;
default:
return LogDirection.UNKNOWN;
}
_connection.Query<UserIdentity>(
SQLiteProcedures.UPDATE_USER_DETAILS,
firstName,
lastName,
hoursPerWeek,
isContractor,
userId
);
}
}
}

View File

@ -78,9 +78,17 @@
</ItemGroup>
<ItemGroup>
<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="Extensions\Extensions.cs" />
<Compile Include="GroupDb.cs" />
<Compile Include="LogSourceDb.cs" />
<Compile Include="PolicyDb.cs" />
<Compile Include="SQLiteRepository.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -101,10 +109,10 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<EmbeddedResource Include="UpgradeScripts\0.2.sql" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UpgradeScripts\0.2.sql" />
<None Include="packages.config" />
</ItemGroup>
<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')" />

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>
<add key="NLogConfigFilePath" value="Configs/NLogConfig.xml" />
<add key="DefaultPageSize" value="20" />
<add key="WebsiteHttpPort" value="8800"/>
<add key="WebsiteHttpPort" value="8800" />
<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>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
@ -18,7 +18,7 @@
</dependentAssembly>
<dependentAssembly>
<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>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
@ -36,6 +36,18 @@
<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" />
</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>
</runtime>
</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 DateTime? UtcTimeStamp { get; set; }
public string CardUId { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using System.Reflection;
using System.Web.Http;
using WindowsDataCenter.Helpers;
using Interfaces;
namespace WindowsDataCenter
@ -7,6 +8,12 @@ namespace WindowsDataCenter
[RoutePrefix("api/app")]
public class ApplicationController:ApiController
{
private IRepository _repo;
public ApplicationController(IRepository repo)
{
_repo = repo;
}
[Route("")]
public IHttpActionResult GetAppDetails()
{
@ -22,5 +29,23 @@ namespace WindowsDataCenter
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)
{
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,
DateTime.UtcNow, resp.Direction);
return Ok(new {Id = logId, resp.Direction});

View File

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

View File

@ -55,14 +55,19 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<DocumentationFile>bin\ReleaseInstallers\WindowsDataCenter.XML</DocumentationFile>
</PropertyGroup>
<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">
<HintPath>..\packages\DalSoft.WebApi.HelpPage.0.0.7.0\lib\net451\DalSoft.WebApi.HelpPage.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Reference Include="Microsoft.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.1.0\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<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>
<Private>True</Private>
</Reference>
<Reference Include="Ninject, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll</HintPath>
<Reference Include="Ninject, Version=3.3.4.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.3.3.4\lib\net45\Ninject.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Extensions.ContextPreservation, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.ContextPreservation.3.2.0.0\lib\net45-full\Ninject.Extensions.ContextPreservation.dll</HintPath>
<Reference Include="Ninject.Extensions.ContextPreservation, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.ContextPreservation.3.3.1\lib\net45\Ninject.Extensions.ContextPreservation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Extensions.NamedScope, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.NamedScope.3.2.0.0\lib\net45-full\Ninject.Extensions.NamedScope.dll</HintPath>
<Reference Include="Ninject.Extensions.Factory, Version=3.3.2.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.Factory.3.3.2\lib\net45\Ninject.Extensions.Factory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Extensions.Xml, Version=3.2.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>
<Reference Include="Ninject.Extensions.NamedScope, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.NamedScope.3.3.0\lib\net45\Ninject.Extensions.NamedScope.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Web.Common, Version=3.2.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>
<Reference Include="Ninject.Extensions.Xml, Version=3.3.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Extensions.Xml.3.3.0\lib\net45\Ninject.Extensions.Xml.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Web.Common.OwinHost, Version=3.2.0.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>
<Reference Include="Ninject.Web.Common, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.Common.3.3.1\lib\net45\Ninject.Web.Common.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Web.WebApi, Version=3.2.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.WebApi.3.2.4.0\lib\net45-full\Ninject.Web.WebApi.dll</HintPath>
<Reference Include="Ninject.Web.Common.OwinHost, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Ninject.Web.Common.OwinHost.3.3.1\lib\net45\Ninject.Web.Common.OwinHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ninject.Web.WebApi.OwinHost, Version=3.2.0.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>
<Reference Include="Ninject.Web.Common.WebHost, Version=3.3.1.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<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>
</Reference>
<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>
<Private>True</Private>
</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.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -162,11 +179,12 @@
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" />
<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>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\Ninject.Web.Common.cs" />
<Compile Include="CardData.cs" />
<Compile Include="Controllers\ApplicationController.cs" />
<Compile Include="Controllers\CardsController.cs" />
@ -196,6 +214,9 @@
<Compile Include="Controllers\ValuesController.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Help\DalSoft.WebApi.HelpPage.Views\HelpPage.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Minifier\MinifierConfig.xml" />
<Content Include="NinjectConfig.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
@ -221,6 +242,9 @@
<Content Include="www\css\bootstrap.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\css\highlightjs.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="www\css\knockout.contextmenu.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@ -245,9 +269,27 @@
<Content Include="www\js\bootstrap.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</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">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@ -309,9 +351,6 @@
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Help\DalSoft.WebApi.HelpPage.Views\HelpPage.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="sqlite3.def" />
<Content Include="sqlite3.dll" />
</ItemGroup>
@ -330,6 +369,9 @@
</ProjectReference>
</ItemGroup>
<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')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@ -337,9 +379,6 @@
</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'))" />
</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.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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="Microsoft.AspNet.Razor" version="3.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.OwinSelfHost" version="5.2.3" 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.Host.HttpListener" 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.Web.Infrastructure" version="1.0.0.0" 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.Extensions.ContextPreservation" version="3.2.0.0" targetFramework="net452" />
<package id="Ninject.Extensions.NamedScope" version="3.2.0.0" targetFramework="net452" />
<package id="Ninject.Extensions.Xml" version="3.2.0.0" targetFramework="net452" />
<package id="Ninject.Web.Common" version="3.2.3.0" targetFramework="net452" />
<package id="Ninject.Web.Common.OwinHost" version="3.2.3.0" targetFramework="net452" />
<package id="Ninject.Web.WebApi" version="3.2.4.0" targetFramework="net452" />
<package id="Ninject.Web.WebApi.OwinHost" version="3.2.4.0" targetFramework="net452" />
<package id="Ninject" version="3.3.4" targetFramework="net452" />
<package id="Ninject.Extensions.ContextPreservation" version="3.3.1" targetFramework="net452" />
<package id="Ninject.Extensions.Factory" version="3.3.2" targetFramework="net452" />
<package id="Ninject.Extensions.NamedScope" version="3.3.0" targetFramework="net452" />
<package id="Ninject.Extensions.Xml" version="3.3.0" targetFramework="net452" />
<package id="Ninject.Web.Common" version="3.3.1" targetFramework="net452" />
<package id="Ninject.Web.Common.OwinHost" version="3.3.1" 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="RazorEngine" version="3.7.2" 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>

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/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://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>
<body>
<nav class="navbar navbar-default">
@ -37,14 +40,14 @@
</div>
</div>
</nav>
<div id="GroupAdminPage" class="container">
<div id="GroupAdminPage" class="container">
<div class="row">
<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">
<span class="glyphicon glyphicon-plus"></span>
</button>
</div>
<hr/>
<hr />
<div class="row">
<div data-bind="with: groupsList, css:{'col-md-8': !$root.groupFormHidden(), 'col-md-12':$root.groupFormHidden()}">
<table class="table table-striped">
@ -68,15 +71,17 @@
<input type="hidden" name="id" data-bind="value: Id">
<div class="form-group">
<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>
<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>
</form>
</div>
</div>
</div>
<div id="CardManagement" class="container">
<div class="row">
<h2 class="col-md-4">Unassigned Cards</h2>
@ -103,7 +108,64 @@
</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="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>
</body>
</html>

View File

@ -4,6 +4,70 @@
self.groupEditItem = ko.observable(null);
self.unassignedCardList = ko.observable(null);
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 = {
overview: "overview",
group: "groups",
@ -14,7 +78,9 @@
getGroups: "/api/groups",
editGroup: "/api/groups/edit",
getUnassignedCards: "/api/cards/unassigned",
clearUnassignedCards: "/api/cards/unassigned"
clearUnassignedCards: "/api/cards/unassigned",
getPolicy:"/api/app/policy",
savePolicy:"/api/app/policy"
};
self.clearGroupForm = function () {
self.helpers.goToMenuOption(self.uiPages.group);
@ -50,7 +116,7 @@
false);
$.ajax({
url: url,
type: 'DELETE',
type: "DELETE",
success: function () {
console.log("deleted " + groupId);
self.hideGroupForm();
@ -100,6 +166,34 @@
self.padNumber = function (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) {
var date = new Date(dateValue); // dd MM YY HH:mm:ss e.g.: 01 Mar 17 17:34:02
return date.getDate() + " "
@ -114,6 +208,7 @@
this.get("#overview", function () {
self.getGroups();
self.getUnassignedCardData();
self.policyReload();
});
this.post("#editgroup", function () {
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>
<tr>
<th class="col-md-1"></th>
<th class="col-md-3">First Name</th>
<th class="col-md-3">Last Name</th>
<th class="col-md-3">
<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/>
<th/>
@ -303,6 +318,11 @@
</menu>-->
</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 class="modal-dialog">
<div class="modal-content">
@ -330,6 +350,7 @@
</div>
</div>
</div>
<!-- Dialog to create a manual time entry-->
<div id="manualLogDialog" class="modal fade" role="dialog" data-bind="with: manualLog">
<div class="modal-dialog">
<div class="modal-content">
@ -350,7 +371,8 @@
optionsText: function(item) { return item.Text },
optionsValue: function(item){ return item.value },
value: Direction,
optionsCaption: 'Choose...'"></select>
optionsCaption: 'Choose...'"
required></select>
</div>
</div>
<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";
var self = this;
self.helpers = new Helpers();
self.menuOptions = ["Home"];
self.menuOptions = ["Home", "Policy"];
self.possibleLogDirections = ko.observableArray([
{ Text: "In", value: 1 },
{ Text: "Out", value: 2 }
@ -19,6 +25,11 @@
self.selectedCalendarWeek = ko.observable(0);
self.errorData = 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 = {
root: "http://localhost:8800",
getUserList: "/api/users",
@ -29,7 +40,8 @@
getGroups: "/api/groups",
getAppDetails: "/api/app",
manualLogsCreate: "/api/logs/create",
manualLogsDelete: "/api/logs/delete"
manualLogsDelete: "/api/logs/delete",
getPolicy: "/api/app/policy"
};
self.uiPages = {
users: "users",
@ -216,25 +228,32 @@
});
}
};
self.getUserList = function (pageSize, pageNumber, groupId) {
var args = null;
self.getUserList = function (pageSize, pageNumber, groupId, sort) {
var args = [];
if (pageSize && pageNumber) {
args = [
args.push(
{
key: "pageSize",
value: pageSize
},
});
args.push(
{
key: "pageNumber",
value: pageNumber
});
}
];
} else if(groupId) {
args = [
if (groupId) {
args.push(
{
key: "groupId",
value: groupId
}];
});
}
if (sort) {
args.push({
key: "sort",
value: sort
});
}
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList, args, false);
$.getJSON(url, function (res) {
@ -256,9 +275,9 @@
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUserList");
});
};
self.searchUsers = function(query) {
self.searchUsers = function(query, sort) {
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,
function(res) {
self.userList(res);
@ -336,13 +355,25 @@
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) {
var url = self.helpers.createRequestUrl(self.apiEndpoints.manualLogsCreate, null, false, false);
$.post(url, newLog, function () {
}, "json")
.done(function () {
self.manualLog(null);
$('#manualLogDialog').modal("hide");
$("#manualLogDialog").modal("hide");
location.reload(); //stay on this users logs page, but just reload the timelogs.
})
.fail(function (resp, status, error) {
@ -379,17 +410,18 @@
self.assignUpdateHandler();
};
function createlog(data, event) {
var logDateInitVal = moment(self.selectedTimeLogDate()).add((data.Day - 1), 'days').toISOString();
self.manualLog({
CalendarWeek:-1,
Direction:-1,
EventTime: new Date().toISOString(),
EventTime: logDateInitVal,
Id: -1,
IdentifierId: -1,
UserId: self.chosenTimeLogUserId,
Year: 0
});
$('#manualLogDialog').modal("show");
self.initialiseManualLogDateTimePicker(self.selectedTimeLogDate());
self.initialiseManualLogDateTimePicker(logDateInitVal);
self.assignUpdateHandler();
};
function deleteLog(data) {
@ -408,28 +440,63 @@
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 () {
this.get("#users", function () {
var query = this.params.query;
var pageSize = this.params.pageSize;
var pageNumber = this.params.pageNumber;
var groupId = this.params.groupId;
var sort = convertSortOption(this.params.sort);
console.log(sort);
self.chosenMenuItemId("Home");
self.groupsList(null);
self.chosenUserDetails(null);
self.userList(null);
self.userTimeLogData(null);
self.policyData(null);
self.manualLog(null);
if (self.appDetails() === null) {
self.getAppDetails();
}
self.getGroups(function (data) { self.groupsList(data); });
if (query)
self.searchUsers(query);
self.searchUsers(query, sort);
else if (groupId && groupId > 0)
self.getUserList(null, null, groupId);
self.getUserList(null, null, groupId, sort);
else
self.getUserList(pageSize, pageNumber);
self.getUserList(pageSize, pageNumber, null,sort);
});
this.get("#userData/:userId", function () {
self.chosenMenuItemId("Data");
@ -437,6 +504,7 @@
self.chosenUserDetails(null);
self.userList(null);
self.userTimeLogData(null);
self.policyData(null);
self.manualLog(null);
self.getUserDetails(this.params.userId);
self.getUnassignedCardData();
@ -454,12 +522,14 @@
self.userList(null);
self.userTimeLogData(null);
self.manualLog(null);
self.policyData(null);
self.getTimeLogData(this.params.userId, self.selectedTimeLogDate());
});
this.get("#newUser", function () {
self.chosenMenuItemId("newUser");
self.userList(null);
self.userTimeLogData(null);
self.policyData(null);
self.chosenUserDetails({
"UserId": -1,
"FirstName": null,
@ -478,6 +548,16 @@
this.get("#stats", function () {
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 () {
$.each(self.chosenUserDetails().AssociatedIdentifiers,
function (k, v) {
@ -496,7 +576,6 @@
this.post("#manualLog",
function() {
self.createManualLog(self.manualLog());
$('#manualLogDialog').modal("hide");
//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="DefaultPageSize" value="20" />
<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>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
@ -31,6 +31,18 @@
<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" />
</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>
</runtime>
</configuration>

View File

@ -57,6 +57,10 @@
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</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">
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
<Private>True</Private>

BIN
Tools/Sentinel.zip Normal file

Binary file not shown.