Compare commits

...

12 Commits

Author SHA1 Message Date
Chris Watts
8c14a30a63 Add stub of v2 react ui 2023-04-11 20:17:20 +01:00
Chris Watts
005da7ce2b create initial react project for flexitime v2 application.
includes .net webapi backend and ui test stubs
2021-03-22 14:54:42 +00:00
090152ff11 add missing favicon.ico 2020-02-26 08:56:51 +00:00
1ab31f6d75 Urgent fixes for inability to create policy with blank Db
also inability to save policy.
2020-02-26 08:31:39 +00:00
e504a09522 update installer for 0.2.2.0 2020-02-26 08:28:58 +00:00
7557a29350 fix issue with pre-setting the date for the dialog when in manual log creation screen.
still not great as its still an issue with context menu getting relevant context.. its like when there is 1 data entry, columns get created and when you right click an empty cell, then it cannot get the context for the cell.
#82
2020-02-25 16:07:03 +00:00
fe5bcd3256 Merge branch 'Release0.2' 2020-02-25 15:14:36 +00:00
b0f7e27dbe Merge branch '#51_-_Add_Sorting_To_Users_List' into Release0.2 2020-02-25 15:12:36 +00:00
b5aeadfc8f fix conditional highlighting to show which sort option is selected
#51
2020-02-25 15:05:55 +00:00
20bf968675 Added sorting to user list, ASCending and DESCending on first and last name
fixed pagination where it wasnt rounding up so <x.5 would show not 1 page less than required.
fix issue introduced in last branch where group drop down/filter wouldnt work.
#51
2020-02-25 14:48:49 +00:00
3699aff489 Merge commit 'a9d8e4c015d964b15ddf766327dfa3b0aae55a02' 2018-06-06 12:13:25 +01:00
dad901ef31 Merge commit 'refs/tags/0.2.0.1111^{}' 2018-06-06 12:13:00 +01:00
267 changed files with 44663 additions and 580 deletions

6
.gitignore vendored
View File

