using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Cryptography.X509Certificates; using Interfaces; using SQLite.Net; using SQLite.Net.Platform.Win32; namespace SQLiteRepository { public class SQLiteRepository : IRepository { private readonly SQLiteConnection _connection; private readonly ILogger _logger; private string _path = "flexitimedb.db"; public SQLiteRepository(ILogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger; _connection = new SQLiteConnection(new SQLitePlatformWin32(), _path); _connection.CreateTable(); _connection.CreateTable(); _connection.CreateTable(); _logger.Trace("Initialised SQLite Repository"); } public UserList GetUsers(int pageNumber = -1, int pageSize = -1) { var ret = new UserList(); List users; int userCount; if (pageNumber == -1 && pageSize == -1) { users = _connection.Query(SQLiteProcedures.GET_ALL_USERS); userCount = users.Count; } else { users = _connection.Query(SQLiteProcedures.GET_ALL_USERS_PAGINATE, pageSize, (pageNumber - 1) * pageSize); userCount = _connection.ExecuteScalar(SQLiteProcedures.GET_TOTAL_USER_COUNT); } if (!users.Any()) { if (pageNumber == -1 && pageSize == -1) { ret.PageNumber = 1; ret.PageSize = 20; } else { ret.PageNumber = pageNumber; ret.PageSize = pageSize; } return ret; } foreach (var user in users) { var userObj = ChangeToUserObject(user); var cards = _connection.Query( SQLiteProcedures.GET_CARDS_BY_USER_ID, user.Id); foreach (var card in cards) { userObj.AssociatedIdentifiers.Add(new Identifier() { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id }); } ret.Users.Add(userObj); } if (pageNumber == -1 && pageSize == -1) { ret.PageSize = 1; //TODO: switch to ret.UserCount ret.PageNumber = 1; } else { ret.PageSize = pageSize; ret.PageNumber = pageNumber; } ret.TotalUserCount = userCount; return ret; } public UserList Search(string searchParam) { _logger.Trace("Searching SQLite database for the term: {0}", searchParam); var ret = new UserList(); searchParam = string.Format("%{0}%", searchParam); var users = _connection.Query( SQLiteProcedures.SEARCH_USER_LIST, searchParam, searchParam); _logger.Trace("Got {0} results for term: {1}", users.Count, searchParam); if (!users.Any()) { ret.PageNumber = 1; ret.PageSize = 20; return ret; } foreach (var user in users) { var userObj = ChangeToUserObject(user); var cards = _connection.Query( SQLiteProcedures.GET_CARDS_BY_USER_ID, user.Id); foreach (var card in cards) { userObj.AssociatedIdentifiers.Add(new Identifier() { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id }); } ret.Users.Add(userObj); } //TODO: figure out paging here. - should there be any? ret.PageSize = 20; ret.PageNumber = 1; return ret; } public User GetUser(int id) { var ret = new User(); try { var users = _connection.Query( SQLiteProcedures.GET_USER_BY_ID, id); if (!users.Any()) return ret; var user = users.First(); ret = ChangeToUserObject(user); var cards = _connection.Query( SQLiteProcedures.GET_CARDS_BY_USER_ID, user.Id); foreach (var card in cards) { ret.AssociatedIdentifiers.Add(new Identifier { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id }); } } catch (Exception ex) { _logger.Error(ex, "Error in GetUser with Id: {0}", id); ret.UserId = id; ret.FirstName = ret.LastName = string.Empty; ret.HoursPerWeek = -1.0f; ret.IsContractor = false; ret.AssociatedIdentifiers.Clear(); } return ret; } //TODO: refac this as it duplicates a lot of code. public TimeLogList GetTimeLogs(int userId) { return GetTimeLogs(userId, DateTime.UtcNow); } public TimeLogList GetTimeLogs(int userId, DateTime selectedDate) { var ret = new TimeLogList { SelectedDate = selectedDate.Date }; var calendarWeek = GetIso8601CalendarWeek(selectedDate); ret.CalendarWeek = calendarWeek; try { ret.TimeLogs = GetTimeLogList(userId, calendarWeek, selectedDate.Year); } catch (Exception ex) { _logger.Error(ex, "Error in GetTimeLogs with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex); } try { ret.HoursPerWeekMinutes = GetUserContractedHours(userId) * 60.0f; } catch (Exception ex) { _logger.Error(ex, "Error in GetUserContracterHours with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex); } return ret; } private float GetUserContractedHours(int userId) { var hoursQuery = _connection.Query(SQLiteProcedures.GET_USER_CONTRACTED_HOURS, userId); if (hoursQuery.Any()) { return hoursQuery.First().HoursPerWeek; } return -1.0f; } public IdentifierList GetUnassignedIdentifierList() { var ret = new IdentifierList(); var cardQuery = _connection.Query( SQLiteProcedures.GET_UNASSIGNED_CARD_LIST, Constants.UNASSIGNED_CARD_USER_ID); foreach (var card in cardQuery) { ret.data.Add(new Identifier { Id = card.Id, IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID, UniqueId = card.CardUId }); } return ret; } //TODO: Check time logs table on update to ensure associated cards/unique identifiers are removed/added as appropriate. public OperationResponse UpdateUser(User user, out int userIdResult) { //if(user.UserId <=0) return OperationResponse.FAILED; var ret = OperationResponse.NONE; var cardIds = new List(); //Get a list of current associated identifiers, convert into a list of Identifier Objects.. var currentCards = _connection.Query(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId) .Select(x => new Identifier { Id = x.Id, IsAssociatedToUser = true, UniqueId = x.CardUId }); var cardsToRemove = currentCards.Except(user.AssociatedIdentifiers).Select(x => x.Id).ToList(); #region GetUnique Identifiers foreach (var card in user.AssociatedIdentifiers) { var existingCard = _connection.Query( SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, card.UniqueId); if (!existingCard.Any()) { var cardInsert = new CardUniqueId { CardUId = card.UniqueId, UserId_FK = -1 }; _connection.Insert(cardInsert); cardIds.Add(cardInsert.Id); if (ret < OperationResponse.CREATED) ret = OperationResponse.CREATED; //only change it if my status supercedes. } else { cardIds.Add(existingCard.First().Id); if (ret < OperationResponse.UPDATED) ret = OperationResponse.UPDATED; } } #endregion #region Update/Create User int userId; if (user.UserId != -1) { //edit.. _connection.Query( "update UserIdentity set FirstName=?, LastName=?, HoursPerWeek=?,IsContractor=? where Id=?", user.FirstName, user.LastName, user.HoursPerWeek, user.IsContractor, user.UserId ); userId = user.UserId; } else { var userInsert = new UserIdentity { FirstName = user.FirstName, LastName = user.LastName, HoursPerWeek = user.HoursPerWeek, IsContractor = user.IsContractor }; _connection.Insert(userInsert); userId = userInsert.Id; if (ret < OperationResponse.CREATED) ret = OperationResponse.CREATED; } #endregion #region Update Card/Unique Id entries. foreach (var cardId in cardIds) { _connection.Query( SQLiteProcedures.UPDATE_CARD_USER_ID, userId, cardId); } foreach (var card in cardsToRemove) { _connection.Query( SQLiteProcedures.UPDATE_CARD_USER_ID, -1, card); } #endregion userIdResult = userId; return ret; } public OperationResponse LogEventTime(Identifier identifier, out int logId) { var cardIdQuery = _connection.Query( SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, identifier.UniqueId); #region Get/Insert the PK Id Identifier to associate the time log to. var ident = new CardUniqueId(); if (!cardIdQuery.Any()) { //new card, create it! ident.UserId_FK = -1; ident.CardUId = identifier.UniqueId; _connection.Insert(ident); } else { //TODO: log when more than one comes back. should NEVER happen but.... ident = cardIdQuery.First(); } #endregion // Get The User Direction (are they going in or out)? var logDirection = GetLogDirection(ident.UserId_FK); #region Get the current time (for swiping). and calendar week/year to help recall the data. var logTime = DateTime.UtcNow; var calendarWeek = GetIso8601CalendarWeek(logTime); var year = logTime.Year; #endregion //TODO: Handle When the identifier is assigned to a user (identifier has -1) //when identifier not assigned to user, just store it anyway and carry on, can update later. var timeLog = new TimeLogDb { SwipeEventDateTime = DateTime.UtcNow, UserId_FK = ident.UserId_FK, IdentifierId = ident.Id, Direction = logDirection, Year = year, CalendarWeek = calendarWeek }; _connection.Insert(timeLog); logId = timeLog.Id; return OperationResponse.SUCCESS; } private List GetTimeLogList(int userId, int calendarWeek, int year) { var timeLogList = _connection.Query( SQLiteProcedures.GET_TIMELOGS, userId, calendarWeek, year); var timeLogs = timeLogList.Select(x => new TimeLog { Id = x.Id, CalendarWeek = x.CalendarWeek, Direction = (LogDirection)x.Direction, IdentifierId = x.IdentifierId, EventTime = x.SwipeEventDateTime, UserId = x.UserId_FK, Year = x.Year }).ToList(); var dict = new Dictionary(); var logList = new List(); //make sure each day of the week is accounted for in the dictionary. foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))) { dict.Add(day, new DailyLogs()); } //add the logs to the respective day of the week. foreach (var log in timeLogs) { dict[log.EventTime.DayOfWeek].Logs.Add(log); } var logGroups = timeLogs.GroupBy(x => x.EventTime.DayOfWeek); foreach (var group in logGroups) { var groupLogs = group.ToList(); var dailyLog = new DailyLogs { Logs = groupLogs, Day = group.Key, DayOfWeek = group.Key.ToString() }; dailyLog.DailyTotal = CalculateDailyTotal(dailyLog); logList.Add(dailyLog); } foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))) { if (logList.Any(x => x.Day == day)) continue; var dailyLog = new DailyLogs {Day = day, DayOfWeek = day.ToString()}; logList.Add(dailyLog); } foreach (var dailyCollection in dict) { dailyCollection.Value.DailyTotal = CalculateDailyTotal(dailyCollection.Value); } return logList.OrderBy(x => ((int) x.Day + 6)%7).ToList(); } private double CalculateDailyTotal(DailyLogs dailyLogs) { var totalInTime = TimeSpan.FromSeconds(0); var logs = dailyLogs.Logs.OrderBy(x => x.Id).ToArray(); var totalCalcMax = IsOdd(logs.Length) ? logs.Length - 1 : logs.Length; for (int i = 0; i < totalCalcMax; i += 2) { totalInTime += (logs[i + 1].EventTime - logs[i].EventTime); } return Math.Round(totalInTime.TotalMinutes, 2); } /// /// determines if the number is an odd or even value /// /// number to determine is odd or even /// true - number is odd private bool IsOdd(int value) { return value % 2 != 0; } /// /// Get the new direction for the user based on previous entry logs in the system. /// /// /// If the user has not logged in today, the direction will be In. /// If the user has logged in already today, the direction will be the opposite of the last /// recorded log direction. ("out" if "in", "in" if "out") /// /// Id of the user to get the log direction of. /// indicating what direction the new log is. private LogDirectionDb GetLogDirection(int userId) { var logDirection = LogDirectionDb.UNKNOWN; if (userId != -1) { var lastEntry = _connection.Query( SQLiteProcedures.GET_LAST_TIMELOG_DIRECTION, userId); if (lastEntry.Any()) { var lastLog = lastEntry.First(); // See if the datetime retrieved is yesterday. If yesterday, logDirection = true (in) if (IsLogDateTimeYesterdayOrOlder(lastLog.SwipeEventDateTime.DateTime)) { logDirection = LogDirectionDb.IN; } else { // we have a time log from today already, so just do the opposite of what we last did! if (lastLog.Direction == LogDirectionDb.IN) logDirection = LogDirectionDb.OUT; else if (lastLog.Direction == LogDirectionDb.OUT) logDirection = LogDirectionDb.IN; } } else { //assume its the first then! logDirection = LogDirectionDb.IN; } } return logDirection; } /// /// Get the calendar week of the year according to the ISO8601 standard (starts monday). /// /// the date to get the calendar week of. /// the calendar week of the year in integer form (1-52) private int GetIso8601CalendarWeek(DateTime date) { var day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(date); if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) { date = date.AddDays(3); } return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); } /// /// Check whether the specified DateTime is from yesterday or older. /// /// the DateTime object to check is yesterday or older /// true - is yesterday or older. private bool IsLogDateTimeYesterdayOrOlder(DateTime dt) { return dt.Date.CompareTo(DateTime.Today.Date) < 0; } private User ChangeToUserObject(UserIdentity user) { return new User { UserId = user.Id, FirstName = user.FirstName, LastName = user.LastName, HoursPerWeek = user.HoursPerWeek, IsContractor = user.IsContractor }; } } }