@ -8,3 +8,9 @@
**.csproj.user
*spa.min.*
**spa.min.*
/FlexitimeUI/_ReSharper.Caches/*
*/_ReSharper.Caches/*
**/_ReSharper.Caches/**
**v3.ncrunchsolution.user
**UpgradeLog*.htm

View File

@ -2,7 +2,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="FlexiTimeSystemInstaller"
Version="0.2.1.0"
Version="0.2.3.0"
Manufacturer="Chris Watts"
UpgradeCode="d38e92db-48f9-40c3-9a6f-d76fbd07326e">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">

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>

View File

@ -72,6 +72,7 @@
<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,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,30 +1,33 @@
using System.Collections.Generic;
namespace Interfaces
{
public class UserList
{
public UserList()
{
Users = new List<User>();
PageSize = 10;
}
public string Query { get; set; }
public int UserCount { get { return Users.Count; } }
public int TotalUserCount { get; set; }
public List<User> Users { get; set; }
public int PageCount
{
get
{
if (TotalUserCount < PageSize)
return 1;
return (TotalUserCount / PageSize);
}
}
public Group GroupFilter { get; set; }
public int PageSize { get; set; }
public int PageNumber { get; set; }
}
using System;
using System.Collections.Generic;
namespace Interfaces
{
public class UserList
{
public UserList()
{
Users = new List<User>();
PageSize = 10;
}
public string Query { get; set; }
public int UserCount { get { return Users.Count; } }
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 (int)Math.Ceiling(Convert.ToDouble(TotalUserCount) / Convert.ToDouble(PageSize));
}
}
public Group GroupFilter { get; set; }
public int PageSize { get; set; }
public int PageNumber { get; set; }
}
}

View File

@ -16,20 +16,18 @@ namespace SQLiteRepository
nameof(TimeLogDb.CalendarWeek) + "=? and " + nameof(TimeLogDb.Year) + "=?)";
public const string GET_ALL_USERS =
"select * from " + nameof(UserIdentity) + " ut "
"select * 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') )"
+ "order by "
+ nameof(UserIdentity.LastName) + " collate nocase, "
+ nameof(UserIdentity.FirstName) + " collate nocase";
+ " 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') )"
+ "order by ? collate nocase ?, ? collate nocase ?";
public const string GET_ALL_USERS_PAGINATE =
"select * from " + nameof(UserIdentity) + " ut "
@ -43,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) + "=?";
@ -96,7 +94,18 @@ 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) + "=?";

View File

@ -45,10 +45,10 @@ namespace SQLiteRepository
CheckForDbUpgrade();
}
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 = GetUserList(groupId, pageSize, pageNumber);
List<UserIdentity> users = GetUserList(groupId, pageSize, pageNumber, sort);
var userCount = GetUserCount();
@ -115,7 +115,7 @@ namespace SQLiteRepository
ret.Users.Add(userObj);
}
ret.PageSize = 20;
ret.PageNumber = (int)Math.Ceiling((double)ret.Users.Count/(double)ret.PageSize);
ret.PageNumber = (int)Math.Ceiling((double)ret.Users.Count / (double)ret.PageSize);
return ret;
}
@ -249,7 +249,7 @@ namespace SQLiteRepository
_connection.Query<CardUniqueId>(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId)
.Select(IdentifierConverter.ConvertToIdentifierDto).ToList();
var cardsToRemove = currentCards.Exclude(user.AssociatedIdentifiers, x => x.Id).Select(x => x.Id).ToList();
#region Update/Create User
int userId;
@ -290,7 +290,7 @@ namespace SQLiteRepository
#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);
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);
@ -317,7 +317,7 @@ namespace SQLiteRepository
else
{
_logger.Debug("Using supplied log time: {0}", logTime.ToString("o"));
}
}
#endregion
var ret = new LogEventResponse();
@ -336,7 +336,7 @@ 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;
}
@ -373,11 +373,11 @@ namespace SQLiteRepository
#endregion
#region Get the current time (for swiping). and calendar week/year to help recall the data.
var calendarWeek = GetIso8601CalendarWeek(logTime);
var year = logTime.Year;
#endregion
var timeLog = new TimeLogDb
{
SwipeEventDateTime = DateTime.UtcNow,
@ -397,7 +397,7 @@ namespace SQLiteRepository
ret.ProcessResponse = OperationResponse.SUCCESS;
return ret;
}
public OperationResponse CreateGroup(Group group, out int groupId)
{
var query = _connection.Query<GroupDb>(SQLiteProcedures.GET_GROUP_BY_NAME, group.Name);
@ -485,8 +485,8 @@ namespace SQLiteRepository
public OperationResponse CreateLog(TimeLog log)
{
log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
log.Year= log.EventTime.Year;
log.Year = log.EventTime.Year;
var dbLog = TimeLogConverter.ConvertFromTimeLogDto(log);
dbLog.IdentifierId = ConvertSourceToIdentifierId(log.Source);
@ -497,13 +497,13 @@ namespace SQLiteRepository
_connection.Insert(dbLog);
return OperationResponse.CREATED;
}
public OperationResponse UpdateLog(TimeLog log)
{
var query = _connection.Query<TimeLogDb>(
SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id);
if(!query.Any())
if (!query.Any())
return OperationResponse.FAILED;
if (log.CalendarWeek > 52 || log.CalendarWeek < 1)
@ -516,8 +516,8 @@ namespace SQLiteRepository
}
_connection.ExecuteScalar<TimeLogDb>(
SQLiteProcedures.UPDATE_TIMELOG_ENTRY,
log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year,
(LogSourceDb) (int) log.Source, log.Id);
log.UserId, (LogDirectionDb)(int)log.Direction, log.EventTime, log.CalendarWeek, log.Year,
(LogSourceDb)(int)log.Source, log.Id);
return OperationResponse.UPDATED;
}
@ -526,8 +526,19 @@ namespace SQLiteRepository
{
var query = $"select * from {nameof(PolicyDb)}";
var policies = _connection.Query<PolicyDb>(query);
if (!policies.Any())
{
return new Policy()
{
ChangeAuthor = string.Empty,
ChangeDate = DateTime.UtcNow,
ChangeDescription = string.Empty,
Html=string.Empty,
Markdown = string.Empty,
Version = "0"
};
}
return PolicyConverter.ConvertToPolicyDto(policies.OrderByDescending(x => x.Version).FirstOrDefault());
}
@ -535,7 +546,7 @@ namespace SQLiteRepository
{
if (string.IsNullOrEmpty(policy.Version))
{
policy.Version = (GetNextPolicyVersion()+1).ToString();
policy.Version = (GetNextPolicyVersion() + 1).ToString();
}
var policyDb = PolicyConverter.ConvertFromPolicyDto(policy);
_connection.Insert(policyDb);
@ -555,24 +566,56 @@ namespace SQLiteRepository
return _connection.ExecuteScalar<int>(SQLiteProcedures.GET_TOTAL_USER_COUNT);
}
private List<UserIdentity> GetUserList(int groupId = -1, int pageSize=-1, int pageNumber=-1)
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)
{
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS_PAGINATE,
pageSize, (pageNumber - 1) * pageSize);
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>(
SQLiteProcedures.GET_ALL_USERS_BY_GROUP,
groupId);
_connection.Query<UserIdentity>(qString);
}
else
{
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS);
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS,
orderByFirst, firstOrderDir,
orderBySecond, secondOrderDir);
}
return users;
@ -620,7 +663,7 @@ namespace SQLiteRepository
foreach (var upgradeScript in upgradeScripts)
{
using (var stream = assembly.GetManifestResourceStream(upgradeScript))
using(var str = new StreamReader(stream))
using (var str = new StreamReader(stream))
{
var script = str.ReadToEnd();
_logger.Trace("Executing upgrade script with name: {0}", upgradeScript);
@ -633,7 +676,7 @@ namespace SQLiteRepository
private void SetDbVersion(string vers)
{
_connection.DeleteAll<DbVersion>();
_connection.Insert(new DbVersion {VersionNumber = vers});
_connection.Insert(new DbVersion { VersionNumber = vers });
_logger.Trace("Set Database version to: {0}", vers);
}
@ -714,7 +757,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>();
@ -726,7 +769,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);
}

View File

@ -1,217 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Fragment>
<PropertyRef Id="STARTSERVICEONINSTALL"/>
<ComponentGroup Id="DataCenterComponents">
<ComponentRef Id="URLReservation"/>
<ComponentRef Id="DataCenterFirewallConfig"/>
<ComponentGroupRef Id="DataCenterFileGroup"/>
<ComponentRef Id="ServiceConfiguration"/>
</ComponentGroup>
<ComponentGroup Id="DataCenterFileGroup" Directory="INSTALLFOLDER">
<Component Id="MicrosoftOwin" Guid="{45563976-8620-4447-9DFE-216DEF263D5A}">
<File Id="Microsoft.Owin.dll"
Source="$(var.WindowsDataCenter.TargetDir)\Microsoft.Owin.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinFileSystems" Guid="{45B6E4C5-FDD3-4B59-84A3-C58C8626BF61}">
<File Id="Microsoft.Owin.FileSystems.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.FileSystems.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinHostHttpListener" Guid="{5C9206E7-8C1B-4713-BE3B-8E754172ABE5}">
<File Id="Microsoft.Owin.Host.HttpListener.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.Host.HttpListener.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinHosting" Guid="{1A250453-173F-4FC6-BD72-5E3B3A67569B}">
<File Id="Microsoft.Owin.Hosting.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.Hosting.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinStaticFiles" Guid="{8F6B1D17-8D7C-4320-8BF6-26D6B3C5115E}">
<File Id="Microsoft.Owin.StaticFiles.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.StaticFiles.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftWebInsfrastructure" Guid="{BCBEE2B7-3E75-4744-8B95-5928E69A8807}">
<File Id="Microsoft.Web.Infrastructure.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Web.Infrastructure.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NewtonsoftJson" Guid="{972634F3-DEC1-41D9-B48E-3836D6C1FD1D}">
<File Id="Newtonsoft.Json.dll"
Source="$(var.WindowsDataCenter.TargetDir)Newtonsoft.Json.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="Ninject" Guid="{31D0E89E-F1F7-4B7D-84E9-229D5C7042B5}">
<File Id="Ninject.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsContextPreservationDll" Guid="{580EAE73-1DBF-4F79-9153-8D5055C3A31F}">
<File Id="Ninject.Extensions.ContextPreservation.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.ContextPreservation.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsNamedScopeDll" Guid="{76602BE7-8A83-4BC2-AAA3-2A06F82353F0}">
<File Id="Ninject.Extensions.NamedScope.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.NamedScope.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsXmlDll" Guid="{3809511F-9C51-4C05-AFE5-F59160EDCB71}">
<File Id="Ninject.Extensions.Xml.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.Xml.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebCommon" Guid="{A4D175A1-D68C-4E20-9E52-A0A8052B9E1D}">
<File Id="Ninject.Web.Common.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.Common.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebCommonOwinHost" Guid="{156FF6D0-00DA-46BC-80C9-816F2CA8975E}">
<File Id="Ninject.Web.Common.OwinHost.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.Common.OwinHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebWebApi" Guid="{20D06854-C822-4780-8EDC-D1601A835626}">
<File Id="Ninject.Web.WebApi.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.WebApi.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebWebApiOwinHost" Guid="{024E48A9-CFAA-4AE1-9B55-399AC27E93B0}">
<File Id="Ninject.Web.WebApi.OwinHost.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.WebApi.OwinHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectConfig" Guid="{85F6271F-7251-4E88-B44D-8EBDA4CBD29D}">
<File Id="NinjectConfig.xml"
Source="$(var.WindowsDataCenter.TargetDir)NinjectConfig.xml"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="Owin" Guid="{B4A90231-65AB-40F1-BEEC-EE02B9DBD4EC}">
<File Id="Owin.dll"
Source="$(var.WindowsDataCenter.TargetDir)Owin.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="RazorEngine" Guid="{494124DC-1BD1-42C2-A249-53757289AAA4}">
<File Id="RazorEngine.dll"
Source="$(var.WindowsDataCenter.TargetDir)RazorEngine.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemNetHttpFormatting" Guid="{53AB40B1-7A74-4B6B-9F0E-B1A74EE27467}">
<File Id="System.Net.Http.Formatting.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Net.Http.Formatting.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebHttp" Guid="{B94609B3-B65E-4FB5-B226-B831D8597A9B}">
<File Id="System.Web.Http.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Http.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebHttpOwin" Guid="{99FF5381-765A-4B03-8668-1083ED498CAB}">
<File Id="System.Web.Http.Owin.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Http.Owin.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebHttpWebHost" Guid="{29E7462F-32C3-46E2-8295-FBF561D37E40}">
<File Id="System.Web.Http.WebHost.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Http.WebHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="WebActivatorEx" Guid="{7AD6A619-5946-4E94-AAD3-1AEEB88272C0}">
<File Id="WebActivatorEx.dll"
Source="$(var.WindowsDataCenter.TargetDir)WebActivatorEx.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="WindowsDataCenter" Guid="{CD36814E-8C1B-4AC1-BDC7-D1595D2A4728}">
<File Id="WindowsDataCenter.exe"
Source="$(var.WindowsDataCenter.TargetDir)WindowsDataCenter.exe"
KeyPath="yes"/>
<ServiceInstall Id="DataCenterService"
Start="auto"
ErrorControl="normal"
Type="ownProcess"
Vital="yes"
Name="FlexitimeDataCenterService"
DisplayName="FlexitimeDataCenterService"
Description="Flexitime API Database and Website"
Account="LocalSystem">
<util:PermissionEx User="Everyone"
ServicePauseContinue="yes"
ServiceQueryStatus="yes"
ServiceStart="yes"
ServiceStop="yes"
ServiceUserDefinedControl="yes" />
<ServiceConfig Id="FlexiTimeServiceConfig"
OnInstall="yes"
DelayedAutoStart="0" />
<util:ServiceConfig FirstFailureActionType="restart"
SecondFailureActionType="restart"
ThirdFailureActionType="restart"
ResetPeriodInDays="1"
RestartServiceDelayInSeconds="10" />
</ServiceInstall>
</Component>
<Component Id="StartDataCenterServiceOnInsall"
Guid="{884828DC-A47E-4CEF-AEEB-E46FFDFFBED3}"
KeyPath="yes">
<ServiceControl Id="FlexitimeDataCenterStarter"
Start="install"
Stop="uninstall"
Remove="uninstall"
Name="FlexitimeDataCenterService"
Wait="yes"/>
<Condition><![CDATA[STARTSERVICEONINSTALL <> "false"]]></Condition>
</Component>
<Component Id="WindowsDataCenterExeConfig" Guid="{CBFA53EB-0663-4A79-9990-451FC1105A12}">
<File Id="WindowsDataCenter.exe.config"
Source="$(var.WindowsDataCenter.TargetDir)WindowsDataCenter.exe.config"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="WindowsDataCenterXml" Guid="{35A62230-0052-4EBE-9775-DE0266BADB4E}">
<File Id="WindowsDataCenter.xml"
Source="$(var.WindowsDataCenter.TargetDir)WindowsDataCenter.XML"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<Component Id="ServiceConfiguration" Directory="INSTALLFOLDER" Guid="{E49ABD61-0CCA-45A2-A525-DF85A0493FF7}" KeyPath="yes">
<util:XmlFile Id="SetServicePort"
Action="setValue"
ElementPath="//appSettings/add[\[]@key='WebsiteHttpPort'[\]]/@value"
Value="[SERVICEPORT]"
File="[#WindowsDataCenter.exe.config]"
SelectionLanguage="XPath"
Sequence="1" />
</Component>
</Fragment>
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Fragment>
<PropertyRef Id="STARTSERVICEONINSTALL"/>
<ComponentGroup Id="DataCenterComponents">
<ComponentRef Id="URLReservation"/>
<ComponentRef Id="DataCenterFirewallConfig"/>
<ComponentGroupRef Id="DataCenterFileGroup"/>
<ComponentRef Id="ServiceConfiguration"/>
</ComponentGroup>
<ComponentGroup Id="DataCenterFileGroup" Directory="INSTALLFOLDER">
<Component Id="CastleCore" Guid="{1B3813B5-C09B-47FB-85C7-6D3F37CADD8C}">
<File Id="Castle.Core.dll"
Source="$(var.WindowsDataCenter.TargetDir)\Castle.Core.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwin" Guid="{45563976-8620-4447-9DFE-216DEF263D5A}">
<File Id="Microsoft.Owin.dll"
Source="$(var.WindowsDataCenter.TargetDir)\Microsoft.Owin.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinFileSystems" Guid="{45B6E4C5-FDD3-4B59-84A3-C58C8626BF61}">
<File Id="Microsoft.Owin.FileSystems.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.FileSystems.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinHostHttpListener" Guid="{5C9206E7-8C1B-4713-BE3B-8E754172ABE5}">
<File Id="Microsoft.Owin.Host.HttpListener.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.Host.HttpListener.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinHosting" Guid="{1A250453-173F-4FC6-BD72-5E3B3A67569B}">
<File Id="Microsoft.Owin.Hosting.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.Hosting.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftOwinStaticFiles" Guid="{8F6B1D17-8D7C-4320-8BF6-26D6B3C5115E}">
<File Id="Microsoft.Owin.StaticFiles.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Owin.StaticFiles.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MicrosoftWebInsfrastructure" Guid="{BCBEE2B7-3E75-4744-8B95-5928E69A8807}">
<File Id="Microsoft.Web.Infrastructure.dll"
Source="$(var.WindowsDataCenter.TargetDir)Microsoft.Web.Infrastructure.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NewtonsoftJson" Guid="{972634F3-DEC1-41D9-B48E-3836D6C1FD1D}">
<File Id="Newtonsoft.Json.dll"
Source="$(var.WindowsDataCenter.TargetDir)Newtonsoft.Json.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="Ninject" Guid="{31D0E89E-F1F7-4B7D-84E9-229D5C7042B5}">
<File Id="Ninject.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsContextPreservationDll" Guid="{580EAE73-1DBF-4F79-9153-8D5055C3A31F}">
<File Id="Ninject.Extensions.ContextPreservation.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.ContextPreservation.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsFactory" Guid="{AA81AB6B-A980-4CF5-AC4D-E5E80ECD52C7}">
<File Id="Ninject.Extensions.Factory.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.Factory.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsNamedScopeDll" Guid="{76602BE7-8A83-4BC2-AAA3-2A06F82353F0}">
<File Id="Ninject.Extensions.NamedScope.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.NamedScope.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectExtensionsXmlDll" Guid="{3809511F-9C51-4C05-AFE5-F59160EDCB71}">
<File Id="Ninject.Extensions.Xml.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Extensions.Xml.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebCommon" Guid="{A4D175A1-D68C-4E20-9E52-A0A8052B9E1D}">
<File Id="Ninject.Web.Common.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.Common.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebCommonOwinHost" Guid="{156FF6D0-00DA-46BC-80C9-816F2CA8975E}">
<File Id="Ninject.Web.Common.OwinHost.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.Common.OwinHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebCommonWebHost" Guid="{595CDF8D-3728-4342-BFD9-34AD23294C6C}">
<File Id="Ninject.Web.Common.Web.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.Common.WebHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebWebApi" Guid="{20D06854-C822-4780-8EDC-D1601A835626}">
<File Id="Ninject.Web.WebApi.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.WebApi.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectWebWebApiOwinHost" Guid="{024E48A9-CFAA-4AE1-9B55-399AC27E93B0}">
<File Id="Ninject.Web.WebApi.OwinHost.dll"
Source="$(var.WindowsDataCenter.TargetDir)Ninject.Web.WebApi.OwinHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NinjectConfig" Guid="{85F6271F-7251-4E88-B44D-8EBDA4CBD29D}">
<File Id="NinjectConfig.xml"
Source="$(var.WindowsDataCenter.TargetDir)NinjectConfig.xml"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="Owin" Guid="{B4A90231-65AB-40F1-BEEC-EE02B9DBD4EC}">
<File Id="Owin.dll"
Source="$(var.WindowsDataCenter.TargetDir)Owin.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="RazorEngine" Guid="{494124DC-1BD1-42C2-A249-53757289AAA4}">
<File Id="RazorEngine.dll"
Source="$(var.WindowsDataCenter.TargetDir)RazorEngine.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemDataSQLite" Guid="{C56CC08B-BCC5-494E-9E48-3E01402FFF97}">
<File Id="System.Data.SQLite.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Data.SQLite.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemNetHttpFormatting" Guid="{53AB40B1-7A74-4B6B-9F0E-B1A74EE27467}">
<File Id="System.Net.Http.Formatting.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Net.Http.Formatting.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebHttp" Guid="{B94609B3-B65E-4FB5-B226-B831D8597A9B}">
<File Id="System.Web.Http.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Http.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebHttpOwin" Guid="{99FF5381-765A-4B03-8668-1083ED498CAB}">
<File Id="System.Web.Http.Owin.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Http.Owin.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebHttpWebHost" Guid="{29E7462F-32C3-46E2-8295-FBF561D37E40}">
<File Id="System.Web.Http.WebHost.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Http.WebHost.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SystemWebRazor" Guid="{EB7F6B70-F224-4FD7-93DF-138CC06F01CB}">
<File Id="System.Web.Razor.dll"
Source="$(var.WindowsDataCenter.TargetDir)System.Web.Razor.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="WebActivatorEx" Guid="{7AD6A619-5946-4E94-AAD3-1AEEB88272C0}">
<File Id="WebActivatorEx.dll"
Source="$(var.WindowsDataCenter.TargetDir)WebActivatorEx.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="WindowsDataCenter" Guid="{CD36814E-8C1B-4AC1-BDC7-D1595D2A4728}">
<File Id="WindowsDataCenter.exe"
Source="$(var.WindowsDataCenter.TargetDir)WindowsDataCenter.exe"
KeyPath="yes"/>
<ServiceInstall Id="DataCenterService"
Start="auto"
ErrorControl="normal"
Type="ownProcess"
Vital="yes"
Name="FlexitimeDataCenterService"
DisplayName="FlexitimeDataCenterService"
Description="Flexitime API Database and Website"
Account="LocalSystem">
<util:PermissionEx User="Everyone"
ServicePauseContinue="yes"
ServiceQueryStatus="yes"
ServiceStart="yes"
ServiceStop="yes"
ServiceUserDefinedControl="yes" />
<ServiceConfig Id="FlexiTimeServiceConfig"
OnInstall="yes"
DelayedAutoStart="0" />
<util:ServiceConfig FirstFailureActionType="restart"
SecondFailureActionType="restart"
ThirdFailureActionType="restart"
ResetPeriodInDays="1"
RestartServiceDelayInSeconds="10" />
</ServiceInstall>
</Component>
<Component Id="StartDataCenterServiceOnInsall"
Guid="{884828DC-A47E-4CEF-AEEB-E46FFDFFBED3}"
KeyPath="yes">
<ServiceControl Id="FlexitimeDataCenterStarter"
Start="install"
Stop="uninstall"
Remove="uninstall"
Name="FlexitimeDataCenterService"
Wait="yes"/>
<Condition><![CDATA[STARTSERVICEONINSTALL <> "false"]]></Condition>
</Component>
<Component Id="WindowsDataCenterExeConfig" Guid="{CBFA53EB-0663-4A79-9990-451FC1105A12}">
<File Id="WindowsDataCenter.exe.config"
Source="$(var.WindowsDataCenter.TargetDir)WindowsDataCenter.exe.config"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="WindowsDataCenterXml" Guid="{35A62230-0052-4EBE-9775-DE0266BADB4E}">
<File Id="WindowsDataCenter.xml"
Source="$(var.WindowsDataCenter.TargetDir)WindowsDataCenter.XML"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<Component Id="ServiceConfiguration" Directory="INSTALLFOLDER" Guid="{E49ABD61-0CCA-45A2-A525-DF85A0493FF7}" KeyPath="yes">
<util:XmlFile Id="SetServicePort"
Action="setValue"
ElementPath="//appSettings/add[\[]@key='WebsiteHttpPort'[\]]/@value"
Value="[SERVICEPORT]"
File="[#WindowsDataCenter.exe.config]"
SelectionLanguage="XPath"
Sequence="1" />
</Component>
</Fragment>
</Wix>

View File

@ -1,37 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<ComponentGroup Id="NLogComponents">
<ComponentGroupRef Id="NLoggerGroup"/>
<ComponentGroupRef Id="NLogConfigFiles"/>
</ComponentGroup>
<ComponentGroup Id="NLoggerGroup" Directory="INSTALLFOLDER">
<Component Id="NLoggerDll" Guid="{48765F51-86CA-4AD8-A34E-E4705353E101}">
<File Id="NLogLoggerDll"
Source="$(var.NLogLogger.TargetDir)\NLogLogger.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NLogDll" Guid="{D9D15A6F-9602-4B6B-BB76-AD0768D14780}">
<File Id="NLog.dll"
Source="$(var.NLogLogger.TargetDir)\NLog.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NLogLoggerDllConfig" Guid="{E3808BFF-5156-4123-B108-BECD0A5A99BC}">
<File Id="NLogLoggerDllConfig"
Source="$(var.NLogLogger.TargetDir)\NLogLogger.dll.config"
KeyPath="yes" />
</Component>
</ComponentGroup>
<ComponentGroup Id="NLogConfigFiles" Directory="CONFIGFILES">
<Component Id="NLogConfigFile" Guid="{DE1F65F2-C286-4495-984C-9BF5861A0567}">
<File Id="NLogConfigXml"
Source="$(var.NLogLogger.TargetDir)\NLogConfig.xml"
KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<ComponentGroup Id="NLogComponents">
<ComponentGroupRef Id="NLoggerGroup"/>
<ComponentGroupRef Id="NLogConfigFiles"/>
</ComponentGroup>
<ComponentGroup Id="NLoggerGroup" Directory="INSTALLFOLDER">
<Component Id="NLoggerDll" Guid="{48765F51-86CA-4AD8-A34E-E4705353E101}">
<File Id="NLogLoggerDll"
Source="$(var.NLogLogger.TargetDir)\NLogLogger.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NLogDll" Guid="{D9D15A6F-9602-4B6B-BB76-AD0768D14780}">
<File Id="NLog.dll"
Source="$(var.NLogLogger.TargetDir)\NLog.dll"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="NLogLoggerDllConfig" Guid="{E3808BFF-5156-4123-B108-BECD0A5A99BC}">
<File Id="NLogLoggerDllConfig"
Source="$(var.NLogLogger.TargetDir)\NLogLogger.dll.config"
KeyPath="yes" />
</Component>
</ComponentGroup>
<ComponentGroup Id="NLogConfigFiles" Directory="CONFIGFILES">
<Component Id="NLogConfigFile" Guid="{DE1F65F2-C286-4495-984C-9BF5861A0567}">
<File Id="NLogConfigXml"
Source="$(var.NLogLogger.TargetDir)\NLogConfig.xml"
KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -1,195 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<Directory Id="STATICWWW" Name="www" >
<Directory Id="STATICWWWCSS" Name="css"/>
<Directory Id="STATICWWWFONTS" Name="fonts" />
<Directory Id="STATICWWWJS" Name="js" />
</Directory>
</DirectoryRef>
<ComponentGroup Id="WebSiteComponents">
<ComponentGroupRef Id="WWWStaticFiles"/>
<ComponentGroupRef Id="WWWStaticJs"/>
<ComponentGroupRef Id="WWWStaticFonts"/>
<ComponentGroupRef Id="WWWStaticCss"/>
</ComponentGroup>
<ComponentGroup Id="WWWStaticFiles" Directory="STATICWWW">
<Component Id="IndexHtml" Guid="{418BCB07-AB9B-4A67-AB31-E9378B806A2E}">
<File Id="index.html"
Source="$(var.WindowsDataCenter.TargetDir)\www\index.html"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="AdminHtml" Guid="{3094817F-74B4-4848-A882-7FDC6B401D86}">
<File Id="admin.html"
Source="$(var.WindowsDataCenter.TargetDir)\www\admin.html"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaCss" Guid="{A5998F36-2CD9-4EB3-A008-12AE1018F153}">
<File Id="spa.css"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaMinCss" Guid="{668AC0CC-8139-4B87-8507-44FC83F6FE07}">
<File Id="spa.min.css"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.min.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaJs" Guid="{0A669ADE-9804-4C2A-9970-BF7EFE52F003}">
<File Id="spa.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaMinJs" Guid="{5B29F7F4-F805-4066-8F65-848119AD5EFD}">
<File Id="spa.min.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="AdminJs" Guid="{B529C781-4AE7-48F1-A2E7-6987A37BD225}">
<File Id="admin.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\admin.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="AdminMinJs" Guid="{1AEBE106-FA98-45E6-B2ED-69806BF9AAFE}">
<File Id="admin.min.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\admin.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="HelpersJs" Guid="{4C8A424D-023E-4311-B100-5BA5AA7DFBAD}">
<File Id="Helpers.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\Helpers.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="HelpersMinJs" Guid="{42142DE2-3952-4B14-A64A-12D67DD657C4}">
<File Id="Helpers.min.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\Helpers.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<ComponentGroup Id="WWWStaticJs" Directory="STATICWWWJS">
<Component Id="BootstrapJs" Guid="{DF7034BA-B315-4AB5-983A-6470D773DF27}">
<File Id="Bootstrap.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\bootstrap.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapMinJs" Guid="{48C14202-7096-4FCA-91BA-7BB24F0D5C22}">
<File Id="Bootstrap.min.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\bootstrap.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="KnockoutContextMenuJs" Guid="{C48C75F7-E4C3-45D7-8041-0BF7A8EEC077}">
<File Id="knockout.contextmenu.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\knockout.contextmenu.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<ComponentGroup Id="WWWStaticFonts" Directory="STATICWWWFONTS" >
<Component Id="GlyphiconsHalflingsRegular" Guid="{AB0C30AC-B19F-4CC1-BE18-6C83BDD16950}">
<File Id="Glyphicons_Halflings_RegularEot"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.eot"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularSvg" Guid="{1B67BD41-8328-463C-AEB1-EAA5496D6086}">
<File Id="Glyphicons_Halflings_RegularSvg"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.svg"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularTtf" Guid="{F2B0B4D8-5809-4524-AFD5-9E25F04EA3AE}">
<File Id="Glyphicons_Halflings_RegularTtf"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.ttf"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularWoff" Guid="{5D644DF7-23C6-4B8B-9FCF-F6F9F0A1FFDC}">
<File Id="Glyphicons_Halflings_RegularWoff"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.woff"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularWoff2" Guid="{87EE2C7C-D6A6-424B-A30B-06E771406C71}">
<File Id="Glyphicons_Halflings_RegularWoff2"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.woff2"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<ComponentGroup Id="WWWStaticCss" Directory="STATICWWWCSS">
<Component Id="BootstrapCss" Guid="{EBB792CD-23B2-4806-B92B-CA4CD3293148}">
<File Id="bootstrap.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapCssMap" Guid="{6E7BEFA7-61F3-4740-96A2-9036F6ABF273}">
<File Id="bootstrap.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapMinCss" Guid="{10BBCC84-63EB-4B4A-816B-460888896A79}">
<File Id="bootstrap.min.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.min.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapMinCssMap" Guid="{909F1752-1472-48DD-99DA-116D391FB12B}">
<File Id="bootstrap.min.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.min.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapThemeCss" Guid="{0EA9E1E9-F077-4B75-A829-6CC93AF55684}">
<File Id="bootstrap_theme.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapGridCssMap" Guid="{5732BF4E-5963-49BD-B466-D9D3CF0B137A}">
<File Id="bootstrap_theme.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapThemeMinCss" Guid="{1281A9F1-28E0-4748-B9B4-8C8C68EDD9E5}">
<File Id="bootstrap_theme.min.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.min.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapThemeMinCssMap" Guid="{F054150C-0E9A-4EB8-8EB2-B5BD81D004CE}">
<File Id="bootstrap_theme.min.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.min.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="KnockoutContextMenuCss" Guid="{D5C5C0F3-500C-419C-A327-5342AFF08204}">
<File Id="knockout.contextmenu.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\knockout.contextmenu.css"
KeyPath="yes"
Checksum="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<Directory Id="STATICWWW" Name="www" >
<Directory Id="STATICWWWCSS" Name="css"/>
<Directory Id="STATICWWWFONTS" Name="fonts" />
<Directory Id="STATICWWWJS" Name="js" />
</Directory>
</DirectoryRef>
<ComponentGroup Id="WebSiteComponents">
<ComponentGroupRef Id="WWWStaticFiles"/>
<ComponentGroupRef Id="WWWStaticJs"/>
<ComponentGroupRef Id="WWWStaticFonts"/>
<ComponentGroupRef Id="WWWStaticCss"/>
</ComponentGroup>
<ComponentGroup Id="WWWStaticFiles" Directory="STATICWWW">
<Component Id="FavIcon" Guid="{8A5C2ED6-C261-4733-9E11-63D937AF0222}">
<File Id="favicon.ico"
Source="$(var.WindowsDataCenter.TargetDir)\www\favicon.ico"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="IndexHtml" Guid="{418BCB07-AB9B-4A67-AB31-E9378B806A2E}">
<File Id="index.html"
Source="$(var.WindowsDataCenter.TargetDir)\www\index.html"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="AdminHtml" Guid="{3094817F-74B4-4848-A882-7FDC6B401D86}">
<File Id="admin.html"
Source="$(var.WindowsDataCenter.TargetDir)\www\admin.html"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaCss" Guid="{A5998F36-2CD9-4EB3-A008-12AE1018F153}">
<File Id="spa.css"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaMinCss" Guid="{668AC0CC-8139-4B87-8507-44FC83F6FE07}">
<File Id="spa.min.css"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.min.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaJs" Guid="{0A669ADE-9804-4C2A-9970-BF7EFE52F003}">
<File Id="spa.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="SpaMinJs" Guid="{5B29F7F4-F805-4066-8F65-848119AD5EFD}">
<File Id="spa.min.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\spa.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="AdminJs" Guid="{B529C781-4AE7-48F1-A2E7-6987A37BD225}">
<File Id="admin.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\admin.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="AdminMinJs" Guid="{1AEBE106-FA98-45E6-B2ED-69806BF9AAFE}">
<File Id="admin.min.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\admin.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="HelpersJs" Guid="{4C8A424D-023E-4311-B100-5BA5AA7DFBAD}">
<File Id="Helpers.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\Helpers.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="HelpersMinJs" Guid="{42142DE2-3952-4B14-A64A-12D67DD657C4}">
<File Id="Helpers.min.js"
Source="$(var.WindowsDataCenter.TargetDir)\www\Helpers.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<ComponentGroup Id="WWWStaticJs" Directory="STATICWWWJS">
<Component Id="BootstrapJs" Guid="{DF7034BA-B315-4AB5-983A-6470D773DF27}">
<File Id="Bootstrap.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\bootstrap.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapMinJs" Guid="{48C14202-7096-4FCA-91BA-7BB24F0D5C22}">
<File Id="Bootstrap.min.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\bootstrap.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="KnockoutContextMenuJs" Guid="{C48C75F7-E4C3-45D7-8041-0BF7A8EEC077}">
<File Id="knockout.contextmenu.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\knockout.contextmenu.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="EasyMdeMinJs" Guid="{F5659507-81DF-4ACC-9996-7CBFEF0CBE26}">
<File Id="easymde.min.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\easymde.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="HighlightJsMinJs" Guid="{E37EF672-31A0-45E4-A0ED-707685EA2E81}">
<File Id="highlightjs.min.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\highlightjs.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="HtmlParserMinJs" Guid="{788487B1-CC7C-4E8B-93C0-B865CEA916DB}">
<File Id="htmlparser.min.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\htmlparser.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="MarkedMinJs" Guid="{4B70E7F7-A883-4BB5-9F34-85BA9C585741}">
<File Id="marked.min.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\marked.min.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="PolicyObject" Guid="{6A5A8235-33CA-460C-A721-7078DB51925F}">
<File Id="PolicyObject.Js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\PolicyObject.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<ComponentGroup Id="WWWStaticFonts" Directory="STATICWWWFONTS" >
<Component Id="GlyphiconsHalflingsRegular" Guid="{AB0C30AC-B19F-4CC1-BE18-6C83BDD16950}">
<File Id="Glyphicons_Halflings_RegularEot"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.eot"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularSvg" Guid="{1B67BD41-8328-463C-AEB1-EAA5496D6086}">
<File Id="Glyphicons_Halflings_RegularSvg"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.svg"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularTtf" Guid="{F2B0B4D8-5809-4524-AFD5-9E25F04EA3AE}">
<File Id="Glyphicons_Halflings_RegularTtf"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.ttf"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularWoff" Guid="{5D644DF7-23C6-4B8B-9FCF-F6F9F0A1FFDC}">
<File Id="Glyphicons_Halflings_RegularWoff"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.woff"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="GlyphiconsHalflingsRegularWoff2" Guid="{87EE2C7C-D6A6-424B-A30B-06E771406C71}">
<File Id="Glyphicons_Halflings_RegularWoff2"
Source="$(var.WindowsDataCenter.TargetDir)www\fonts\glyphicons-halflings-regular.woff2"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup>
<ComponentGroup Id="WWWStaticCss" Directory="STATICWWWCSS">
<Component Id="BootstrapCss" Guid="{EBB792CD-23B2-4806-B92B-CA4CD3293148}">
<File Id="bootstrap.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapCssMap" Guid="{6E7BEFA7-61F3-4740-96A2-9036F6ABF273}">
<File Id="bootstrap.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapMinCss" Guid="{10BBCC84-63EB-4B4A-816B-460888896A79}">
<File Id="bootstrap.min.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.min.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapMinCssMap" Guid="{909F1752-1472-48DD-99DA-116D391FB12B}">
<File Id="bootstrap.min.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap.min.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapThemeCss" Guid="{0EA9E1E9-F077-4B75-A829-6CC93AF55684}">
<File Id="bootstrap_theme.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapGridCssMap" Guid="{5732BF4E-5963-49BD-B466-D9D3CF0B137A}">
<File Id="bootstrap_theme.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapThemeMinCss" Guid="{1281A9F1-28E0-4748-B9B4-8C8C68EDD9E5}">
<File Id="bootstrap_theme.min.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.min.css"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="BootstrapThemeMinCssMap" Guid="{F054150C-0E9A-4EB8-8EB2-B5BD81D004CE}">
<File Id="bootstrap_theme.min.css.map"
Source="$(var.WindowsDataCenter.TargetDir)www\css\bootstrap-theme.min.css.map"
KeyPath="yes"
Checksum="yes"/>
</Component>
<Component Id="KnockoutContextMenuCss" Guid="{D5C5C0F3-500C-419C-A327-5342AFF08204}">
<File Id="knockout.contextmenu.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\knockout.contextmenu.css"
KeyPath="yes"
Checksum="yes" />
</Component>
<Component Id="EasyMdeMinCss" Guid="{F84BEE5C-1682-4D1D-97F4-6A68D8090BA1}">
<File Id="easymde.min.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\easymde.min.css"
KeyPath="yes"
Checksum="yes" />
</Component>
<Component Id="HighlightjsMinCss" Guid="{F65BABFC-1EDB-4F60-807B-3BB23FE6E144}">
<File Id="highlightjs.min.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\highlightjs.min.css"
KeyPath="yes"
Checksum="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

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);

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// 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("0.2.1.0")]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: AssemblyVersion("0.2.3.0")]
[assembly: AssemblyFileVersion("0.2.3.0")]

View File

@ -119,7 +119,7 @@
<textarea id="policyEditor"></textarea>
</div>
<div id="preview" class="tab-pane fade in">
<div data-bind="html: policyData.html" class="well-lg"></div>
<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"-->

View File

@ -22,7 +22,7 @@
var escaped = "unknown";
if (parserHandler.dom.length > 0) {
escaped = parserHandler.dom[0].raw.toLowerCase().trim().replace(/ /g, "-");
escaped = parserHandler.dom[0].raw.toLowerCase().trim().replace(/ /g, "-").replace(/#+/g,"");
}
return "<h" + level + " id=\"" + escaped + "\">"
@ -66,7 +66,7 @@
});
self.editor.codemirror.on("changes",
function () {
self.policyData.html(self.editor.options.previewRender(self.editor.value()));
self.policyData.Html(self.editor.options.previewRender(self.editor.value()));
});
self.uiPages = {
overview: "overview",
@ -169,8 +169,9 @@
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() {
//console.log(self.policyData();)rea#
var data = ko.toJS(self.policyData);
$.post(url, data, function() {
}, "json") //todo: check this serialisation as the object is now complex.
.done(function() {
self.policyReload();

File diff suppressed because one or more lines are too long

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/>

View File

@ -3,28 +3,29 @@
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);
self.ChangeDate = ko.observable(data.ChangeDate);
self.ChangeDescription = ko.observable(data.ChangeDescription);
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: ""
ChangeDate: moment().toISOString(),
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);
self.update = function (data) {
if (!data) return;
self.ChangeDate(data.ChangeDate);
self.ChangeDescription(data.ChangeDescription);
self.ChangeAuthor(data.ChangeAuthor);
self.Version(data.Version);
self.Markdown(data.Markdown);
self.Html(data.Html);
}
}

View File

@ -1,4 +1,10 @@
function DataVM() {
const SortOptions = {
LastAsc: "LastNameAscending",
LastDesc: "LastNameDescending",
FirstAsc: "FirstNameAscending",
FirstDesc: "FirstNameDescending"
};
function DataVM() {
"use strict";
var self = this;
self.helpers = new Helpers();
@ -23,6 +29,7 @@
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",
@ -156,7 +163,7 @@
var args = [
{
key: "groupId",
value: datacreateContextMenu
value: data
}
];
var url = self.helpers.createRequestUrl("users", args, false, false);
@ -221,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) {
@ -261,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);
@ -396,18 +410,27 @@
self.assignUpdateHandler();
};
function createlog(data, event) {
var logDateInitVal = moment(self.selectedTimeLogDate()).add((data.Day - 1), 'days').toISOString();
var createDate = "";
if (data && data.UserId && data.IdentifierId) {
createDate = data.EventTime;
} else if (data && data.Day) {
var mondayDate = moment(moment(self.userTimeLogData().SelectedDate).day("Monday").toISOString());
createDate = mondayDate.add(data.Day-1, 'days').toISOString();
} else {
createDate = self.userTimeLogData().SelectedDate;
}
self.manualLog({
CalendarWeek:-1,
Direction:-1,
EventTime: logDateInitVal,
EventTime: createDate,
Id: -1,
IdentifierId: -1,
UserId: self.chosenTimeLogUserId,
Year: 0
});
$('#manualLogDialog').modal("show");
self.initialiseManualLogDateTimePicker(logDateInitVal);
self.initialiseManualLogDateTimePicker(createDate);
self.assignUpdateHandler();
};
function deleteLog(data) {
@ -426,12 +449,46 @@
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);
@ -444,11 +501,11 @@
}
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");
@ -528,7 +585,6 @@
this.post("#manualLog",
function() {
self.createManualLog(self.manualLog());
$('#manualLogDialog').modal("hide");
//self.goToTimeLogs(self.chosenTimeLogUserId, null, [{ key: "selectedDate", value: self.selectedTimeLogDate() }]);
});

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Flexitime.DataAccess\Flexitime.DataAccess.csproj" />
<ProjectReference Include="..\Flexitime.Interfaces\Flexitime.Interfaces.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,97 @@
using System;
using System.Linq;
using Flexitime.DataAccess;
using Flexitime.DataAccess.EF;
using Flexitime.DataAccess.Objects;
using Flexitime.Interfaces;
namespace DBTest
{
class Program
{
static void Main(string[] args)
{
var dbconnprovider = new TestDbConnProvider();
var db = new Database(dbconnprovider);
//var users = db.Users;
//var userDb = new FlexitimeDbContext(dbconnprovider);
//userDb.Database.EnsureCreated();
//var users = userDb.Users.ToList();
//AddUser(userDb);
//AddIdentifier( userDb);
//AddTimeLog(userDb);
//users = userDb.Users.ToList();
//var tlogs = userDb.TimeLogs.ToList();
//var idents = userDb.Identifiers.ToList();
}
private static void AddIdentifier(FlexitimeDbContext flexitimeDb)
{
var ident = new IdentifierDb
{
UniqueId = Guid.NewGuid(),
LastUsed = DateTime.UtcNow,
IsAssociatedToUser = true
};
var createdEntry = flexitimeDb.Add(ident).Entity;
var user = flexitimeDb.Users.First();
user.AssociatedIdentifiers.Add(createdEntry);
flexitimeDb.Update(user);
flexitimeDb.SaveChanges();
flexitimeDb.SaveChanges();
}
private static void AddUser(FlexitimeDbContext flexitimeDb)
{
var user = new UserDb
{
FirstName = "admin",
LastName = "admin",
UserName = "admin",
Password = "P@ssw0rd!",
IsContractor = false,
HoursPerWeek = 37.0
};
flexitimeDb.Add(user);
flexitimeDb.SaveChanges();
}
private static void AddTimeLog(FlexitimeDbContext flexitimeDbContext)
{
var timelog = new TimeLogDb
{
LogTime = DateTimeOffset.UtcNow
};
var ident = flexitimeDbContext.Identifiers.First();
timelog.Identifier = ident;
timelog.IdentifierId = ident.Id;
flexitimeDbContext.Add(timelog);
flexitimeDbContext.SaveChanges();
}
}
class TestDbConnProvider : IConnectionStringProvider
{
public string ConnectionString =>
"Data Source=.\\SQLSERVER2019;Initial Catalog=FlexitimeData;Integrated Security=SSPI;";
}
}

View File

@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Flexitime.DataAccess.EF;
using Flexitime.DataAccess.Objects;
using Flexitime.Interfaces;
using Flexitime.Objects;
using Microsoft.EntityFrameworkCore;
using LogSourceDescriptor = Flexitime.Objects.LogSourceDescriptor;
namespace Flexitime.DataAccess
{
public class Database : IDataAccess
{
private FlexitimeDbContext db;
private IMapper mapper;
public Database(IConnectionStringProvider connectionStringProvider)
{
db = new FlexitimeDbContext(connectionStringProvider);
db.Database.EnsureCreated();
db.InsertBaseData();
mapper = MappingHelper.CreateMapper(new UserProfile(), new TimeLogProfile(), new GroupProfile(),
new TeamProfile(), new PermissionsProfile(), new ApplicationProfile(), new IdentifierProfile(), new LogDescriptorProfile());
}
public Task<List<User>> GetUsers()
{
return Task.Run(() => mapper.Map<List<User>>(db.Users
.Include(x => x.AssociatedIdentifiers)
.Include(x => x.DirectReports)
//.Include(x=>x.Groups)
//.Include(x=>x.Permissions)
.Include(x => x.Team)));
}
public Task<User> GetUserById(Guid id)
{
return Task.Run(()=>mapper.Map<User>(
db.Users.Where(x => x.Id == id)
.Include(x => x.AssociatedIdentifiers)
.Include(x => x.DirectReports)
//.Include(x=>x.Groups)
.Include(x => x.Permissions)
.Include(x => x.Team)
.FirstOrDefault()));
}
public async Task<User> AddUser(User newUser)
{
if (newUser.Id != Guid.Empty)
{
var existingUser = await GetUserById(newUser.Id);
if (existingUser != null)
{
return null;
}
}
newUser.Id = Guid.Empty;
var userToAdd = mapper.Map<UserDb>(newUser);
userToAdd.Id = Guid.Empty;
var result = db.Users.Add(userToAdd);
await db.SaveChangesAsync();
return mapper.Map<User>(result.Entity);
}
public Task<User> GetUserByUsername(string userName)
{
return Task.Run(() => mapper.Map<User>(
db.Users.Where(x => x.UserName == userName)
.Include(x => x.AssociatedIdentifiers)
.Include(x => x.DirectReports)
//.Include(x=>x.Groups)
.Include(x => x.Permissions)
.Include(x => x.Team)
.FirstOrDefault()));
}
public async Task<User> UpdateUser(User user)
{
var userToUpdate = mapper.Map<UserDb>(user);
var existing = db.Users.FirstOrDefault(x => x.Id == user.Id);
existing.AssociatedIdentifiers = userToUpdate.AssociatedIdentifiers;
existing.LineManager = userToUpdate.LineManager;
existing.Permissions = userToUpdate.Permissions;
existing.UserName = userToUpdate.UserName;
existing.DirectReports = userToUpdate.DirectReports;
existing.FirstName = userToUpdate.FirstName;
existing.LastName = userToUpdate.LastName;
existing.IsContractor = userToUpdate.IsContractor;
existing.Groups = userToUpdate.Groups;
existing.HoursPerWeek = userToUpdate.HoursPerWeek;
existing.Password = userToUpdate.Password;
existing.Team = userToUpdate.Team;
existing.TeamId = userToUpdate.Team.Id;
db.Users.Update(existing);
await db.SaveChangesAsync();
return await GetUserById(user.Id);
}
}
internal static class MappingHelper
{
public static IMapper CreateMapper(params Profile[] profiles)
{
var _profiles = profiles.ToList();
var mapperCfg = new MapperConfiguration(x => _profiles.ForEach(x.AddProfile));
mapperCfg.AssertConfigurationIsValid();
return new Mapper(mapperCfg);
}
}
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<User, UserDb>()
.ForMember(x => x.State,
opt => opt.MapFrom(x => (int)x.State))
.ReverseMap();
CreateMap<UserDb, User>()
.ForMember(dst => dst.State,
opt => opt.MapFrom(x => Enum.Parse(typeof(UserState), x.State.ToString())))
.ForMember(dst => dst.DirectReportIds,
opt => opt.MapFrom(src => src.DirectReports.Select(x => x.Id)))
.ReverseMap();
}
}
public class TimeLogProfile : Profile
{
public TimeLogProfile()
{
CreateMap<TimeLog, TimeLogDb>()
.ReverseMap();
CreateMap<TimeLogDb, TimeLog>()
.ReverseMap();
}
}
public class GroupProfile : Profile
{
public GroupProfile()
{
CreateMap<Group, GroupDb>()
.ReverseMap();
CreateMap<GroupDb, Group>()
.ForMember(dst => dst.UserCount, opt => opt.Ignore())
.ReverseMap();
}
}
public class TeamProfile : Profile
{
public TeamProfile()
{
CreateMap<Team, TeamDb>()
.ReverseMap();
CreateMap<TeamDb, Team>()
.ForMember(dst => dst.Users, opt => opt.MapFrom(src => src.Members.Users))
.ReverseMap();
}
}
public class PermissionsProfile : Profile
{
public PermissionsProfile()
{
CreateMap<Permission, PermissionDb>()
.ReverseMap();
CreateMap<PermissionDb, Permission>()
.ReverseMap();
}
}
public class ApplicationProfile : Profile
{
public ApplicationProfile()
{
CreateMap<Application, ApplicationDb>()
.ReverseMap();
CreateMap<ApplicationDb, Application>()
.ReverseMap();
}
}
public class IdentifierProfile : Profile
{
public IdentifierProfile()
{
CreateMap<Identifier, IdentifierDb>()
.ReverseMap();
CreateMap<IdentifierDb, Identifier>()
.ReverseMap();
}
}
public class LogDescriptorProfile : Profile
{
public LogDescriptorProfile()
{
CreateMap<LogSourceDescriptor, LogSourceDescriptorDb>()
.ReverseMap();
CreateMap<LogSourceDescriptorDb, LogSourceDescriptor>()
.ReverseMap();
}
}
}

View File

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Flexitime.DataAccess.Objects;
using Flexitime.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Flexitime.DataAccess.EF
{
public class FlexitimeDbContext : DbContext
{
private readonly IConnectionStringProvider _connectionStringProvider;
public FlexitimeDbContext(IConnectionStringProvider connectionStringProvider)
{
_connectionStringProvider = connectionStringProvider;
}
public DbSet<UserDb> Users { get; set; }
public DbSet<TimeLogDb> TimeLogs { get; set; }
public DbSet<IdentifierDb> Identifiers { get; set; }
public DbSet<ApplicationDb> Applications { get; set; }
public DbSet<GroupDb> Groups { get; set; }
public DbSet<TeamDb> Teams { get; set; }
public DbSet<LogSourceDescriptorDb> LogSources { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionStringProvider.ConnectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new UserMap());
modelBuilder.ApplyConfiguration(new IdentifierMap());
base.OnModelCreating(modelBuilder);
}
public void InsertBaseData()
{
AddAdminUser();
AddRootApplication();
}
private void AddRootApplication()
{
var systemApp = new ApplicationDb
{
Id = Guid.NewGuid(),
Name = "System",
Permissions = new List<PermissionDb>
{
CreatePermission("Add/Edit User", "s.u.e"),
CreatePermission("Delete User", "s.u.d"),
CreatePermission("Add/Edit Group", "s.g.e"),
CreatePermission("Delete Group", "s.g.d"),
CreatePermission("Associate Identifier", "s.i.e")
}
};
var existingSystemApp = Applications.Where(x => x.Name == "System")
.Include(x=>x.Permissions)
.FirstOrDefault();
if (existingSystemApp == null)
{
Applications.Add(systemApp);
var adminUser = Users.FirstOrDefault(x => x.UserName == "admin");
adminUser?.Permissions.AddRange(systemApp.Permissions);
SaveChanges();
}
else
{
var missingPermissions = systemApp.Permissions
.Where(x=> existingSystemApp.Permissions.All(y => y.Tag != x.Tag))
.ToList();
if (missingPermissions.Any())
{
existingSystemApp.Permissions.AddRange(missingPermissions);
Applications.Update(existingSystemApp);
SaveChanges();
}
}
}
private PermissionDb CreatePermission(string name, string tag)
{
return new()
{
Id = Guid.NewGuid(),
Name = name,
Tag = tag
};
}
private void AddAdminUser()
{
if (!Users.Any())
{
Users.Add(new UserDb
{
Id = Guid.NewGuid(),
FirstName = "Admin",
LastName = "Admin",
Password = "P@ssw0rd!",
UserName = "admin"
});
SaveChanges();
}
}
}
public class UserMap : IEntityTypeConfiguration<UserDb>
{
public void Configure(EntityTypeBuilder<UserDb> builder)
{
builder.ToTable("UserDb")
.HasKey(x => x.Id);
builder
.HasOne(x => x.LineManager)
.WithOne().OnDelete(DeleteBehavior.ClientSetNull);
builder
.HasOne(x => x.Team)
.WithOne();
builder
.HasMany(x => x.AssociatedIdentifiers)
.WithOne().OnDelete(DeleteBehavior.Cascade);
}
}
public class IdentifierMap : IEntityTypeConfiguration<IdentifierDb>
{
public void Configure(EntityTypeBuilder<IdentifierDb> builder)
{
builder.ToTable("IdentifierDb")
.HasKey(x => x.Id);
}
}
public class TimeLogsMap : IEntityTypeConfiguration<TimeLogDb>
{
public void Configure(EntityTypeBuilder<TimeLogDb> builder)
{
builder.ToTable("TimeLogDb")
.HasKey(x => x.Id);
}
}
}

View File

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="utf-8"?>
<DirectedGraph GraphDirection="LeftToRight" xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
<Node Id="ApplicationDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="20,611.62,171.696666666667,141.92" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="ApplicationDb" Name="ApplicationDb" />
<Node Id="ApplicationDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="40,651.62,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="ApplicationDb.Name" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="120,651.62,51.6966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="Name" MaxLength="None" Name="Name" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="ApplicationDb.Permissions" Category="Navigation Collection" Bounds="40,707.58,97.5066666666667,25.96" Dependent="PermissionDb" Field="" Inverse="Application" Label="Permissions (*)" Name="Permissions" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;PermissionDb&gt;" />
<Node Id="GroupDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="834.158222459071,780.113078871474,200.160049641927,197.8804" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="GroupDb" Name="GroupDb" UseManualLocation="True" />
<Node Id="GroupDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="854.158192588721,820.113261937001,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="GroupDb.IsPrivate" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="854.158188519711,876.073344944814,64.2833333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="IsPrivate" MaxLength="None" Name="IsPrivate" PropertyAccessMode="PropertyAccessMode.Default" Type="bool" ValueGenerated="None" />
<Node Id="GroupDb.Name" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="934.158369997576,820.113261937001,51.6966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="Name" MaxLength="None" Name="Name" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="GroupDb.Owners" Category="Navigation Property" Bounds="854.158186485206,932.033427952626,77.0500000000001,25.96" Dependent="GroupOwners" Field="" Inverse="Group" Label="Owners (1)" Name="Owners" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="GroupOwners" />
<Node Id="GroupDb.Users" Category="Navigation Property" Bounds="948.441752565935,876.073344944814,65.8766666666667,25.96" Dependent="" Field="" Inverse="Groups" Label="Users (1)" Name="Users" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;UserDb&gt;" />
<Node Id="GroupDbUserDb" Category="EntityType" Annotations="" BaseClass="" Bounds="649.848354695638,306.756624112899,128,141.9203" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="GroupDbUserDb" Name="GroupDbUserDb" />
<Node Id="GroupDbUserDb.GroupsId" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="679.53169148763,402.716824112899,68.6333333333333,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="GroupsId" MaxLength="None" Name="GroupsId" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="None" />
<Node Id="GroupDbUserDb.UsersId" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="679.531684570312,346.756724112899,59.14,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="UsersId" MaxLength="None" Name="UsersId" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="None" />
<Node Id="GroupOwners" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="541.465026855469,821.136654557235,210.766606445312,141.920344042969" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="GroupOwners" Name="GroupOwners" />
<Node Id="GroupOwners.Group" Category="Navigation Property" Bounds="561.465029703776,917.096898600204,70.2766666666666,25.96" Dependent="" Field="" Inverse="Owners" Label="Group (1)" Name="Group" Principal="GroupDb" PropertyAccessMode="PropertyAccessMode.Default" Type="GroupDb" />
<Node Id="GroupOwners.GroupId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="641.465015869141,861.136754557235,63.54,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="GroupId" MaxLength="None" Name="GroupId" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="None" />
<Node Id="GroupOwners.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping] SqlServer:ValueGenerationStrategy: IdentityColumn" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="561.465026855469,861.136754557235,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="int" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="GroupOwners.Owner" Category="Navigation Collection" Bounds="661.741633300781,917.096898600204,70.49,25.96" Dependent="UserDb" Field="" Inverse="" Label="Owner (*)" Name="Owner" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;UserDb&gt;" />
<Node Id="IModel" Category="Model" Annotations="Relational:MaxIdentifierLength: 128 Relational:RelationalModel: Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel SqlServer:ValueGenerationStrategy: IdentityColumn" Bounds="-1.13686837721616E-13,-5.06377059229567,1078.46166666667,1050.42406059802" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" Label="FlexitimeDbContext" ProductVersion="5.0.5" PropertyAccessMode="PropertyAccessMode.Default" />
<Node Id="IdentifierDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="411.92497639974,253.455354581649,207.846735432943,197.880413525391" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="IdentifierDb" Name="IdentifierDb" />
<Node Id="IdentifierDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="431.924987792969,293.455471573836,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="IdentifierDb.IsAssociatedToUser" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="431.92497639974,405.37566810704,121.143333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="IsAssociatedToUser" MaxLength="None" Name="IsAssociatedToUser" PropertyAccessMode="PropertyAccessMode.Default" Type="bool" ValueGenerated="None" />
<Node Id="IdentifierDb.LastUsed" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="511.924994303385,293.455454581649,67.5966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="LastUsed" MaxLength="None" Name="LastUsed" PropertyAccessMode="PropertyAccessMode.Default" Type="DateTime" ValueGenerated="None" />
<Node Id="IdentifierDb.UniqueId" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="431.924992879232,349.415585099227,68.3333333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="UniqueId" MaxLength="None" Name="UniqueId" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="None" />
<Node Id="IdentifierDb.UserDbId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="530.258378499349,349.41556810704,69.5133333333333,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="UserDbId" MaxLength="None" Name="UserDbId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;Guid&gt;" ValueGenerated="None" />
<Node Id="LogSourceDescriptor" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="807.906666666667,283.84,207.883333333333,197.88" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="LogSourceDescriptor" Name="LogSourceDescriptor" />
<Node Id="LogSourceDescriptor.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping] SqlServer:ValueGenerationStrategy: IdentityColumn" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="827.906666666667,323.84,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="int" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="LogSourceDescriptor.LocalDateTime" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="827.906666666667,435.76,97.9066666666666,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="LocalDateTime" MaxLength="None" Name="LocalDateTime" PropertyAccessMode="PropertyAccessMode.Default" Type="DateTimeOffset" ValueGenerated="None" />
<Node Id="LogSourceDescriptor.SourceApiKey" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="827.906666666667,379.8,92.7733333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="SourceApiKey" MaxLength="None" Name="SourceApiKey" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="LogSourceDescriptor.SourceName" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="907.906666666667,323.84,87.8833333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="SourceName" MaxLength="None" Name="SourceName" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="PermissionDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="130,253.455351981552,251.696660970052,197.880416992188" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="PermissionDb" Name="PermissionDb" />
<Node Id="PermissionDb.Application" Category="Navigation Property" Bounds="150.000003051758,405.375668973739,97.1,25.96" Dependent="" Field="" Inverse="Permissions" Label="Application (1)" Name="Application" Principal="ApplicationDb" PropertyAccessMode="PropertyAccessMode.Default" Type="ApplicationDb" />
<Node Id="PermissionDb.ApplicationId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="249.513340657552,349.41556810704,90.3633333333333,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="ApplicationId" MaxLength="None" Name="ApplicationId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;Guid&gt;" ValueGenerated="None" />
<Node Id="PermissionDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="150,293.455468973739,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="PermissionDb.Name" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="309.999994303385,293.455454581649,51.6966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="Name" MaxLength="None" Name="Name" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="PermissionDb.Tag" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="230,293.455454581649,49.9999999999999,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="Tag" MaxLength="None" Name="Tag" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="PermissionDb.UserDbId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="150.000001424153,349.415568973739,69.5133333333334,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="UserDbId" MaxLength="None" Name="UserDbId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;Guid&gt;" ValueGenerated="None" />
<Node Id="TeamDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="339,883.440000000001,171.696666666667,141.92" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="TeamDb" Name="TeamDb" />
<Node Id="TeamDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="359,923.440000000001,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="TeamDb.Name" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="439,923.440000000001,51.6966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="Name" MaxLength="None" Name="Name" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="TeamDb.Users" Category="Navigation Collection" Bounds="359,979.400000000001,64.41,25.96" Dependent="UserDb" Field="" Inverse="Team" Label="Users (*)" Name="Users" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;UserDb&gt;" />
<Node Id="TimeLogDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] Relational:TableName: TimeLogs ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="765.234983723958,34.9364225870201,293.226682942709,197.8804" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="TimeLogDb" Name="TimeLogDb" />
<Node Id="TimeLogDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="785.234985351562,74.9365225870201,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="TimeLogDb.Identifier" Category="Navigation Property" Bounds="785.235002441406,130.89662258702,84.41,25.96" Dependent="" Field="" Inverse="" Label="Identifier (1)" Name="Identifier" Principal="IdentifierDb" PropertyAccessMode="PropertyAccessMode.Default" Type="IdentifierDb" />
<Node Id="TimeLogDb.IdentifierId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="960.788333333334,74.9365225870201,77.6733333333331,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="IdentifierId" MaxLength="None" Name="IdentifierId" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="None" />
<Node Id="TimeLogDb.LogTime" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="865.234991048177,74.9365225870201,65.5533333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="LogTime" MaxLength="None" Name="LogTime" PropertyAccessMode="PropertyAccessMode.Default" Type="DateTimeOffset" ValueGenerated="None" />
<Node Id="TimeLogDb.SourceDescriptor" Category="Navigation Property" Bounds="785.234983723958,186.85672258702,127.913333333333,25.96" Dependent="" Field="" Inverse="" Label="SourceDescriptor (1)" Name="SourceDescriptor" Principal="LogSourceDescriptor" PropertyAccessMode="PropertyAccessMode.Default" Type="LogSourceDescriptor" />
<Node Id="TimeLogDb.SourceDescriptorId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="899.645004069011,130.89662258702,121.176666666667,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="SourceDescriptorId" MaxLength="None" Name="SourceDescriptorId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;int&gt;" ValueGenerated="None" />
<Node Id="UserDb" Category="EntityType" Annotations="ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding Relational:DefaultMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase] Relational:TableMappings: System.Collections.Generic.List`1[Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping] Relational:TableName: Users ServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding" BaseClass="" Bounds="279.384977914315,481.335910514266,472.926718207467,309.800644042969" ChangeTrackingStrategy="ChangeTrackingStrategy.Snapshot" Group="Expanded" IsAbstract="False" Label="UserDb" Name="UserDb" />
<Node Id="UserDb.AssociatedIdentifiers" Category="Navigation Collection" Bounds="587.351693454319,745.176454557235,144.96,25.96" Dependent="IdentifierDb" Field="" Inverse="" Label="AssociatedIdentifiers (*)" Name="AssociatedIdentifiers" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;IdentifierDb&gt;" />
<Node Id="UserDb.DirectReports" Category="Navigation Collection" Bounds="299.385004001194,745.176454557235,107.43,25.96" Dependent="UserDb" Field="" Inverse="" Label="DirectReports (*)" Name="DirectReports" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;UserDb&gt;" />
<Node Id="UserDb.FirstName" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="401.998318888346,577.296110514266,73.7966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="FirstName" MaxLength="None" Name="FirstName" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="UserDb.GroupOwnersId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="428.564997151693,689.216310514267,103.593333333333,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="GroupOwnersId" MaxLength="None" Name="GroupOwnersId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;int&gt;" ValueGenerated="None" />
<Node Id="UserDb.Groups" Category="Navigation Property" Bounds="505.795026245117,577.296110514266,75.37,25.96" Dependent="" Field="" Inverse="Users" Label="Groups (1)" Name="Groups" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;GroupDb&gt;" />
<Node Id="UserDb.HoursPerWeek" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="541.58833396629,633.256210514267,97.59,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="HoursPerWeek" MaxLength="None" Name="HoursPerWeek" PropertyAccessMode="PropertyAccessMode.Default" Type="float" ValueGenerated="None" />
<Node Id="UserDb.Id" Category="Property Primary" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="299.384979248047,521.336010514266,50,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="True" IsRequired="True" IsShadow="False" IsUnicode="True" Label="Id" MaxLength="None" Name="Id" PropertyAccessMode="PropertyAccessMode.Default" Type="Guid" ValueGenerated="ValueGenerated.OnAdd" />
<Node Id="UserDb.IsContractor" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="299.385000632957,633.256210514266,84.6966666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="IsContractor" MaxLength="None" Name="IsContractor" PropertyAccessMode="PropertyAccessMode.Default" Type="bool" ValueGenerated="None" />
<Node Id="UserDb.LastEventDateTime" Category="Property Required" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="436.815008070205,745.176454557235,120.536666666667,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="True" IsShadow="False" IsUnicode="True" Label="LastEventDateTime" MaxLength="None" Name="LastEventDateTime" PropertyAccessMode="PropertyAccessMode.Default" Type="DateTime" ValueGenerated="None" />
<Node Id="UserDb.LastName" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="299.384983723958,577.296110514266,72.6133333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="LastName" MaxLength="None" Name="LastName" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="UserDb.LineManager" Category="Navigation Property" Bounds="562.158335367839,689.216310514267,105.916666666667,25.96" Dependent="" Field="" Inverse="" Label="LineManager (1)" Name="LineManager" Principal="UserDb" PropertyAccessMode="PropertyAccessMode.Default" Type="UserDb" />
<Node Id="UserDb.LineManagerId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="299.385006103516,689.216310514266,99.18,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="LineManagerId" MaxLength="None" Name="LineManagerId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;Guid&gt;" ValueGenerated="None" />
<Node Id="UserDb.Password" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="661.828379652236,521.336010514266,69.5466666666666,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="Password" MaxLength="None" Name="Password" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
<Node Id="UserDb.Permissions" Category="Navigation Collection" Bounds="414.081667299624,633.256210514266,97.5066666666667,25.96" Dependent="PermissionDb" Field="" Inverse="" Label="Permissions (*)" Name="Permissions" Principal="" PropertyAccessMode="PropertyAccessMode.Default" Type="List&lt;PermissionDb&gt;" />
<Node Id="UserDb.Team" Category="Navigation Property" Bounds="467.48166402181,521.336010514266,64.8333333333333,25.96" Dependent="" Field="" Inverse="Users" Label="Team (1)" Name="Team" Principal="TeamDb" PropertyAccessMode="PropertyAccessMode.Default" Type="TeamDb" />
<Node Id="UserDb.TeamId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="379.385016276042,521.336010514266,58.0966666666667,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="TeamId" MaxLength="None" Name="TeamId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;Guid&gt;" ValueGenerated="None" />
<Node Id="UserDb.UserDbId" Category="Property Foreign" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="562.315019124349,521.336010514266,69.5133333333333,25.96" Field="nofield" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="True" IsIndexed="True" IsPrimaryKey="False" IsRequired="False" IsShadow="True" IsUnicode="True" Label="UserDbId" MaxLength="None" Name="UserDbId" PropertyAccessMode="PropertyAccessMode.Default" Type="Nullable&lt;Guid&gt;" ValueGenerated="None" />
<Node Id="UserDb.UserName" Category="Property Optional" AfterSaveBehavior="PropertySaveBehavior.Save" Annotations="Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase] Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]" BeforeSaveBehavior="PropertySaveBehavior.Save" Bounds="611.165041097005,577.296110514267,75.4833333333333,25.96" Field="" IsAlternateKey="False" IsConcurrencyToken="False" IsForeignKey="False" IsIndexed="False" IsPrimaryKey="False" IsRequired="False" IsShadow="False" IsUnicode="True" Label="UserName" MaxLength="None" Name="UserName" PropertyAccessMode="PropertyAccessMode.Default" Type="string" ValueGenerated="None" />
</Nodes>
<Links>
<Link Source="ApplicationDb" Target="ApplicationDb.Id" Category="Contains" />
<Link Source="ApplicationDb" Target="ApplicationDb.Name" Category="Contains" />
<Link Source="ApplicationDb" Target="ApplicationDb.Permissions" Category="Contains" />
<Link Source="GroupDb" Target="GroupDb.Id" Category="Contains" />
<Link Source="GroupDb" Target="GroupDb.IsPrivate" Category="Contains" />
<Link Source="GroupDb" Target="GroupDb.Name" Category="Contains" />
<Link Source="GroupDb" Target="GroupDb.Owners" Category="Contains" />
<Link Source="GroupDb" Target="GroupDb.Users" Category="Contains" />
<Link Source="GroupDbUserDb" Target="GroupDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="745.042771395463,448.676924112899,142.078978384132,323.197119375579" From="GroupDbUserDb.GroupsId" IsUnique="False" Label="1:*" LabelBounds="783.503404761899,515.236310602442,14.0733333333333,15.96" Name="GroupDbUserDb -&gt; GroupDb" To="GroupDb.Id" />
<Link Source="GroupDbUserDb" Target="GroupDbUserDb.GroupsId" Category="Contains" />
<Link Source="GroupDbUserDb" Target="GroupDbUserDb.UsersId" Category="Contains" />
<Link Source="GroupDbUserDb" Target="UserDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="639.958904952466,448.676924112899,19.5410806102647,25.5138844714087" From="GroupDbUserDb.UsersId" IsUnique="False" Label="1:*" LabelBounds="651.317245686478,462.64996247887,14.0733333333334,15.96" Name="GroupDbUserDb -&gt; UserDb" To="UserDb.Id" />
<Link Source="GroupOwners" Target="GroupDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="752.231633300781,884.003590975451,72.9358444603664,3.31028372898231" From="GroupOwners.GroupId" IsUnique="True" Label="1:1" LabelBounds="780.838876398715,865.698730722688,15.54,15.96" Name="GroupOwners -&gt; GroupDb" To="GroupDb.Id" />
<Link Source="GroupOwners" Target="GroupOwners.Group" Category="Contains" />
<Link Source="GroupOwners" Target="GroupOwners.GroupId" Category="Contains" />
<Link Source="GroupOwners" Target="GroupOwners.Id" Category="Contains" />
<Link Source="GroupOwners" Target="GroupOwners.Owner" Category="Contains" />
<Link Source="IModel" Target="ApplicationDb" Category="Contains" />
<Link Source="IModel" Target="GroupDb" Category="Contains" />
<Link Source="IModel" Target="GroupDbUserDb" Category="Contains" />
<Link Source="IModel" Target="GroupOwners" Category="Contains" />
<Link Source="IModel" Target="IdentifierDb" Category="Contains" />
<Link Source="IModel" Target="LogSourceDescriptor" Category="Contains" />
<Link Source="IModel" Target="PermissionDb" Category="Contains" />
<Link Source="IModel" Target="TeamDb" Category="Contains" />
<Link Source="IModel" Target="TimeLogDb" Category="Contains" />
<Link Source="IModel" Target="UserDb" Category="Contains" />
<Link Source="IdentifierDb" Target="IdentifierDb.Id" Category="Contains" />
<Link Source="IdentifierDb" Target="IdentifierDb.IsAssociatedToUser" Category="Contains" />
<Link Source="IdentifierDb" Target="IdentifierDb.LastUsed" Category="Contains" />
<Link Source="IdentifierDb" Target="IdentifierDb.UniqueId" Category="Contains" />
<Link Source="IdentifierDb" Target="IdentifierDb.UserDbId" Category="Contains" />
<Link Source="IdentifierDb" Target="UserDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="515.848341116795,451.33576810704,5.25162363373965E-07,21.0001424072267" From="IdentifierDb.UserDbId" IsUnique="False" Label="1:*" LabelBounds="519.848341379377,453.855839360668,14.0733333333333,15.96" Name="IdentifierDb -&gt; UserDb" To="UserDb.Id" />
<Link Source="LogSourceDescriptor" Target="LogSourceDescriptor.Id" Category="Contains" />
<Link Source="LogSourceDescriptor" Target="LogSourceDescriptor.LocalDateTime" Category="Contains" />
<Link Source="LogSourceDescriptor" Target="LogSourceDescriptor.SourceApiKey" Category="Contains" />
<Link Source="LogSourceDescriptor" Target="LogSourceDescriptor.SourceName" Category="Contains" />
<Link Source="PermissionDb" Target="ApplicationDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="141.807363012455,451.335768973739,69.0932678957193,152.09014912077" From="PermissionDb.ApplicationId" IsUnique="False" Label="1:*" LabelBounds="178.174904050424,528.208066218285,14.0733333333333,15.96" Name="PermissionDb -&gt; ApplicationDb" To="ApplicationDb.Id" />
<Link Source="PermissionDb" Target="PermissionDb.Application" Category="Contains" />
<Link Source="PermissionDb" Target="PermissionDb.ApplicationId" Category="Contains" />
<Link Source="PermissionDb" Target="PermissionDb.Id" Category="Contains" />
<Link Source="PermissionDb" Target="PermissionDb.Name" Category="Contains" />
<Link Source="PermissionDb" Target="PermissionDb.Tag" Category="Contains" />
<Link Source="PermissionDb" Target="PermissionDb.UserDbId" Category="Contains" />
<Link Source="PermissionDb" Target="UserDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="346.478241515172,451.335768973739,21.4011939896662,23.3635735855307" From="PermissionDb.UserDbId" IsUnique="False" Label="1:*" LabelBounds="341.630712297784,464.36847618029,14.0733333333333,15.96" Name="PermissionDb -&gt; UserDb" To="UserDb.Id" />
<Link Source="TeamDb" Target="TeamDb.Id" Category="Contains" />
<Link Source="TeamDb" Target="TeamDb.Name" Category="Contains" />
<Link Source="TeamDb" Target="TeamDb.Users" Category="Contains" />
<Link Source="TimeLogDb" Target="IdentifierDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="627.65160636805,214.780141063669,137.583377355908,75.92064405795" From="TimeLogDb.IdentifierId" IsUnique="False" Label="1:*" LabelBounds="697.409572416184,254.49155076717,14.0733333333333,15.96" Name="TimeLogDb -&gt; IdentifierDb" To="IdentifierDb.Id" />
<Link Source="TimeLogDb" Target="LogSourceDescriptor" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="911.848328430212,232.81682258702,1.37396887112118E-06,42.0231774129799" From="TimeLogDb.SourceDescriptorId" IsUnique="False" Label="1:*" LabelBounds="915.848329117196,245.848411228119,14.0733333333333,15.96" Name="TimeLogDb -&gt; LogSourceDescriptor" To="LogSourceDescriptor.Id" />
<Link Source="TimeLogDb" Target="TimeLogDb.Id" Category="Contains" />
<Link Source="TimeLogDb" Target="TimeLogDb.Identifier" Category="Contains" />
<Link Source="TimeLogDb" Target="TimeLogDb.IdentifierId" Category="Contains" />
<Link Source="TimeLogDb" Target="TimeLogDb.LogTime" Category="Contains" />
<Link Source="TimeLogDb" Target="TimeLogDb.SourceDescriptor" Category="Contains" />
<Link Source="TimeLogDb" Target="TimeLogDb.SourceDescriptorId" Category="Contains" />
<Link Source="UserDb" Target="GroupOwners" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="595.15691982549,791.136554557235,11.258348353236,21.9890675588098" From="UserDb.GroupOwnersId" IsUnique="False" Label="1:*" LabelBounds="584.932531237399,803.042561414501,14.0733333333334,15.96" Name="UserDb -&gt; GroupOwners" To="GroupOwners.Id" />
<Link Source="UserDb" Target="TeamDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="447.618951206691,791.136554557235,23.9253785164266,83.6504204238083" From="UserDb.TeamId" IsUnique="False" Label="1:*" LabelBounds="463.584732296623,826.238739302674,14.0733333333334,15.96" Name="UserDb -&gt; TeamDb" To="TeamDb.Id" />
<Link Source="UserDb" Target="UserDb" Category="Foreign Key" Annotations="Relational:ForeignKeyMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.ForeignKeyConstraint]" Bounds="419.513458251953,451.335906982422,186.029510498047,30" From="UserDb.UserDbId" IsUnique="False" Label="1:*" LabelBounds="500.600439099212,455.453098361185,14.0733333333333,15.96" Name="UserDb -&gt; UserDb" To="UserDb.Id" />
<Link Source="UserDb" Target="UserDb.AssociatedIdentifiers" Category="Contains" />
<Link Source="UserDb" Target="UserDb.DirectReports" Category="Contains" />
<Link Source="UserDb" Target="UserDb.FirstName" Category="Contains" />
<Link Source="UserDb" Target="UserDb.GroupOwnersId" Category="Contains" />
<Link Source="UserDb" Target="UserDb.Groups" Category="Contains" />
<Link Source="UserDb" Target="UserDb.HoursPerWeek" Category="Contains" />
<Link Source="UserDb" Target="UserDb.Id" Category="Contains" />
<Link Source="UserDb" Target="UserDb.IsContractor" Category="Contains" />
<Link Source="UserDb" Target="UserDb.LastEventDateTime" Category="Contains" />
<Link Source="UserDb" Target="UserDb.LastName" Category="Contains" />
<Link Source="UserDb" Target="UserDb.LineManager" Category="Contains" />
<Link Source="UserDb" Target="UserDb.LineManagerId" Category="Contains" />
<Link Source="UserDb" Target="UserDb.Password" Category="Contains" />
<Link Source="UserDb" Target="UserDb.Permissions" Category="Contains" />
<Link Source="UserDb" Target="UserDb.Team" Category="Contains" />
<Link Source="UserDb" Target="UserDb.TeamId" Category="Contains" />
<Link Source="UserDb" Target="UserDb.UserDbId" Category="Contains" />
<Link Source="UserDb" Target="UserDb.UserName" Category="Contains" />
</Links>
<Categories>
<Category Id="Contains" Label="Contains" Description="Whether the source of the link contains the target object" CanBeDataDriven="False" CanLinkedNodesBeDataDriven="True" IncomingActionLabel="Contained By" IsContainment="True" OutgoingActionLabel="Contains" />
<Category Id="EntityType" />
<Category Id="Foreign Key" />
<Category Id="Model" />
<Category Id="Navigation Collection" />
<Category Id="Navigation Property" />
<Category Id="Property Foreign" />
<Category Id="Property Optional" />
<Category Id="Property Primary" />
<Category Id="Property Required" />
</Categories>
<Properties>
<Property Id="AfterSaveBehavior" Group="Property Flags" DataType="System.String" />
<Property Id="Annotations" Description="Annotations" Group="Model Properties" DataType="System.String" />
<Property Id="BaseClass" Description="Base class" Group="Model Properties" DataType="System.String" />
<Property Id="BeforeSaveBehavior" Group="Property Flags" DataType="System.String" />
<Property Id="Bounds" DataType="System.Windows.Rect" />
<Property Id="CanBeDataDriven" Label="CanBeDataDriven" Description="CanBeDataDriven" DataType="System.Boolean" />
<Property Id="CanLinkedNodesBeDataDriven" Label="CanLinkedNodesBeDataDriven" Description="CanLinkedNodesBeDataDriven" DataType="System.Boolean" />
<Property Id="ChangeTrackingStrategy" Description="Change tracking strategy" Group="Model Properties" DataType="System.String" />
<Property Id="Dependent" Description="Dependent entity" Group="Model Properties" DataType="System.String" />
<Property Id="Expression" DataType="System.String" />
<Property Id="Field" Description="Backing field" Group="Model Properties" DataType="System.String" />
<Property Id="From" Description="Target property" Group="Model Properties" DataType="System.String" />
<Property Id="GraphDirection" DataType="Microsoft.VisualStudio.Diagrams.Layout.LayoutOrientation" />
<Property Id="Group" Label="Group" Description="Display the node as a group" DataType="Microsoft.VisualStudio.GraphModel.GraphGroupStyle" />
<Property Id="GroupLabel" DataType="System.String" />
<Property Id="IncomingActionLabel" Label="IncomingActionLabel" Description="IncomingActionLabel" DataType="System.String" />
<Property Id="Inverse" Description="Inverse entity" Group="Model Properties" DataType="System.String" />
<Property Id="IsAbstract" Label="IsAbstract" Description="IsAbstract" Group="Model Properties" DataType="System.Boolean" />
<Property Id="IsAlternateKey" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsConcurrencyToken" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsContainment" DataType="System.Boolean" />
<Property Id="IsEnabled" DataType="System.Boolean" />
<Property Id="IsForeignKey" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsIndexed" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsPrimaryKey" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsRequired" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsShadow" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsUnicode" Group="Property Flags" DataType="System.Boolean" />
<Property Id="IsUnique" Group="Model Properties" DataType="System.Boolean" />
<Property Id="Label" Label="Label" Description="Displayable label of an Annotatable object" DataType="System.String" />
<Property Id="LabelBounds" DataType="System.Windows.Rect" />
<Property Id="MaxLength" DataType="System.String" />
<Property Id="Name" Group="Model Properties" DataType="System.String" />
<Property Id="OutgoingActionLabel" Label="OutgoingActionLabel" Description="OutgoingActionLabel" DataType="System.String" />
<Property Id="Principal" Description="Principal entity" Group="Model Properties" DataType="System.String" />
<Property Id="ProductVersion" Label="Product Version" Description="EF Core product version" Group="Model Properties" DataType="System.String" />
<Property Id="PropertyAccessMode" Group="Property Flags" DataType="System.String" />
<Property Id="TargetType" DataType="System.Type" />
<Property Id="To" Description="Source property" Group="Model Properties" DataType="System.String" />
<Property Id="Type" Description="CLR data type" Group="Model Properties" DataType="System.String" />
<Property Id="UseManualLocation" DataType="System.Boolean" />
<Property Id="Value" DataType="System.String" />
<Property Id="ValueGenerated" Group="Property Flags" DataType="System.String" />
<Property Id="ValueLabel" DataType="System.String" />
</Properties>
<Styles>
<Style TargetType="Node" GroupLabel="EntityType" ValueLabel="True">
<Condition Expression="HasCategory('EntityType')" />
<Setter Property="Background" Value="#FFC0C0C0" />
</Style>
<Style TargetType="Node" GroupLabel="Property Primary" ValueLabel="True">
<Condition Expression="HasCategory('Property Primary')" />
<Setter Property="Background" Value="#FF008000" />
</Style>
<Style TargetType="Node" GroupLabel="Property Optional" ValueLabel="True">
<Condition Expression="HasCategory('Property Optional')" />
<Setter Property="Background" Value="#FF808040" />
</Style>
<Style TargetType="Node" GroupLabel="Property Foreign" ValueLabel="True">
<Condition Expression="HasCategory('Property Foreign')" />
<Setter Property="Background" Value="#FF8080FF" />
</Style>
<Style TargetType="Node" GroupLabel="Property Required" ValueLabel="True">
<Condition Expression="HasCategory('Property Required')" />
<Setter Property="Background" Value="#FFC0A000" />
</Style>
<Style TargetType="Node" GroupLabel="Navigation Property" ValueLabel="True">
<Condition Expression="HasCategory('Navigation Property')" />
<Setter Property="Background" Value="#FF990000" />
</Style>
<Style TargetType="Node" GroupLabel="Navigation Collection" ValueLabel="True">
<Condition Expression="HasCategory('Navigation Collection')" />
<Setter Property="Background" Value="#FFFF3232" />
</Style>
<Style TargetType="Node" GroupLabel="Model" ValueLabel="True">
<Condition Expression="HasCategory('Model')" />
<Setter Property="Background" Value="#FFFFFFFF" />
</Style>
</Styles>
</DirectedGraph>

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Flexitime.Interfaces\Flexitime.Interfaces.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class ApplicationDb
{
public ApplicationDb()
{
Permissions = new List<PermissionDb>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public List<PermissionDb> Permissions { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class GroupDb
{
public GroupDb()
{
IsPrivate = false;
Users = null;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public bool IsPrivate { get; set; }
public List<UserDb> Users { get; set; }
public GroupOwners Owners { get; set; }
public List<PermissionDb> Permissions { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class GroupOwners
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public Guid GroupId { get; set; }
public GroupDb Group { get; set; }
public List<UserDb> Owner { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class IdentifierDb
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Guid UniqueId { get; set; }
public bool IsAssociatedToUser { get; set; }
public DateTime LastUsed { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class LogSourceDescriptorDb
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string SourceName { get; set; }
public DateTimeOffset LocalDateTime { get; set; }
public string SourceApiKey { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class PermissionDb
{
public string Name { get; set; }
public string Tag { get; set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class TeamDb
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public TeamMembers Members{ get; set; }
//team owner/manager?
public UserDb Manager { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Flexitime.DataAccess.Objects
{
public class TeamMembers
{
[Key]
public int Id { get; set; }
public Guid TeamId { get; set; }
public TeamDb Team { get; set; }
public List<UserDb> Users { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class TimeLogDb
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Guid IdentifierId { get; set; }
public IdentifierDb Identifier { get; set; }
public DateTimeOffset LogTime { get; set; }
public LogSourceDescriptorDb SourceDescriptor { get; set; }
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Flexitime.DataAccess.Objects
{
public class UserDb
{
public UserDb()
{
AssociatedIdentifiers = new List<IdentifierDb>();
Groups = new List<GroupDb>();
DirectReports = new List<UserDb>();
Permissions = new List<PermissionDb>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public double HoursPerWeek { get; set; }
public bool IsContractor { get; set; }
public int AssociatedIdentifierCount => AssociatedIdentifiers.Count;
public DateTime LastEventDateTime { get; set; }
public List<IdentifierDb> AssociatedIdentifiers { get; set; }
//TODO: Is this that the user is in the group or this is their groups?
public List<GroupDb> Groups { get; set; }
public int State { get; set; }
public Guid? TeamId { get; set; }
/// <summary>
/// user that belongs to team
/// </summary>
/// <remarks>differs from Groups in that a group is a symbolic collection where a team is a publicly identifiable entity</remarks>
public TeamDb Team { get; set; }
/// <summary>
/// Id of the Users Line Manager
/// </summary>
public UserDb LineManager { get; set; }
/// <summary>
/// Ids of the users direct reports
/// </summary>
public List<UserDb> DirectReports { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public List<PermissionDb> Permissions { get; set; }
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Flexitime.Objects\Flexitime.Objects.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Flexitime.Interfaces
{
public interface IConnectionStringProvider
{
string ConnectionString { get; }
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Flexitime.Objects;
namespace Flexitime.Interfaces
{
public interface IDataAccess
{
Task<List<User>> GetUsers();
Task<User> GetUserById(Guid id);
Task<User> AddUser(User newUser);
Task<User> GetUserByUsername(string userName);
Task<User> UpdateUser(User user);
}
}

View File

@ -0,0 +1,7 @@
namespace Flexitime.Interfaces
{
public interface ITokenFactory
{
string Generate(int size = 32);
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Flexitime.Objects.API
{
public class AssociateIdentifierRequest
{
public Identifier Identifier { get; set; }
public Guid UserId { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace Flexitime.Objects.API
{
public class UsernameValidCheckRequest
{
public string Username { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace Flexitime.Objects
{
public class Application
{
public Application()
{
Permissions = new List<Permission>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public List<Permission> Permissions { get; set; }
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="cd &quot;$(ProjectDir)&quot;&#xD;&#xA;csharptojs $(ProjectDir)" />
</Target>
</Project>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Flexitime.Objects
{
public class Group
{
public Group()
{
IsPrivate = false;
Users = new List<User>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public int UserCount { get; set; }
public bool IsPrivate { get; set; }
public List<User> Users { get; set; }
}
}

View File

@ -0,0 +1,27 @@
using System;
namespace Flexitime.Objects
{
public class Identifier
{
public Identifier() { }
public Guid Id { get; set; }
public string UniqueId { get; set; }
public bool IsAssociatedToUser { get; set; }
public DateTime LastUsed { get; set; }
public override bool Equals(object obj)
{
var identObj = obj as Identifier;
if (identObj == null) return false;
return identObj.Id == Id
&& identObj.IsAssociatedToUser == IsAssociatedToUser
&& identObj.UniqueId == UniqueId;
}
public override int GetHashCode()
{
return Id.GetHashCode() ^ UniqueId.GetHashCode() ^ IsAssociatedToUser.GetHashCode() ^ LastUsed.GetHashCode();
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace Flexitime.Objects
{
public class LogSourceDescriptor
{
public string SourceName { get; set; }
public DateTimeOffset LocalDateTime { get; set; }
public string SourceApiKey { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Flexitime.Objects
{
public class LoginRequest
{
[Required] public string Username { get; set; }
[Required] public string Password { get; set; }
}
}

View File

@ -0,0 +1,27 @@
using System;
namespace Flexitime.Objects
{
public class LoginResponse
{
public LoginResponse()
{
}
public LoginResponse(User user, string token)
{
Id = user.Id;
FirstName = user.FirstName;
LastName = user.LastName;
Username = user.UserName;
Token = token;
}
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Token { get; set; }
public string RefreshToken { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace Flexitime.Objects
{
public class Permission
{
public string Name { get; set; }
public string Tag { get; set; }
public Guid Id { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Flexitime.Objects
{
public class Policy
{
public Version Version { get; set; }
public string DescriptionOfChanges { get; set; }
public User Author { get; set; }
public DateTimeOffset DateOfChange { get; set; }
public string Markdown { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace Flexitime.Objects
{
public class Team
{
public Team()
{
Users = new List<User>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public List<User> Users { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Flexitime.Objects
{
public class TimeLog
{
public string IdentifierId { get; set; }
public DateTimeOffset LogTime { get; set; }
public LogSourceDescriptor SourceDescriptor { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Flexitime.Objects
{
public class TokenLoginRequest : LoginRequest
{
public TokenLoginRequest()
{
Attributes = new List<KeyValuePair<string, string>>();
}
public string SourceName { get; set; }
public string SourceAddress { get; set; }
public List<KeyValuePair<string, string>> Attributes { get; set; }
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
namespace Flexitime.Objects
{
public class User
{
public User()
{
AssociatedIdentifiers = new List<Identifier>();
Groups = new List<Group>();
Permissions = new List<Permission>();
FirstName = string.Empty;
LastName = string.Empty;
DirectReportIds = new List<int>();
Team = null;
}
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public float HoursPerWeek { get; set; }
public bool IsContractor { get; set; }
public int AssociatedIdentifierCount => AssociatedIdentifiers.Count;
public DateTime LastEventDateTime { get; set; }
public List<Identifier> AssociatedIdentifiers { get; set; }
public List<Group> Groups { get; set; }
/// <summary>
/// user that belongs to team
/// </summary>
/// <remarks>differs from Groups in that a group is a symbolic collection where a team is a publically identifiable entity</remarks>
public Team Team { get; set; }
public UserState State { get; set; }
/// <summary>
/// Id of the Users Line Manager
/// </summary>
public User LineManager { get; set; }
/// <summary>
/// Ids of the users direct reports
/// </summary>
public List<int> DirectReportIds { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public List<Permission> Permissions { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace Flexitime.Objects
{
public enum UserState
{
Unknown,
In,
Out,
OutOfOffice,
Remote
}
}

View File

@ -0,0 +1,12 @@
{
"assemblies": [
{
"name": "Flexitime.Objects",
"include": [ "Flexitime.Objects" ]
}
],
"assembliesPath": "./bin/debug/netcoreapp2.2",
"outputPath": "../flexitimeui/src/models",
"noClean": true,
"useNugetCacheResolver": true
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FlexitimeAPI.Constants
{
public static class Constants
{
public const string InvalidAppSettings =
"There is a problem with the structure of the appsettings.json. Require sections or items may be missing or named incorrectly. Details: {0}";
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Threading.Tasks;
using Flexitime.Objects;
using FlexitimeAPI.Helpers;
using FlexitimeAPI.Services;
using Microsoft.AspNetCore.Mvc;
namespace FlexitimeAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class AuthenticationController : ControllerBase
{
private readonly IUserService _userService;
public AuthenticationController(IUserService userService)
{
_userService = userService;
}
[Route("login")]
[HttpPost]
public async Task<IActionResult> Authenticate(LoginRequest model)
{
var response = await _userService.Authenticate(model);
if (response == null)
return BadRequest(new { message = "Username or password is incorrect" });
return Ok(response);
}
[Authorize]
[Route("logout")]
[HttpGet]
public IActionResult Logout()
{
//should be able to get the id here and log a message of that user logging out..
return Ok();
}
[Route("token")]
[HttpPost]
public IActionResult TokenAuthenticationRequest(TokenLoginRequest model)
{
throw new NotImplementedException();
var response = _userService.Authenticate(model);
if (response == null)
return BadRequest(new { message = "Username or password is incorrect" });
return Ok(response);
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Flexitime.Objects.API;
using FlexitimeAPI.Exceptions;
using FlexitimeAPI.Helpers;
using FlexitimeAPI.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace FlexitimeAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class IdentifierController : ControllerBase
{
private readonly IIdentifierService _identifierService;
public IdentifierController(IIdentifierService identifierService)
{
_identifierService = identifierService;
}
[Authorize] //permission?
[HttpPost]
public async Task<IActionResult> Associate(AssociateIdentifierRequest associateIdentifierRequest)
{
try
{
await _identifierService.Associate(associateIdentifierRequest.Identifier,
associateIdentifierRequest.UserId);
return Ok();
}
catch (InvalidUserIdException iUidEx)
{
var modelStateDictionary = new ModelStateDictionary();
modelStateDictionary.AddModelError(nameof(Flexitime.Objects.User.Id), iUidEx.Message);
return BadRequest(modelStateDictionary);
}
}
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using Flexitime.Objects;
using FlexitimeAPI.Helpers;
namespace FlexitimeAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PolicyController : ControllerBase
{
[HttpGet]
public List<Policy> GetPolicies()
{
throw new NotImplementedException();
}
[Authorize(Permissions=new []{"p.w"})]
[HttpPost]
public IActionResult UpdatePolicy(Policy newPolicy)
{
//get the authenticated user and populate the author property
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,37 @@
using Flexitime.Objects;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
namespace FlexitimeAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TeamsController : ControllerBase
{
[HttpGet]
public List<Team> GetTeams()
{
throw new NotImplementedException();
}
[HttpGet]
[Route("{id}")]
public Team GetTeam(int id)
{
throw new NotImplementedException();
}
[HttpPost]
public Team CreateTeam(Team team)
{
throw new NotImplementedException();
}
[HttpPut]
public Team UpdateTeam(Team team)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using Flexitime.Objects;
using Microsoft.AspNetCore.Mvc;
namespace FlexitimeAPI.Controllers
{
[Route("[controller]")]
[ApiController]
public class TimeController : ControllerBase
{
[HttpPost]
[Route("log")]
public LoginResponse StoreLog(TimeLog log)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Flexitime.Objects;
using Flexitime.Objects.API;
using FlexitimeAPI.Exceptions;
using FlexitimeAPI.Helpers;
using FlexitimeAPI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Server.IIS;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.Extensions.Logging;
namespace FlexitimeAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private readonly ILogger<UsersController> _logger;
private readonly IUserService _userService;
public UsersController(ILogger<UsersController> logger, IUserService userService)
{
_logger = logger;
_userService = userService;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var userList = await _userService.GetAll();
if (!userList.Any())
{
return NoContent();
}
return Ok(userList);
}
[Authorize]
[HttpGet]
[Route("{id}")]
public async Task<IActionResult> Get(Guid id)
{
var user = await _userService.GetById(id);
if (user == null)
{
return NotFound();
}
return Ok(user);
}
[Authorize("s.u.e")]
[HttpPost]
public async Task<IActionResult> Create([FromBody] User newUser)
{
try
{
var createdUser = await _userService.Add(newUser);
if (createdUser == null)
{
return BadRequest();
}
return CreatedAtAction(nameof(Get), createdUser.Id);
}
catch (InvalidUserNameException iuex)
{
var modelStateDictionary = new ModelStateDictionary();
modelStateDictionary.AddModelError(nameof(Flexitime.Objects.User.UserName), iuex.Message);
return BadRequest(modelStateDictionary);
}
}
[Authorize("s.u.e")]
[HttpPost]
[Route("usernamevalid")]
public Task<IActionResult> UsernameValid([FromBody] UsernameValidCheckRequest usernameValidCheck)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
namespace FlexitimeAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -0,0 +1,8 @@
namespace FlexitimeAPI.Exceptions
{
public static class ExceptionConstants
{
public const string InvalidUsername = "Invalid Username supplied, the username: {0} is a duplicate or banned.";
public const string InvalidUserId = "Invalid User Id supplied: {0}";
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace FlexitimeAPI.Exceptions
{
public class InvalidUserIdException : Exception
{
public InvalidUserIdException(Guid userId)
: base(BuildExceptionMessage(userId))
{
}
public InvalidUserIdException(Guid userId, Exception exception)
: base(BuildExceptionMessage(userId), exception)
{
}
private static string BuildExceptionMessage(Guid userId)
=> string.Format(ExceptionConstants.InvalidUserId, userId);
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace FlexitimeAPI.Exceptions
{
public class InvalidUserNameException : Exception
{
public InvalidUserNameException(string username)
: base(BuildExceptionMessage(username))
{
}
public InvalidUserNameException(string username, Exception exception)
: base(BuildExceptionMessage(username), exception)
{
}
private static string BuildExceptionMessage(string username)
=> string.Format(ExceptionConstants.InvalidUsername, username);
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="RandomNameGeneratorLibrary" Version="1.2.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Flexitime.DataAccess\Flexitime.DataAccess.csproj" />
<ProjectReference Include="..\Flexitime.Interfaces\Flexitime.Interfaces.csproj" />
<ProjectReference Include="..\Flexitime.Objects\Flexitime.Objects.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Repositories\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
using System;
using System.Diagnostics.CodeAnalysis;
using FlexitimeAPI.Models;
using Newtonsoft.Json;
namespace FlexitimeAPI.Functions
{
[ExcludeFromCodeCoverage]
public static class AppSettingsExtensions
{
public static void Verify<TApplicationSettings>(this AppSettings appSettings)
where TApplicationSettings : AppSettings
{
try
{
var applicationSettingsJson = JsonConvert.SerializeObject(appSettings);
JsonConvert.DeserializeObject<TApplicationSettings>(applicationSettingsJson);
}
catch (Exception exception)
{
throw new Exception(string.Format(Constants.Constants.InvalidAppSettings, exception.Message));
}
}
}
}

View File

@ -0,0 +1,17 @@
using Flexitime.Interfaces;
using FlexitimeAPI.Interfaces;
namespace FlexitimeAPI.Functions
{
public class ConnectionStringProvider : IConnectionStringProvider
{
private readonly IApplicationSettings _appSettings;
public ConnectionStringProvider(IApplicationSettings appSettings)
{
_appSettings = appSettings;
}
public string ConnectionString => _appSettings.ConnectionString;
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Linq;
using Flexitime.Objects;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FlexitimeAPI.Helpers
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public AuthorizeAttribute()
{
Permissions = new string[] { };
}
public AuthorizeAttribute(params string[] permissions)
{
Permissions = permissions;
}
public string[] Permissions { get; set; }
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = (User) context.HttpContext.Items["User"];
if (user == null)
// not logged in
context.Result = new JsonResult(
new { message = "Unauthorized" })
{ StatusCode = StatusCodes.Status401Unauthorized };
else if (Permissions.Any()
&& user.Permissions != null
&& !user.Permissions.Select(y => y.Tag)
.Intersect(Permissions.ToList())
.Any()) //check we have permissions if they have been specified
context.Result = new JsonResult(
new { message = "Unauthorized" })
{ StatusCode = StatusCodes.Status401Unauthorized };
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FlexitimeAPI.Interfaces;
using FlexitimeAPI.Models;
using FlexitimeAPI.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace FlexitimeAPI.Helpers
{
public class JwtMiddleware
{
private readonly IApplicationSettings _appSettings;
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next, IApplicationSettings appSettings)
{
_next = next;
_appSettings = appSettings;
}
public async Task Invoke(HttpContext context, IUserService userService)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
await AttachUserToContext(context, userService, token);
await _next(context);
}
private async Task AttachUserToContext(HttpContext context, IUserService userService, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out var validatedToken);
var jwtToken = (JwtSecurityToken) validatedToken;
var userId = Guid.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// attach user to context on successful jwt validation
context.Items["User"] = await userService.GetById(userId);
}
catch(Exception ex)
{
// do nothing if jwt validation fails
// user is not attached to context so request won't have access to secure routes
}
}
}
}

View File

@ -0,0 +1,19 @@
using Flexitime.Interfaces;
using System;
using System.Security.Cryptography;
namespace FlexitimeAPI.Helpers
{
public class TokenGenerator:ITokenFactory
{
public string Generate(int size = 32)
{
var randomNumber = new byte[size];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Flexitime.Objects;
namespace FlexitimeAPI.Interfaces
{
public interface ITimePersistenceService
{
UserState StoreTime(TimeLog logEntry);
void UpdateTimeLog(Guid logToUpdate, TimeLog updatedLogEntry);
}
public interface IApplicationSettings
{
public string Secret { get; }
public string ConnectionString { get; }
}
public interface ILoginService
{
string GenerateJwtToken(User user);
}
public interface IIdentifierService
{
Task Associate(Identifier identifier, Guid userId);
}
}

View File

@ -0,0 +1,18 @@
using FlexitimeAPI.Interfaces;
using Infrastructure.Functions;
using Newtonsoft.Json;
namespace FlexitimeAPI.Models
{
public class AppSettings:IApplicationSettings
{
[JsonProperty(Required = Required.Always)]
[JsonConverter(typeof(NonEmptyStringConverter))]
public string Secret { get; set; }
[JsonProperty(Required = Required.Always)]
[JsonConverter(typeof(NonEmptyStringConverter))]
public string ConnectionString { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace FlexitimeAPI
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:46914",
"sslPort": 44370
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"FlexitimeAPI": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Flexitime.Objects;
using FlexitimeAPI.Exceptions;
using FlexitimeAPI.Interfaces;
namespace FlexitimeAPI.Services
{
public class IdentifierService : IIdentifierService
{
private readonly IUserService _userService;
public IdentifierService(IUserService userService)
{
_userService = userService;
}
public async Task Associate(Identifier identifier, Guid userId)
{
var user = await _userService.GetById(userId);
if (user == null)
{
throw new InvalidUserIdException(userId);
}
user.AssociatedIdentifiers.Add(identifier);
await _userService.Update(user);
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Flexitime.Objects;
using FlexitimeAPI.Interfaces;
using Microsoft.IdentityModel.Tokens;
namespace FlexitimeAPI.Services
{
public class LoginService : ILoginService
{
private readonly IApplicationSettings _appSettings;
public LoginService(IApplicationSettings appSettings)
{
_appSettings = appSettings;
}
public string GenerateJwtToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var claims = new Dictionary<string, object>
{
{"permissions",user.Permissions.Select(x=>x.Tag)}
};
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] {new Claim("id", user.Id.ToString())}),
Expires = DateTime.UtcNow.AddHours(2),
Issuer = "FlexitimeUI",
NotBefore = DateTime.UtcNow.AddSeconds(-5),
IssuedAt = DateTime.UtcNow,
Claims = claims,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
//tokenHandler.
return tokenHandler.WriteToken(token);
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Flexitime.Objects;
using FlexitimeAPI.Interfaces;
namespace FlexitimeAPI.Services
{
public class TimePersistenceService:ITimePersistenceService
{
public UserState StoreTime(TimeLog logEntry)
{
throw new NotImplementedException();
}
public void UpdateTimeLog(Guid logToUpdate, TimeLog updatedLogEntry)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Flexitime.DataAccess.Objects;
using Flexitime.Interfaces;
using Flexitime.Objects;
using FlexitimeAPI.Exceptions;
using FlexitimeAPI.Interfaces;
using FlexitimeAPI.Models;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using RandomNameGeneratorLibrary;
namespace FlexitimeAPI.Services
{
public interface IUserService
{
Task<LoginResponse> Authenticate(LoginRequest model);
Task<IEnumerable<User>> GetAll();
Task<User> GetById(Guid id);
Task<User> Add(User newUser);
Task<bool> UsernameIsValid(string username);
Task<User> Update(User user);
}
public class UserService : IUserService
{
private readonly IDataAccess _dataSource;
private readonly ILoginService _loginService;
public UserService(IDataAccess dataSource, ILoginService loginService)
{
_dataSource = dataSource;
_loginService = loginService;
}
public async Task<LoginResponse> Authenticate(LoginRequest model)
{
var user = (await _dataSource.GetUsers()).SingleOrDefault(x => x.UserName == model.Username && x.Password == model.Password);
// return null if user not found
if (user == null) return null;
// authentication successful so generate jwt token
var token = _loginService.GenerateJwtToken(user);
return new LoginResponse(user, token);
}
public async Task<IEnumerable<User>> GetAll()
{
return await _dataSource.GetUsers();
}
public async Task<User> GetById(Guid id)
{
return await _dataSource.GetUserById(id);
}
public async Task<User> Add(User newUser)
{
var userNameIsValid = await UsernameIsValid(newUser.UserName);
if (!userNameIsValid)
{
throw new InvalidUserNameException(newUser.UserName);
}
return await _dataSource.AddUser(newUser);
}
public async Task<bool> UsernameIsValid(string userName)
{
if (string.IsNullOrWhiteSpace(userName))
{
return false;
}
//TODO: blacklisted username check
var existingUser = await _dataSource.GetUserByUsername(userName);
return existingUser == null;
}
public async Task<User> Update(User user)
{
if (user == null) return null;
if ((await GetById(user.Id)) == null)
{
return null;
}
return await _dataSource.UpdateUser(user);
}
}
}

View File

@ -0,0 +1,97 @@
using System.IO;
using System.Text.Json.Serialization;
using Flexitime.DataAccess;
using Flexitime.Interfaces;
using FlexitimeAPI.Functions;
using FlexitimeAPI.Helpers;
using FlexitimeAPI.Interfaces;
using FlexitimeAPI.Models;
using FlexitimeAPI.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
namespace FlexitimeAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var settings = InitialiseAppSettings();
services.AddSingleton(typeof(IApplicationSettings), settings);
services.AddSingleton<ILoginService, LoginService>();
services.AddSingleton<IConnectionStringProvider, ConnectionStringProvider>();
services.AddSingleton<IDataAccess, Database>();
services.AddSingleton<IUserService, UserService>();
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder
.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
services.AddControllers().AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {Title = "FlexitimeAPI", Version = "v1"});
});
}
private IApplicationSettings InitialiseAppSettings()
{
var appSettings= new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build()
.GetSection("Settings")
.Get<AppSettings>();
appSettings.Verify<AppSettings>();
return appSettings;
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "FlexitimeAPI v1"));
}
app.UseCors();
//app.UseHttpsRedirection();
app.UseRouting();
app.UseMiddleware<JwtMiddleware>();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace FlexitimeAPI
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Settings": {
"Secret": "JiminiyCricket|{08EBD219-AABA-4C33-8312-AE93EECE77D2}",
"ConnectionString": "Data Source=.\\SQLSERVER2019;Initial Catalog=FlexitimeData;Integrated Security=SSPI;"
}
}

View File

@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Settings": {
"Secret": "JiminiyCricket|{08EBD219-AABA-4C33-8312-AE93EECE77D2}",
"ConnectionString": "Data Source=.\\SQLSERVER2019;Initial Catalog=FlexitimeData;Integrated Security=SSPI;"
}
}

View File

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using AutoFixture;
using Flexitime.Objects;
using FlexitimeAPI.Helpers;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Moq;
using Xunit;
namespace FlexitimeApi.UnitTests
{
public class AuthorizeAttributeTests
{
private readonly Fixture _f = new();
[Fact]
public void AuthorizeAttribute_Should_AuthorizeWithNoExplicitPermissions()
{
var sut = new AuthorizeAttribute();
var mockUser = _f.Build<User>()
.Without(x => x.Permissions)
.Without(x => x.Groups)
.Without(x => x.Team)
.Create();
var ctx = SetupContext(mockUser);
sut.OnAuthorization(ctx);
ctx.Result.Should().BeNull();
}
[Fact]
public void AuthorizeAttribute_ShouldNot_AuthorizeWhenUserHasInvalidPermission()
{
var mockUser = _f.Build<User>()
.With(x => x.Permissions,
_f.Build<Permission>().With(y => y.Tag, "g.x").Without(y => y.Application).CreateMany(1).ToList())
.Without(x => x.Groups)
.Without(x => x.Team)
.Create();
var ctx = SetupContext(mockUser);
var sut = new AuthorizeAttribute {Permissions = new[] {"u.w"}};
sut.OnAuthorization(ctx);
ctx.Result.Should().BeOfType<JsonResult>();
var actual = ctx.Result as JsonResult;
actual.StatusCode.Should().Be(401);
}
[Fact]
public void AuthorizeAttribute_Should_AuthorizeWithCorrectExplicitPermission()
{
var mockUser = _f.Build<User>()
.With(x => x.Permissions,
_f.Build<Permission>().With(y => y.Tag, "g.x").Without(y => y.Application).CreateMany(1).ToList())
.Without(x => x.Groups)
.Without(x => x.Team)
.Create();
var ctx = SetupContext(mockUser);
var sut = new AuthorizeAttribute {Permissions = new[] {"g.x"}};
sut.OnAuthorization(ctx);
ctx.Result.Should().BeNull();
}
private AuthorizationFilterContext SetupContext(User mockUser)
{
var httpContextMock = new Mock<HttpContext>();
httpContextMock
.Setup(a => a.Request.Headers["Authorization"])
.Returns("mock WRONG apikey");
httpContextMock.SetupGet(x => x.Items)
.Returns(new Dictionary<object, object?> {{"User", mockUser}});
ActionContext fakeActionContext =
new ActionContext(httpContextMock.Object,
new Microsoft.AspNetCore.Routing.RouteData(),
new Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor());
var ret = new AuthorizationFilterContext(fakeActionContext,
new List<IFilterMetadata>());
return ret;
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofixture" Version="4.15.0" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="XUnit" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FlexitimeAPI\FlexitimeAPI.csproj" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
<prism:PrismApplication x:Class="FlexitimeTaskbarUtility.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FlexitimeTaskbarUtility.Converters"
ShutdownMode="OnExplicitShutdown"
xmlns:prism="http://prismlibrary.com/">
<prism:PrismApplication.Resources>
<!-- merge NotifyIcon and related stuff into the application -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NotifyIconResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</prism:PrismApplication.Resources>
</prism:PrismApplication>

View File

@ -0,0 +1,60 @@
using System.Windows;
using System.Windows.Controls;
using FlexitimeTaskbarUtility.Extensions;
using FlexitimeTaskbarUtility.Interfaces;
using FlexitimeTaskbarUtility.Services;
using FlexitimeTaskbarUtility.ViewModels;
using Hardcodet.Wpf.TaskbarNotification;
using Prism.DryIoc;
using Prism.Events;
using Prism.Ioc;
using Prism.Regions;
namespace FlexitimeTaskbarUtility
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
private TaskbarIcon notifyIcon;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
}
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
base.ConfigureRegionAdapterMappings(regionAdapterMappings);
regionAdapterMappings.RegisterMapping(typeof(ContextMenu), Container.Resolve<ContextMenuRegionAdapter>());
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<ILoginService, LoginService>();
containerRegistry.Register<ISignInService, SignInService>();
containerRegistry.Register<NotifyIconViewModel>();
containerRegistry.Register<LoginControlViewModel>();
}
protected override Window CreateShell()
{
return null;
}
protected override void OnInitialized()
{
base.OnInitialized();
notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");
notifyIcon.DataContext = Container.Resolve<NotifyIconViewModel>();
}
protected override void OnExit(ExitEventArgs e)
{
notifyIcon.Dispose(); //the icon would clean up automatically, but this is cleaner
base.OnExit(e);
}
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,72 @@
<UserControl x:Class="FlexitimeTaskbarUtility.Components.LoginControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels="clr-namespace:FlexitimeTaskbarUtility.ViewModels"
xmlns:components="clr-namespace:FlexitimeTaskbarUtility.Components"
xmlns:prism="http://prismlibrary.com/"
d:DataContext="{d:DesignInstance Type=viewModels:LoginControlViewModel}"
mc:Ignorable="d"
MinHeight="150"
MinWidth="300"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid Background="Azure">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontWeight="Bold"
FontSize="22"
Margin="10,10,0,0">
Login
</TextBlock>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
FontSize="18"
Margin="10,0,0,0">
Username
</TextBlock>
<TextBox Grid.Row="0"
Grid.Column="1"
FontSize="18"
Text="{Binding UserName}"
Margin="0,0,10,0"/>
<TextBlock Grid.Row="1"
Grid.Column="0"
FontSize="18"
Margin="10,0,0,0">
Password
</TextBlock>
<PasswordBox Grid.Row="1"
Grid.Column="1"
Name="PasswordBox"
components:PasswordBoxAssistant.BindPassword="True"
components:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,10,0"/>
</Grid>
<Button Grid.Row="2"
Height="30"
Width="120"
FontSize="18"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Margin="0,15,0,0"
Command="{Binding LoginCommand}"
CommandParameter="{Binding ElementName=PasswordBox}">
Login
</Button>
</Grid>
</UserControl>

View File

@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace FlexitimeTaskbarUtility.Components
{
/// <summary>
/// Interaction logic for LoginControl.xaml
/// </summary>
public partial class LoginControl : UserControl
{
public LoginControl()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,108 @@
using System.Windows;
using System.Windows.Controls;
namespace FlexitimeTaskbarUtility.Components
{
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached(nameof(BoundPassword), typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
nameof(BindPassword), typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached(nameof(UpdatingPassword), typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
}

View File

@ -0,0 +1,13 @@
<UserControl x:Class="FlexitimeTaskbarUtility.Components.SigninControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Height="100"
Width="200">
<Grid Background="Green">
<TextBlock HorizontalAlignment="Center" Foreground="White" VerticalAlignment="Center">Sign in control</TextBlock>
<Button Height="30" VerticalAlignment="Bottom" Command="{Binding SignInCommand}">Sign In</Button>
</Grid>
</UserControl>

View File

@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace FlexitimeTaskbarUtility.Components
{
/// <summary>
/// Interaction logic for SigninControl.xaml
/// </summary>
public partial class SigninControl : UserControl
{
public SigninControl()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flexitime.Objects;
namespace FlexitimeTaskbarUtility.Converters
{
public class UserStatusToImageSourceConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(UserState))
{
throw new ArgumentException(nameof(value));
}
var userState = (UserState) value;
string basePath = string.Intern("pack://application:,,,/FlexitimeTaskbarUtility;component/Images/");
string imageName;
switch (userState)
{
case UserState.Unknown:
imageName = "Clock_LoggedOut.ico";
break;
case UserState.In:
imageName = "Clock_In.ico";
break;
case UserState.Out:
imageName = "Clock_Out.ico";
break;
case UserState.OutOfOffice:
imageName = "Clock_OutOfOffice.ico";
break;
case UserState.Remote:
imageName = "Clock_Remote.ico";
break;
default:
throw new ArgumentOutOfRangeException();
}
ImageSource ret = new BitmapImage(new Uri($"{basePath}{imageName}"));
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using Flexitime.Objects;
namespace FlexitimeTaskbarUtility.Converters
{
public class UserStatusToLoginDialogVisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(UserState))
{
throw new ArgumentException(nameof(value));
}
var userState = (UserState)value;
Visibility ret;
switch (userState)
{
case UserState.Unknown:
ret = Visibility.Visible;
break;
default:
ret = Visibility.Collapsed;
break;
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using Flexitime.Objects;
namespace FlexitimeTaskbarUtility.Converters
{
public class UserStatusToSigninControlVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(UserState))
{
throw new ArgumentException(nameof(value));
}
var userState = (UserState)value;
Visibility ret;
switch (userState)
{
case UserState.Unknown:
ret = Visibility.Collapsed;
break;
default:
ret = Visibility.Visible;
break;
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Windows.Input;
namespace NotifyIconWpf.Sample.Windowless
{
/// <summary>
/// Simplistic delegate command for the demo.
/// </summary>
public class DelegateCommand : ICommand
{
public Action CommandAction { get; set; }
public Func<bool> CanExecuteFunc { get; set; }
public void Execute(object parameter)
{
CommandAction();
}
public bool CanExecute(object parameter)
{
return CanExecuteFunc == null || CanExecuteFunc();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}

Some files were not shown because too many files have changed in this diff Show More