diff --git a/.gitignore b/.gitignore index 39a91ef..c776f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ F4SDHelper/bin/ F4SDHelper/obj/ F4SDM42WebApi/bin/ F4SDM42WebApi/obj/ -artifacts/ +artifacts/ +Legacy/BasePackage/Assemblies/ +Legacy/PackageTemplate/Assemblies/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0cc3083 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Repository Instructions + +## Git Completion + +- After completing and verifying requested repository changes, create a Git commit and push the current branch to `origin` unless the user explicitly asks not to commit or push. +- Stage only files that belong to the completed task. Leave unrelated local changes, reference material, exports, and generated artifacts untouched. +- Report the commit hash and pushed branch to the user. +- This Git rule does not authorize any TFS check-in or upload. + +## TFS and Git + +- TFS remains active for the Matrix42 ESM 12.1.3-25.x legacy implementation. +- Import TFS changes into Git with `tools/Sync-TfsLegacy.ps1` and review them on a dedicated `sync/tfs-legacy` branch. +- Never copy a complete TFS workspace over the sandbox projects. +- Never push, check in, or otherwise publish changes to TFS automatically. +- The final transfer from Git into TFS is always performed and reviewed manually by the user in Visual Studio. +- Do not run `tf checkin`, `tf reconcile`, scripted TFS uploads, or equivalent commands. + +## Platform Ownership + +- `Legacy/` contains the .NET Framework 4.7.2 host for Matrix42 ESM 12.1.3-25.x. +- `F4SDM42WebApi/` and `PackageTemplate/` contain the sandboxed .NET 8 host for Matrix42 ESM 26.1 and newer. +- `F4SDHelper/Common/C4IT.F4SD.WebApi.Contracts.cs` contains contracts shared only by both Web API variants. +- The external `_Common/C4IT.F4SD.Base.Ticket.cs` is shared with other F4SD products and must not be changed merely to simplify this Web API. +- Keep platform-specific dependency resolution, controllers, package metadata, and journal access in their respective host. + +## Builds + +- Use `C4IT - F4SD - M42WebApi.sln` for the sandbox build. +- Use `C4IT - F4SD - M42WebApi.Legacy.sln` for the legacy build. +- `Release_signed` is the optional signed configuration for both variants. +- Package versions are derived from `SharedAssemblyInfo.cs`. diff --git a/C4IT - F4SD - M42WebApi.Legacy.sln b/C4IT - F4SD - M42WebApi.Legacy.sln new file mode 100644 index 0000000..f692028 --- /dev/null +++ b/C4IT - F4SD - M42WebApi.Legacy.sln @@ -0,0 +1,32 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F4SD - Helper (Legacy)", "Legacy\F4SDHelper\F4SD - Helper.Legacy.csproj", "{27A63A98-232D-4D54-A781-E9D493417D59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F4SD - M42WebApi (Legacy)", "Legacy\F4SDM42WebApi\F4SD - M42WebApi.Legacy.csproj", "{B6316CC4-4827-4D16-A005-DDB8055695BC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Release_signed|Any CPU = Release_signed|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {27A63A98-232D-4D54-A781-E9D493417D59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27A63A98-232D-4D54-A781-E9D493417D59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27A63A98-232D-4D54-A781-E9D493417D59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27A63A98-232D-4D54-A781-E9D493417D59}.Release|Any CPU.Build.0 = Release|Any CPU + {27A63A98-232D-4D54-A781-E9D493417D59}.Release_signed|Any CPU.ActiveCfg = Release_signed|Any CPU + {27A63A98-232D-4D54-A781-E9D493417D59}.Release_signed|Any CPU.Build.0 = Release_signed|Any CPU + {B6316CC4-4827-4D16-A005-DDB8055695BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6316CC4-4827-4D16-A005-DDB8055695BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6316CC4-4827-4D16-A005-DDB8055695BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6316CC4-4827-4D16-A005-DDB8055695BC}.Release|Any CPU.Build.0 = Release|Any CPU + {B6316CC4-4827-4D16-A005-DDB8055695BC}.Release_signed|Any CPU.ActiveCfg = Release_signed|Any CPU + {B6316CC4-4827-4D16-A005-DDB8055695BC}.Release_signed|Any CPU.Build.0 = Release_signed|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/F4SDHelper/Common/C4IT.F4SD.WebApi.Contracts.cs b/F4SDHelper/Common/C4IT.F4SD.WebApi.Contracts.cs new file mode 100644 index 0000000..b1c9d29 --- /dev/null +++ b/F4SDHelper/Common/C4IT.F4SD.WebApi.Contracts.cs @@ -0,0 +1,58 @@ +using System; + +namespace C4IT.FASD.Base +{ + public class cF4SDTicket : cF4SDTicketSummary + { + public enum enumTicketCreationSource + { + Unknown = 0, + Mail = 1, + Phone = 2, + F4SD = 3 + } + + public class cTicketJournalItem + { + public DateTime CreationDate { get; set; } + public string Header { get; set; } + public string CreatedBy { get; set; } + public string DescriptionHtml { get; set; } + public string Description { get; set; } + public bool IsVisibleForUser { get; set; } + public Guid ActivityObjectId { get; set; } + public Guid JournalId { get; set; } + } + + public Guid AffectedUserId { get; set; } + public Guid AssetId { get; set; } + public DateTime CreationDate { get; set; } + public DateTime? ClosingDate { get; set; } + public int CreationSourceId { get; set; } + public string CreationSource { get; set; } + public string Description { get; set; } + public string DescriptionHtml { get; set; } + public int PriorityId { get; set; } + public string Priority { get; set; } + public Guid CategoryId { get; set; } + public string Category { get; set; } + public string CategoryHierarchical { get; set; } + public string CIName { get; set; } + public string DirectLinkEdit { get; set; } + public Guid AssetCIId { get; set; } + public int AssetSKUAssetGroupId { get; set; } + public string AssetSKUAssetGroup { get; set; } + public int AssetSKUTypeId { get; set; } + public string AssetSKUType { get; set; } + public string DirectLinkPreview { get; set; } + public string DirectLinkClose { get; set; } + public string AffectedUser { get; set; } + public string SolutionHtml { get; set; } + public string Solution { get; set; } + public string AssetDomain { get; set; } + public string Urgency { get; set; } + public int UrgencyId { get; set; } + public string Impact { get; set; } + public int ImpactId { get; set; } + } +} diff --git a/F4SDHelper/F4SD - Helper.csproj b/F4SDHelper/F4SD - Helper.csproj index 76827c8..38daf0e 100644 --- a/F4SDHelper/F4SD - Helper.csproj +++ b/F4SDHelper/F4SD - Helper.csproj @@ -1,5 +1,5 @@ - + net8.0 Library C4IT.F4SDM @@ -9,7 +9,8 @@ latestmajor false false - Debug;Release;Debug_and_copy;Release_and_copy;Release_signed + Debug;Release;Debug_and_copy;Release_and_copy;Release_signed + $(MSBuildProjectDirectory)\..\..\_Common @@ -28,10 +29,18 @@ Common\C4IT.Logging.LogManager.cs - + + Common\C4IT.F4SD.Base.Ticket.cs + + Properties\SharedAssemblyInfo.cs - - - + + + + + + + diff --git a/F4SDM42WebApi/BuildM42Package.ps1 b/F4SDM42WebApi/BuildM42Package.ps1 index 8d617c1..4b189e0 100644 --- a/F4SDM42WebApi/BuildM42Package.ps1 +++ b/F4SDM42WebApi/BuildM42Package.ps1 @@ -40,7 +40,7 @@ if (-not (Test-Path -LiteralPath (Join-Path $templateDirFull 'package.json'))) { throw "Package template not found: $templateDirFull" } -if (-not (Test-Path -LiteralPath (Join-Path $assembliesDirFull 'C4ITF4SDM42WebApi.dll'))) { +if (-not (Get-ChildItem -LiteralPath $assembliesDirFull -Filter 'C4ITF4SDM42WebApi.dll' -File -Recurse | Select-Object -First 1)) { throw "Package assemblies not found: $assembliesDirFull" } @@ -53,8 +53,12 @@ if (Test-Path -LiteralPath $zipPath) { } New-Item -ItemType Directory -Path $packageDir -Force | Out-Null -Copy-Item -Path (Join-Path $templateDirFull '*') -Destination $packageDir -Recurse -Force -Copy-Item -Path $assembliesDirFull -Destination (Join-Path $packageDir 'Assemblies') -Recurse -Force +Copy-Item -Path (Join-Path $templateDirFull '*') -Destination $packageDir -Recurse -Force +$packageAssembliesPath = Join-Path $packageDir 'Assemblies' +if (Test-Path -LiteralPath $packageAssembliesPath) { + Remove-Item -LiteralPath $packageAssembliesPath -Recurse -Force +} +Copy-Item -Path $assembliesDirFull -Destination $packageAssembliesPath -Recurse -Force $packageJsonPath = Join-Path $packageDir 'package.json' $packageJson = [System.IO.File]::ReadAllText($packageJsonPath) @@ -62,24 +66,22 @@ $packageJson = [regex]::Replace($packageJson, '"Version"\s*:\s*"[^"]+"', "`"Vers $packageJson = [regex]::Replace($packageJson, '"LastUpdatedDate"\s*:\s*"[^"]+"', "`"LastUpdatedDate`": `"$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ss')`"") [System.IO.File]::WriteAllText($packageJsonPath, $packageJson, [System.Text.UTF8Encoding]::new($true)) -$configDataPath = Join-Path $packageDir 'install\1010_Config_Data\02-01-0080 C4IT_F4SDConfigurationType.dat' -if (-not (Test-Path -LiteralPath $configDataPath)) { - throw "Configuration data file not found: $configDataPath" -} - -$configData = [System.IO.File]::ReadAllText($configDataPath) -$configData = [regex]::Replace( - $configData, - '([\s\S]*?)[^<]*()', - "`${1}$PackageVersion`${2}", - [System.Text.RegularExpressions.RegexOptions]::Singleline -) - -if ($configData -notmatch "$([regex]::Escape($PackageVersion))") { - throw "Could not update C4IT_F4SDConfigurationType Version to $PackageVersion in $configDataPath" -} - -[System.IO.File]::WriteAllText($configDataPath, $configData, [System.Text.UTF8Encoding]::new($true)) +$configDataPath = Join-Path $packageDir 'install\1010_Config_Data\02-01-0080 C4IT_F4SDConfigurationType.dat' +if (Test-Path -LiteralPath $configDataPath) { + $configData = [System.IO.File]::ReadAllText($configDataPath) + $configData = [regex]::Replace( + $configData, + '([\s\S]*?)[^<]*()', + "`${1}$PackageVersion`${2}", + [System.Text.RegularExpressions.RegexOptions]::Singleline + ) + + if ($configData -notmatch "$([regex]::Escape($PackageVersion))") { + throw "Could not update C4IT_F4SDConfigurationType Version to $PackageVersion in $configDataPath" + } + + [System.IO.File]::WriteAllText($configDataPath, $configData, [System.Text.UTF8Encoding]::new($true)) +} $textFileExtensions = @('.json', '.xml', '.dat', '.type', '.class', '.config', '.host') Get-ChildItem -LiteralPath $packageDir -Recurse -File | diff --git a/F4SDM42WebApi/F4SD - M42WebApi.csproj b/F4SDM42WebApi/F4SD - M42WebApi.csproj index ac58675..f9f36b2 100644 --- a/F4SDM42WebApi/F4SD - M42WebApi.csproj +++ b/F4SDM42WebApi/F4SD - M42WebApi.csproj @@ -92,7 +92,8 @@ - + + diff --git a/F4SDM42WebApi/F4SDHelperService.UserId.cs b/F4SDM42WebApi/F4SDHelperService.UserId.cs new file mode 100644 index 0000000..0749414 --- /dev/null +++ b/F4SDM42WebApi/F4SDHelperService.UserId.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +using C4IT.FASD.Base; +using C4IT.Logging; + +using Matrix42.Common; + +using update4u.SPS.DataLayer; + +using static C4IT.Logging.cLogManager; + +namespace C4IT.F4SD +{ + public partial class F4SDHelperService + { + internal async Task> getTicketListByUser( + Guid userId, + int hours, + int queueoption, + List queues) + { + var method = MethodBase.GetCurrentMethod(); + LogMethodBegin(method); + try + { + await Task.Delay(0); + if (userId == Guid.Empty) + return new List(); + + var activityFilter = GetActivityFilter(userId, hours, ticketAndServiceRequestEnabled(), queueoption, queues); + LogEntry($"Generating ticket list for userId '{userId}'. ASQL filter: {activityFilter}", LogLevels.Debug); + + var activityTable = FragmentRequestBase.SimpleLoad( + SPSActivityClassBaseID, + "[Expression-ObjectID] as EOID, TicketNumber, Subject, Service.ID as ServiceId, Service.Name as ServiceName" + + ", SUBQUERY(BasicSchemaObjectType as bso, bso.Name, bso.id = base.T(SPSCommonClassBase).TypeID) as ActivityType" + + ", COALESCE(T(SPSActivityClassIncident).Asset.T(SPSComputerClassBase).Name, T(SPSActivityClassIncident).Asset.T(SPSAssetClassSIMCard).PhoneNumber, T(SPSActivityClassIncident).Asset.Name, T(SPSActivityClassIncident).Asset.objectid) as AssetName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSActivityClassIncident).Asset.T(SPSCommonClassBase).TypeID) as AssetCIName", + activityFilter); + + if (activityTable?.Rows == null || activityTable.Rows.Count == 0) + return new List(); + + var tickets = new List(activityTable.Rows.Count); + foreach (DataRow entry in activityTable.Rows) + { + var activityId = getGuidFromObject(entry["EOID"]); + var ticketNumber = getStringFromObject(entry["TicketNumber"]); + var subject = getStringFromObject(entry["Subject"]); + if (string.IsNullOrEmpty(ticketNumber) || string.IsNullOrEmpty(subject)) + continue; + + var state = GetActivityState(activityId); + tickets.Add(new cF4SDTicketSummary + { + TicketObjectId = activityId, + Name = ticketNumber, + ActivityType = getStringFromObject(entry["ActivityType"]), + Summary = subject, + StatusId = state.Item1, + Status = state.Item2, + AssetCIName = getStringFromObject(entry["AssetCIName"]), + AssetName = getStringFromObject(entry["AssetName"]), + ServiceId = getGuidFromObject(entry["ServiceId"]), + ServiceName = getStringFromObject(entry["ServiceName"]), + UserId = userId, + IsPrimaryAccount = true + }); + } + + return tickets.OrderByDescending(ticket => ticket.Name).ToList(); + } + catch (Exception exception) + { + LogException(exception); + return new List(); + } + finally + { + LogMethodEnd(method); + } + } + + internal async Task getTicketOverviewCounts( + Guid userId, + string scope, + IEnumerable keys, + int queueoption, + List queues) + { + var method = MethodBase.GetCurrentMethod(); + LogMethodBegin(method); + try + { + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + var normalizedKeys = (keys ?? Array.Empty()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (normalizedKeys.Count == 0) + normalizedKeys.AddRange(TicketOverviewKeys); + + var entries = await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues); + List unassignedEntries = null; + if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey)) + unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues); + + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var key in normalizedKeys) + { + var source = !useRoleScope && IsUnassignedOverviewKey(key) + ? unassignedEntries ?? new List() + : entries; + counts[key] = source.Count(entry => MatchesTicketOverviewKey(entry, key)); + } + + return new TicketOverviewCountsResult { Counts = counts }; + } + catch (Exception exception) + { + LogException(exception); + return new TicketOverviewCountsResult(); + } + finally + { + LogMethodEnd(method); + } + } + + internal async Task getTicketOverviewCountsByRoles( + Guid userId, + IEnumerable roleGuids, + IEnumerable keys, + int queueoption, + List queues) + { + var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, roleGuids); + return await getTicketOverviewCountsByRoles(null, roleIds, keys, queueoption, queues); + } + + internal async Task> getTicketOverviewRelations( + Guid userId, + string scope, + string key, + int count, + int queueoption, + List queues) + { + var method = MethodBase.GetCurrentMethod(); + LogMethodBegin(method); + try + { + if (userId == Guid.Empty || string.IsNullOrWhiteSpace(key)) + return new List(); + + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + var entries = !useRoleScope && IsUnassignedOverviewKey(key) + ? await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues) + : await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues); + + var filtered = entries + .Where(entry => MatchesTicketOverviewKey(entry, key)) + .OrderByDescending(entry => entry.CreatedDate); + if (count > 0) + filtered = filtered.Take(count).OrderByDescending(entry => entry.CreatedDate); + + return filtered.Select(entry => new TicketOverviewRelationDto + { + Type = enumF4sdSearchResultClass.Ticket, + Name = entry.TicketNumber ?? string.Empty, + DisplayName = entry.TicketNumber ?? string.Empty, + id = entry.TicketId, + Status = enumF4sdSearchResultStatus.Active, + Infos = new Dictionary + { + ["Summary"] = entry.Summary ?? string.Empty, + ["StatusId"] = ConvertM42State(entry.State), + ["ActivityType"] = entry.ActivityType ?? string.Empty, + ["UserDisplayName"] = entry.InitiatorDisplayName ?? string.Empty, + ["UserAccount"] = entry.InitiatorAccount ?? string.Empty, + ["UserDomain"] = entry.InitiatorDomain ?? string.Empty, + ["UserSid"] = entry.InitiatorSid ?? string.Empty, + ["Sids"] = entry.InitiatorSid ?? string.Empty, + ["UserId"] = entry.InitiatorId == Guid.Empty ? string.Empty : entry.InitiatorId.ToString(), + ["UserGuid"] = entry.InitiatorId == Guid.Empty ? string.Empty : entry.InitiatorId.ToString() + }, + Identities = new List + { + new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = entry.TicketId }, + new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = entry.InitiatorId } + } + }).ToList(); + } + catch (Exception exception) + { + LogException(exception); + return new List(); + } + finally + { + LogMethodEnd(method); + } + } + + private async Task> LoadTicketOverviewEntries( + Guid userId, + bool useRoleScope, + int queueoption, + List queues) + { + if (userId == Guid.Empty) + return new List(); + + var filter = await BuildTicketOverviewFilterAsync(userId, useRoleScope, queueoption, queues); + return await LoadTicketOverviewEntriesByFilter(filter); + } + + private async Task> LoadTicketOverviewUnassignedEntriesForPersonalScope( + Guid userId, + int queueoption, + List queues) + { + if (userId == Guid.Empty) + return new List(); + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null); + var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues); + if (string.IsNullOrWhiteSpace(filter)) + return new List(); + + return await LoadTicketOverviewEntriesByFilter(filter + " AND Recipient IS NULL"); + } + + private async Task BuildTicketOverviewFilterAsync( + Guid userId, + bool useRoleScope, + int queueoption, + List queues) + { + var filter = BuildTicketOverviewBaseFilter(queueoption, queues); + if (!useRoleScope) + return filter + $" AND (Recipient = '{Escape(userId.ToString("D"))}')"; + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null); + return BuildTicketOverviewFilterForRoleIds(roleIds, filter); + } + + private async Task> ResolveTicketOverviewRoleIdsAsync(Guid userId, IEnumerable roleGuids) + { + var roleIds = (roleGuids ?? Enumerable.Empty()) + .Where(id => id != Guid.Empty) + .Distinct() + .ToList(); + if (roleIds.Count > 0 || userId == Guid.Empty) + return roleIds; + + var roles = await getRoleMembershipById(userId) ?? new List(); + return roles + .Where(role => role != null && role.Id != Guid.Empty) + .Select(role => role.Id) + .Distinct() + .ToList(); + } + + private string GetActivityFilter( + Guid userId, + int hours, + bool ticketAndServiceRequestEnabled, + int queueoption, + List queues) + { + var filter = $"Initiator = '{Escape(userId.ToString("D"))}'"; + if (ticketAndServiceRequestEnabled) + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)"; + } + else + { + filter += " AND UsedInTypeSPSActivityTypeIncident IS NOT NULL"; + } + + var startDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow.AddHours(-hours), true); + var endDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow, true); + filter += " AND (T(SPSCommonClassBase).State <> 204" + + $" OR (ClosedDate > {startDate} AND ClosedDate < {endDate}))"; + + return AppendQueueFilter(filter, queueoption, queues); + } + } +} diff --git a/F4SDM42WebApi/F4SDHelperService.cs b/F4SDM42WebApi/F4SDHelperService.cs index 7dd0206..da035bd 100644 --- a/F4SDM42WebApi/F4SDHelperService.cs +++ b/F4SDM42WebApi/F4SDHelperService.cs @@ -27,7 +27,7 @@ using static C4IT.Logging.cLogManager; namespace C4IT.F4SD { - public class F4SDHelperService + public partial class F4SDHelperService { private static Guid SPSUserClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSUserClassBase"); private static Guid SPSAccountClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassBase"); diff --git a/F4SDM42WebApi/F4SDM42WebApiController.cs b/F4SDM42WebApi/F4SDM42WebApiController.cs index 424b586..7a7a25f 100644 --- a/F4SDM42WebApi/F4SDM42WebApiController.cs +++ b/F4SDM42WebApi/F4SDM42WebApiController.cs @@ -7,7 +7,7 @@ using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; - + using Matrix42.Common; using Matrix42.Contracts.Common.Security; using Matrix42.Contracts.ServiceManagement.ServiceContracts; @@ -18,14 +18,14 @@ using Matrix42.Services.Description.Contracts; using Matrix42.WebApi.Contracts; using Matrix42.WebApi.Contracts.OData; using update4u.SPS.Utility.GlobalConfiguration; - + using C4IT.F4SDM; using C4IT.FASD.Base; using C4IT.Logging; - + using static C4IT.FASD.Base.cF4SDTicket; using static C4IT.Logging.cLogManager; - + namespace C4IT.F4SD { [RoutePrefix("api/C4ITF4SDWebApi")] @@ -39,12 +39,12 @@ namespace C4IT.F4SD //public readonly IFragmentService _fragmentService; private readonly IDependencyResolver _resolver; private readonly IEnumerationProvider _enumerationProvider; - + private readonly F4SDHelperService _f4stHelperService; public string BaseUrl => Request?.RequestUri == null ? string.Empty : $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}"; public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi"; - + public F4SDM42WebApiController(IDependencyResolver resolver, IEnumerationProvider enumerationProvider) { _resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); @@ -53,12 +53,12 @@ namespace C4IT.F4SD //_objectService = objectService; //_fragmentService = fragmentService; //_incidentService = Guard.NullArgument(incidentService, "incidentService"); - + _globalConfigurationProvider = GlobalConfigurationProvider.Instance; _f4stHelperService = new F4SDHelperService(); EnsureInitialized(); - } - + } + private static readonly object initLock = new object(); private static void EnsureInitialized() { @@ -78,22 +78,22 @@ namespace C4IT.F4SD } } catch { }; - } - + } + private T GetRequiredService() where T : class { var service = _resolver.TryGet(); if (service != null) return service; - + throw new InvalidOperationException($"Required Matrix42 service is not registered: {typeof(T).FullName}"); } - + internal IJournalService GetJournalService() { return GetRequiredService(); } - + [Route("getDirectLinkCreateTicket"), HttpGet] public async Task getDirectLinkCreateTicket([FromUri] string sid = "", [FromUri] string assetname = "") { @@ -110,8 +110,9 @@ namespace C4IT.F4SD type = QueryValue(type, nameof(type)); return await _f4stHelperService.getDirectLinkF4SD(eoid, type) ?? string.Empty; } - - [Route("getTicketList"), HttpGet] + + [Obsolete("Use getTicketListForUser with a Matrix42 user ID.")] + [Route("getTicketList"), HttpGet] public async Task> getTicketList( [FromUri] string sid, [FromUri] int hours, @@ -124,7 +125,7 @@ namespace C4IT.F4SD queueoption = QueryValue(queueoption, nameof(queueoption)); queues = QueryValue(queues, nameof(queues)); var decodedPairs = ParseQueues(queues); - + // Nun weiterreichen an Service return await _f4stHelperService.getTicketListByUser( sid, @@ -132,8 +133,27 @@ namespace C4IT.F4SD queueoption, decodedPairs ) ?? new List(); - } - + } + + [Route("getTicketListForUser"), HttpGet] + public async Task> getTicketListForUser( + [FromUri] Guid userId, + [FromUri] int hours, + [FromUri] int queueoption = 0, + [FromUri] string queues = "") + { + userId = QueryValue(userId, nameof(userId)); + hours = QueryValue(hours, nameof(hours)); + queueoption = QueryValue(queueoption, nameof(queueoption)); + queues = QueryValue(queues, nameof(queues)); + + return await _f4stHelperService.getTicketListByUser( + userId, + hours, + queueoption, + ParseQueues(queues)) ?? new List(); + } + [Route("getTicketDetails"), HttpGet] public async Task getTicketDetails([FromUri] Guid objectId) @@ -142,18 +162,19 @@ namespace C4IT.F4SD var tickets = await _f4stHelperService.getTicketDetails(new List() { objectId }); if (tickets?.Count > 0) return tickets[0]; - + return new cF4SDTicket { TicketObjectId = objectId }; } - + [Route("getTicketHistory"), HttpGet] public async Task> getTicketHistory([FromUri] Guid objectId) { objectId = QueryValue(objectId, nameof(objectId)); return await _f4stHelperService.GetJournalEntries(objectId) ?? new List(); } - - [Route("getTicketOverviewCounts"), HttpGet] + + [Obsolete("Use getTicketOverviewCountsForUser with a Matrix42 user ID.")] + [Route("getTicketOverviewCounts"), HttpGet] public async Task getTicketOverviewCounts( [FromUri] string sid, [FromUri] string scope = "personal", @@ -172,13 +193,42 @@ namespace C4IT.F4SD .Select(key => key.Trim()) .Where(key => !string.IsNullOrWhiteSpace(key)) .ToList(); - + var decodedQueues = ParseQueues(queues); return await _f4stHelperService.getTicketOverviewCounts(sid, scope, parsedKeys, queueoption, decodedQueues) ?? new F4SDHelperService.TicketOverviewCountsResult(); - } - - [Route("getTicketOverviewCountsByRoles"), HttpPost] + } + + [Route("getTicketOverviewCountsForUser"), HttpGet] + public async Task getTicketOverviewCountsForUser( + [FromUri] Guid userId, + [FromUri] string scope = "personal", + [FromUri] string keys = "", + [FromUri] int queueoption = 0, + [FromUri] string queues = "") + { + userId = QueryValue(userId, nameof(userId)); + scope = QueryValue(scope, nameof(scope)); + keys = QueryValue(keys, nameof(keys)); + queueoption = QueryValue(queueoption, nameof(queueoption)); + queues = QueryValue(queues, nameof(queues)); + + var parsedKeys = (keys ?? string.Empty) + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(key => key.Trim()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .ToList(); + + return await _f4stHelperService.getTicketOverviewCounts( + userId, + scope, + parsedKeys, + queueoption, + ParseQueues(queues)) ?? new F4SDHelperService.TicketOverviewCountsResult(); + } + + [Obsolete("Use getTicketOverviewCountsByRolesForUser with a Matrix42 user ID.")] + [Route("getTicketOverviewCountsByRoles"), HttpPost] public async Task getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request) { var parsedKeys = (request?.Keys ?? new List()) @@ -186,12 +236,12 @@ namespace C4IT.F4SD .Select(key => key.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - + var roleGuids = (request?.RoleGuids ?? new List()) .Where(roleId => roleId != Guid.Empty) .Distinct() .ToList(); - + var decodedQueues = ParseQueues(request?.Queues ?? string.Empty); return await _f4stHelperService.getTicketOverviewCountsByRoles( request?.Sid, @@ -200,9 +250,34 @@ namespace C4IT.F4SD request?.QueueOption ?? 0, decodedQueues ) ?? new F4SDHelperService.TicketOverviewCountsByRoleResult(); - } - - [Route("getTicketOverviewRelations"), HttpGet] + } + + [Route("getTicketOverviewCountsByRolesForUser"), HttpPost] + public async Task getTicketOverviewCountsByRolesForUser( + [FromBody] TicketOverviewCountsByRolesForUserRequest request) + { + var parsedKeys = (request?.Keys ?? new List()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .Select(key => key.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var roleGuids = (request?.RoleGuids ?? new List()) + .Where(roleId => roleId != Guid.Empty) + .Distinct() + .ToList(); + + return await _f4stHelperService.getTicketOverviewCountsByRoles( + request?.userId ?? Guid.Empty, + roleGuids, + parsedKeys, + request?.QueueOption ?? 0, + ParseQueues(request?.Queues ?? string.Empty)) + ?? new F4SDHelperService.TicketOverviewCountsByRoleResult(); + } + + [Obsolete("Use getTicketOverviewRelationsForUser with a Matrix42 user ID.")] + [Route("getTicketOverviewRelations"), HttpGet] public async Task> getTicketOverviewRelations( [FromUri] string sid, [FromUri] string scope = "personal", @@ -221,7 +296,32 @@ namespace C4IT.F4SD var decodedQueues = ParseQueues(queues); return await _f4stHelperService.getTicketOverviewRelations(sid, scope, key, count, queueoption, decodedQueues) ?? new List(); - } + } + + [Route("getTicketOverviewRelationsForUser"), HttpGet] + public async Task> getTicketOverviewRelationsForUser( + [FromUri] Guid userId, + [FromUri] string scope = "personal", + [FromUri] string key = "", + [FromUri] int count = 0, + [FromUri] int queueoption = 0, + [FromUri] string queues = "") + { + userId = QueryValue(userId, nameof(userId)); + scope = QueryValue(scope, nameof(scope)); + key = QueryValue(key, nameof(key)); + count = QueryValue(count, nameof(count)); + queueoption = QueryValue(queueoption, nameof(queueoption)); + queues = QueryValue(queues, nameof(queues)); + + return await _f4stHelperService.getTicketOverviewRelations( + userId, + scope, + key, + count, + queueoption, + ParseQueues(queues)) ?? new List(); + } /* [Route("updateActivitySolution/{objectId}"), HttpPost] public async Task updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml) @@ -242,18 +342,18 @@ namespace C4IT.F4SD group = QueryValue(group, nameof(group)); EntityEnumeration enumerationTemp = GetEnumeration(name, mode); var vals = enumerationTemp.Values; - + if (group > -1) { vals = vals.Where(row => !row.Extentions.TryGetValue("StateGroup", out var stateGroup) || (ConvertHelper.ParseInt(stateGroup, 0) == group)).ToArray(); } - + string[] columns = new string[] { "position" }; foreach (var item in vals) { item.Extentions = item.Extentions.Where(x => columns.Contains(x.Key.ToLower())).ToDictionary(x => x.Key, x => x.Value); } - + EntityEnumeration enumeration = new EntityEnumeration { Name = enumerationTemp.Name, @@ -262,26 +362,26 @@ namespace C4IT.F4SD //CacheOutputAttribute.RegisterResponseEtag($"enum_{enumeration.Name}_{(int)mode}", $"{enumeration.Name}_{(int)mode}", cultureInvariant: false, userInvariant: true, val); return enumeration; } - + [Route("getMyRoleMemberships"), HttpGet] public async Task getMyRoleMemberships() { var userId = GetCurrentUserId(); if (userId == Guid.Empty) throw new UnauthorizedAccessException("Cannot determine the interactive Matrix42 user."); - + var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { userId }); return await _f4stHelperService.UserPermissionsInfo(filter) ?? new UserPermissionsInfo(); } [HttpGet] [Route("getRoleMemberships")] - public async Task getRoleMemberships([FromUri] string sid = "", [FromUri] string upn = "", [FromUri] Guid? id = null) + public async Task getRoleMemberships([FromUri] string sid = "", [FromUri] string upn = "", [FromUri] Guid? id = null) { sid = QueryValue(sid, nameof(sid)); upn = QueryValue(upn, nameof(upn)); id = QueryValue(id, nameof(id)); var filter = ""; - + if (id != null && id.Value != Guid.Empty) { filter = AsqlHelper.BuildInCondition("ID", new Guid[] { id.Value }); @@ -294,41 +394,58 @@ namespace C4IT.F4SD { filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).UserPrincipalName", new string[] { upn }); } - + if (!string.IsNullOrEmpty(filter)) { return await _f4stHelperService.UserPermissionsInfo(filter) ?? new UserPermissionsInfo(); } - - return new UserPermissionsInfo(); - } - - public class TicketOverviewCountsByRolesRequest + + return new UserPermissionsInfo(); + } + + [HttpGet] + [Route("getRoleMemberships/{userId}")] + public async Task getRoleMembershipForUser([FromUri] Guid userId) + { + return await _f4stHelperService.UserPermissionsInfo( + AsqlHelper.BuildInCondition("ID", new[] { userId })) ?? new UserPermissionsInfo(); + } + + public class TicketOverviewCountsByRolesRequest { public string Sid { get; set; } public List RoleGuids { get; set; } = new List(); public List Keys { get; set; } = new List(); public int? QueueOption { get; set; } - public string Queues { get; set; } - } - + public string Queues { get; set; } + } + + public class TicketOverviewCountsByRolesForUserRequest + { + public Guid userId { get; set; } + public List RoleGuids { get; set; } = new List(); + public List Keys { get; set; } = new List(); + public int? QueueOption { get; set; } + public string Queues { get; set; } + } + private EntityEnumeration GetEnumeration(string name, EntityEnumerationVisibilityMode mode) { name = name?.Trim(); var dataTable = _enumerationProvider.GetEnumeration(name, GetVisibilityFilter(mode)); if (dataTable == null) throw new InvalidOperationException($"Enumeration '{name}' was not found."); - + var valueColumn = FindColumn(dataTable, "Value") ?? FindNumericColumn(dataTable); if (string.IsNullOrEmpty(valueColumn)) throw new InvalidOperationException($"Enumeration '{name}' does not contain a numeric value column."); - + var displayColumn = FindColumn(dataTable, "DisplayString") ?? FindColumn(dataTable, "DisplayExpression") ?? FindColumn(dataTable, "Name") ?? valueColumn; var hiddenColumn = FindColumn(dataTable, "Hidden"); - + var values = dataTable.Rows.Cast() .Select(row => new EntityEnumerationValue { @@ -339,14 +456,14 @@ namespace C4IT.F4SD .ToDictionary(column => column.ColumnName, column => row[column]) }) .ToArray(); - + return new EntityEnumeration { Name = name, Values = values }; } - + private static bool? GetVisibilityFilter(EntityEnumerationVisibilityMode mode) { switch (mode) @@ -359,14 +476,14 @@ namespace C4IT.F4SD return null; } } - + private static string FindColumn(DataTable dataTable, string columnName) { return dataTable.Columns.Cast() .FirstOrDefault(column => string.Equals(column.ColumnName, columnName, StringComparison.OrdinalIgnoreCase)) ?.ColumnName; } - + private static string FindNumericColumn(DataTable dataTable) { var excludedNames = new HashSet(StringComparer.OrdinalIgnoreCase) @@ -375,12 +492,12 @@ namespace C4IT.F4SD "Position", "StateGroup" }; - + return dataTable.Columns.Cast() .FirstOrDefault(column => !excludedNames.Contains(column.ColumnName) && IsNumericType(column.DataType)) ?.ColumnName; } - + private static bool IsNumericType(Type type) { return type == typeof(byte) @@ -392,7 +509,7 @@ namespace C4IT.F4SD || type == typeof(uint) || type == typeof(ulong); } - + private static Guid GetCurrentUserId() { var principal = Thread.CurrentPrincipal as IM42Principal; @@ -400,39 +517,39 @@ namespace C4IT.F4SD ?? principal?.M42Identity?.UserFragmentID ?? Guid.Empty; } - + private string GetQueryValue(string name) { return Request?.GetQueryNameValuePairs() .FirstOrDefault(pair => string.Equals(pair.Key, name, StringComparison.OrdinalIgnoreCase)) .Value; } - + private string QueryValue(string value, string name) { return GetQueryValue(name) ?? value ?? string.Empty; } - + private int QueryValue(int value, string name) { return int.TryParse(GetQueryValue(name), out var parsed) ? parsed : value; } - + private Guid QueryValue(Guid value, string name) { return Guid.TryParse(GetQueryValue(name), out var parsed) ? parsed : value; } - + private Guid? QueryValue(Guid? value, string name) { return Guid.TryParse(GetQueryValue(name), out var parsed) ? parsed : value; } - + private TEnum QueryValue(TEnum value, string name) where TEnum : struct { return Enum.TryParse(GetQueryValue(name), true, out var parsed) ? parsed : value; } - + private static List ParseQueues(string queues) { return (queues ?? string.Empty) @@ -442,10 +559,10 @@ namespace C4IT.F4SD var segments = part.Split(':'); if (segments.Length != 2) return null; - + var name = WebUtility.UrlDecode(segments[0]); var idStr = WebUtility.UrlDecode(segments[1]); - + return Guid.TryParse(idStr, out var guid) ? new cApiM42TicketQueueInfo { QueueName = name, QueueID = guid } : null; @@ -453,7 +570,7 @@ namespace C4IT.F4SD .Where(q => q != null) .ToList(); } - + [Route("isAlive"), HttpGet] public IHttpActionResult isAlive() { @@ -462,10 +579,10 @@ namespace C4IT.F4SD RequestMessage = Request }; response.Headers.ConnectionClose = true; - + return ResponseMessage(response); } - + [Route("loglevel"), HttpGet] public async Task setDebugMode([FromUri] string debug = "0") { @@ -488,7 +605,7 @@ namespace C4IT.F4SD LogMethodEnd(CM); } } - + [Route("log"), HttpGet] public IHttpActionResult getLog([FromUri] string download = "0", [FromUri] int count = 50, [FromUri] string filter = "") { @@ -500,7 +617,7 @@ namespace C4IT.F4SD var response = _f4stHelperService.privGetLog(download, count, Request, filter); if (response == null) return new StatusCodeResult(HttpStatusCode.NoContent); - + return new ResponseMessageResult(response); } catch (Exception E) @@ -514,9 +631,9 @@ namespace C4IT.F4SD public partial class F4SDM42LogsWebApiController : ApiController { private readonly F4SDHelperService _f4stHelperService; - + public static bool IsInitialized { get; private set; } = false; - + private static readonly object initLock = new object(); private static void EnsureInitialized() { @@ -537,15 +654,15 @@ namespace C4IT.F4SD } catch { }; } + - - + public F4SDM42LogsWebApiController() { _f4stHelperService = new F4SDHelperService(); EnsureInitialized(); } - + [Route(""), HttpGet] public IEnumerable getLog2(ODataQueryOptions queryOptions) { @@ -559,26 +676,26 @@ namespace C4IT.F4SD return Enumerable.Empty(); } } - + [Route("$count")] [HttpGet] public int Log2Count(ODataQueryOptions queryOptions) { return ApplyLogFilter(_f4stHelperService.privGetLog2(), queryOptions).Count(); } - + [Route("{id}")] [OperationType(OperationType.GetObject)] public cM42LogEntry GetClass(int id) { return _f4stHelperService.privGetLog2(id) ?? new cM42LogEntry { LineNumber = id }; } - + private static IEnumerable ApplyLogFilter(IEnumerable entries, ODataQueryOptions queryOptions) { var result = entries ?? Enumerable.Empty(); var filter = queryOptions?.Filter; - + if (!string.IsNullOrWhiteSpace(filter)) { result = result.Where(entry => @@ -586,16 +703,16 @@ namespace C4IT.F4SD (entry.logLvl?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0 || (entry.Theme?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0); } - + return result; } } - + public class cGetPropertyBody { public string TableName { get; set; } public List Columns { get; set; } = new List(); public cGetPropertyBody() { } - + } } diff --git a/Legacy/F4SDHelper/F4SD - Helper.Legacy.csproj b/Legacy/F4SDHelper/F4SD - Helper.Legacy.csproj new file mode 100644 index 0000000..cebf1e3 --- /dev/null +++ b/Legacy/F4SDHelper/F4SD - Helper.Legacy.csproj @@ -0,0 +1,52 @@ + + + net472 + Library + C4IT.F4SDM + C4ITF4SDM42WebApiHelper + false + false + 7.3 + Debug;Release;Release_signed + $(MSBuildProjectDirectory)\..\..\..\_Common + + + + true + full + false + + + + pdbonly + true + + + + + + + + + + + + + Common\C4IT.F4SD.Base.Ticket.cs + + + Common\C4IT.F4SD.WebApi.Contracts.cs + + + Common\C4IT.Logging.LogManager.cs + + + Properties\SharedAssemblyInfo.cs + + + + + + + diff --git a/Legacy/F4SDHelper/Properties/AssemblyInfo.cs b/Legacy/F4SDHelper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2df82cd --- /dev/null +++ b/Legacy/F4SDHelper/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("C4IT - F4SD - WebApi for M42")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("eb95e1fb-7a9e-4894-bd28-2d0be2715ebe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/Legacy/F4SDM42WebApi/F4SD - M42WebApi.Legacy.csproj b/Legacy/F4SDM42WebApi/F4SD - M42WebApi.Legacy.csproj new file mode 100644 index 0000000..188d6a8 --- /dev/null +++ b/Legacy/F4SDM42WebApi/F4SD - M42WebApi.Legacy.csproj @@ -0,0 +1,69 @@ + + + net472 + Library + C4IT.F4SDM + C4ITF4SDM42WebApi + false + false + 7.3 + Debug;Release;Release_signed + $(MSBuildProjectDirectory)\..\M42Libraries\12.1.3 + true + + + + true + full + false + + + + pdbonly + true + + + + true + + + + $(M42LegacyLibrariesDir)\Matrix42.Common.dllfalse + $(M42LegacyLibrariesDir)\Matrix42.Contracts.Common.dllfalse + $(M42LegacyLibrariesDir)\Matrix42.Contracts.Platform.dllfalse + $(M42LegacyLibrariesDir)\Matrix42.Contracts.ServiceManagement.dllfalse + $(M42LegacyLibrariesDir)\Matrix42.Pandora.Contracts.dllfalse + $(M42LegacyLibrariesDir)\Matrix42.Services.Description.Contracts.dllfalse + $(M42LegacyLibrariesDir)\Newtonsoft.Json.dllfalse + + + + + + + + $(M42LegacyLibrariesDir)\System.Web.Http.dllfalse + $(M42LegacyLibrariesDir)\System.Web.OData.dllfalse + $(M42LegacyLibrariesDir)\update4u.SPS.DataLayer.dllfalse + $(M42LegacyLibrariesDir)\update4u.SPS.Utility.dllfalse + + + + + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + diff --git a/Legacy/F4SDM42WebApi/F4SDHelperService.cs b/Legacy/F4SDM42WebApi/F4SDHelperService.cs new file mode 100644 index 0000000..3f60c2d --- /dev/null +++ b/Legacy/F4SDM42WebApi/F4SDHelperService.cs @@ -0,0 +1,2730 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; + +using Matrix42.Common; +using update4u.SPS.DataLayer; +using update4u.SPS.DataLayer.Transaction; + +using Newtonsoft.Json; + +using C4IT.F4SDM; +using C4IT.FASD.Base; +using C4IT.Logging; + +using static C4IT.Logging.cLogManager; + +namespace C4IT.F4SD +{ + public class F4SDHelperService + { + private static Guid SPSUserClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSUserClassBase"); + private static Guid SPSAccountClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassBase"); + private static Guid SPSCommonClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSCommonClassBase"); + private static Guid SPSAccountClassADID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassAD"); + private static Guid SPSActivityClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassBase"); + private static Guid SPSActivityClassIncidentID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassIncident"); + private static Guid SPSActivityClassUnitOfWorkID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassUnitOfWork"); + private static Guid SPSScCategoryClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSScCategoryClassBase"); + private static Guid SPSAssetClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAssetClassBase"); + private static Guid SPSSecurityClassRole = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSSecurityClassRole"); + + public static readonly Guid Administrators = new Guid("{A5D7B682-B211-4D94-A96D-8C57EEDAFDEA}"); + + private const string TicketCloseActionId = "51bb3283-7bd1-e511-9a82-60e327035d31"; + private const string TicketDirectLinkCreateTemplate = @"{0}/wm/app-ServiceDesk/notSet/create-object/{1}?view-options=%7B%22embedded%22:false%7D&presetParams={2}"; + private const string IncidentDirectLinkCloseTemplate = @"{0}/wm/app-ServiceDesk/notSet/preview-object/SPSActivityTypeIncident/{1}/0/?view-options={2}"; + private const string TicketDirectLinkPreviewTemplate = @"{0}/wm/app-ServiceDesk/notSet/preview-object/{1}/{2}/0/"; + private const string TicketDirectLinkEditTemplate = @"{0}/wm/app-ServiceDesk/notSet/edit-object/{1}/{2}"; + + private const string placeHolderSubject = "PARAM_SUBJECT"; + private const string placeHolderDescription = "PARAM_DESCRIPTION"; + private const string c4itf4sdmonLinkBase = "f4sdsend://localhost"; + + private const string F4SDTicketTableName = "M42WPM-TICKETS-INFOS"; + private const string F4SDTicketStatusColumnName = "STATUS"; + + + public static IEnumerable ReadLines(Func streamProvider, + Encoding encoding) + { + using (var stream = streamProvider()) + using (var reader = new StreamReader(stream, encoding)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + } + + internal async Task getDirectLinkF4SD(Guid EOID, string type) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Generating F4SD URI for type: '{type}', GUID: '{EOID}'", LogLevels.Debug); + var builder = new UriBuilder(c4itf4sdmonLinkBase); + var queryString = HttpUtility.ParseQueryString(builder.Query); + + switch (type.ToLowerInvariant().Split('.').Last()) + { + case "person": + case "user": + var user = getUsersByAsql($"[Expression-ObjectId]='{EOID}'")?.FirstOrDefault(); + string Sids = string.Empty; + + if (user != null) + { + var accounts = getAccountsByAsql($"Owner='{user.Id}'"); + Sids = string.Join(",", accounts?.Where(x => !string.IsNullOrEmpty(x.Sid)).Select(x => x.Sid) ?? Array.Empty()); + } + + if (!string.IsNullOrEmpty(user?.Name)) + { + queryString.Add("command", "UserSidSearch"); + queryString.Add("name", user.Name); + queryString.Add("sids", Sids); + } + break; + + case "asset": + var asset = getAssetsByAsql($"UsedInTypeSPSComputerType is not null AND [Expression-ObjectId]='{EOID}'")?.FirstOrDefault(); + if (asset != null) + { + queryString.Add("command", "ComputerDomainSearch"); + queryString.Add("name", asset.Name); + queryString.Add("domain", asset.DomainName); + } + break; + + case "incident": + case "servicerequest": + case "ticket": + var ticket = (await getTicketDetails(new List { EOID }))?.FirstOrDefault(); + if (ticket != null) + { + queryString.Add("command", "TicketSearch"); + queryString.Add("tname", ticket.Name); + queryString.Add("tid", ticket.TicketObjectId.ToString()); + + if (ticket.AffectedUserId != Guid.Empty) + { + var initiator = getUsersByAsql($"ID = '{ticket.AffectedUserId}'")?.FirstOrDefault(); + string userName = initiator?.Name; + Sids = string.Join(",", getAccountsByAsql($"Owner='{ticket.AffectedUserId}'")?.Where(x => !string.IsNullOrEmpty(x.Sid)).Select(x => x.Sid) ?? Array.Empty()); + + queryString.Add("uname", userName); + queryString.Add("sids", Sids); + } + } + break; + + default: + break; + } + + builder.Query = queryString.ToString(); + return string.IsNullOrEmpty(builder.Query) ? null : builder.ToString(); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + + [Obsolete] + internal async Task> getTicketListByUser(string userSid, int hours, int queueoption, List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Generating ticket list for userSid: '{userSid}', {hours} hours", LogLevels.Debug); + await Task.Delay(0); + + var tickets = new List(); + var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues); + LogEntry($"ASql Filter: {activityFilter}"); + + var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + string.Format("[Expression-ObjectID] as EOID, TicketNumber, Subject, Service.ID as ServiceId, Service.Name as ServiceName, SUBQUERY(BasicSchemaObjectType as bso, bso.Name, bso.id = base.T(SPSCommonClassBase).TypeID) as ActivityType" + + ", COALESCE(T(SPSActivityClassIncident).Asset.T(SPSComputerClassBase).Name, T(SPSActivityClassIncident).Asset.T(SPSAssetClassSIMCard).PhoneNumber, T(SPSActivityClassIncident).Asset.Name, T(SPSActivityClassIncident).Asset.objectid) as AssetName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSActivityClassIncident).Asset.T(SPSCommonClassBase).TypeID) as AssetCIName" + + ", CASE WHEN Initiator.PrimaryAccount.T(SPSAccountClassAd).Sid = '{0}' THEN 1 ELSE 0 END AS IsPrimaryAccount", + userSid), + activityFilter); + + if (activityTable?.Rows == null || activityTable.Rows.Count == 0) + { + LogEntry($"No activity entries found for userSid: '{userSid}'", LogLevels.Warning); + return null; + } + + for (int i = 0; i < activityTable.Rows.Count; i++) + { + DataRow entry = activityTable.Rows[i]; + var activityEOId = getGuidFromObject(entry["EOID"]); + var ticketNumber = getStringFromObject(entry["TicketNumber"]); + var activityType = getStringFromObject(entry["ActivityType"]); + var assetName = getStringFromObject(entry["AssetName"]); + var ServiceName = getStringFromObject(entry["ServiceName"]); + var assetCIName = getStringFromObject(entry["AssetCIName"]); + var subject = getStringFromObject(entry["Subject"]); + + var stateData = GetActivityState(activityEOId); + var state = stateData.Item1; + var stateDisp = stateData.Item2; + + LogEntry($"Activity found {i + 1}/{activityTable.Rows.Count}: ObjectID={activityEOId}, TicketNumber={ticketNumber}, Subject={subject}, State={state}", LogLevels.Debug); + + if (string.IsNullOrEmpty(ticketNumber)) + { + LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning); + continue; + } + + if (string.IsNullOrEmpty(assetName)) + { + LogEntry($"No AssetName found for activity entry", LogLevels.Debug); + } + + if (string.IsNullOrEmpty(assetCIName)) + { + LogEntry($"No AssetCIName found for activity entry", LogLevels.Debug); + } + var ServiceId = getGuidFromObject(entry["ServiceId"]); + if (ServiceId == Guid.Empty) + { + LogEntry($"no ServiceId found for activity entry", LogLevels.Debug); + } + var AssetCIName = getStringFromObject(entry["AssetCIName"]); + if (AssetCIName == string.Empty) + { + LogEntry($"no AssetCIName found for activity entry", LogLevels.Debug); + } + var Subject = getStringFromObject(entry["Subject"]); + if (Subject == string.Empty) + { + LogEntry($"no Subject found for activity entry", LogLevels.Warning); + continue; + } + var IsPrimaryAccount = getIntFromObject(entry["IsPrimaryAccount"]); + + if (string.IsNullOrEmpty(subject)) + { + LogEntry($"No Subject found for activity entry", LogLevels.Warning); + continue; + } + + tickets.Add(new cF4SDTicketSummary() + { + TicketObjectId = activityEOId, + Name = ticketNumber, + ActivityType = activityType, + AssetCIName = assetCIName, + AssetName = assetName, + ServiceId = ServiceId, + ServiceName = ServiceName, + StatusId = state, + Status = stateDisp, + Summary = subject, + IsPrimaryAccount = IsPrimaryAccount == 1 + }); + } + + tickets = tickets.OrderByDescending(x => x.Name).ToList(); + return tickets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task> getTicketListByUser(Guid userId, int hours, int queueoption, List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Generating ticket list for userId: '{userId}', {hours} hours", LogLevels.Debug); + await Task.Delay(0); + + var tickets = new List(); + var activityFilter = GetActivityFilter(userId, hours, ticketAndServiceRequestEnabled(), queueoption, queues); + LogEntry($"ASql Filter: {activityFilter}"); + + var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + "[Expression-ObjectID] as EOID, TicketNumber, Subject, Service.ID as ServiceId, Service.Name as ServiceName, SUBQUERY(BasicSchemaObjectType as bso, bso.Name, bso.id = base.T(SPSCommonClassBase).TypeID) as ActivityType" + + ", COALESCE(T(SPSActivityClassIncident).Asset.T(SPSComputerClassBase).Name, T(SPSActivityClassIncident).Asset.T(SPSAssetClassSIMCard).PhoneNumber, T(SPSActivityClassIncident).Asset.Name, T(SPSActivityClassIncident).Asset.objectid) as AssetName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSActivityClassIncident).Asset.T(SPSCommonClassBase).TypeID) as AssetCIName", + activityFilter); + + if (activityTable?.Rows == null || activityTable.Rows.Count == 0) + { + LogEntry($"No activity entries found for userId: '{userId}'", LogLevels.Warning); + return null; + } + + for (int i = 0; i < activityTable.Rows.Count; i++) + { + DataRow entry = activityTable.Rows[i]; + var activityEOId = getGuidFromObject(entry["EOID"]); + var ticketNumber = getStringFromObject(entry["TicketNumber"]); + var activityType = getStringFromObject(entry["ActivityType"]); + var assetName = getStringFromObject(entry["AssetName"]); + var ServiceName = getStringFromObject(entry["ServiceName"]); + var assetCIName = getStringFromObject(entry["AssetCIName"]); + var subject = getStringFromObject(entry["Subject"]); + + var stateData = GetActivityState(activityEOId); + var state = stateData.Item1; + var stateDisp = stateData.Item2; + + LogEntry($"Activity found {i + 1}/{activityTable.Rows.Count}: ObjectID={activityEOId}, TicketNumber={ticketNumber}, Subject={subject}, State={state}", LogLevels.Debug); + + if (string.IsNullOrEmpty(ticketNumber)) + { + LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning); + continue; + } + + if (string.IsNullOrEmpty(assetName)) + { + LogEntry($"No AssetName found for activity entry", LogLevels.Debug); + } + + if (string.IsNullOrEmpty(assetCIName)) + { + LogEntry($"No AssetCIName found for activity entry", LogLevels.Debug); + } + var ServiceId = getGuidFromObject(entry["ServiceId"]); + if (ServiceId == Guid.Empty) + { + LogEntry($"no ServiceId found for activity entry", LogLevels.Debug); + } + var AssetCIName = getStringFromObject(entry["AssetCIName"]); + if (AssetCIName == string.Empty) + { + LogEntry($"no AssetCIName found for activity entry", LogLevels.Debug); + } + var Subject = getStringFromObject(entry["Subject"]); + if (Subject == string.Empty) + { + LogEntry($"no Subject found for activity entry", LogLevels.Warning); + continue; + } + + if (string.IsNullOrEmpty(subject)) + { + LogEntry($"No Subject found for activity entry", LogLevels.Warning); + continue; + } + + tickets.Add(new cF4SDTicketSummary() + { + TicketObjectId = activityEOId, + Name = ticketNumber, + ActivityType = activityType, + AssetCIName = assetCIName, + AssetName = assetName, + ServiceId = ServiceId, + ServiceName = ServiceName, + StatusId = state, + Status = stateDisp, + Summary = subject, + IsPrimaryAccount = true + }); + } + + tickets = tickets.OrderByDescending(x => x.Name).ToList(); + return tickets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private static readonly string[] TicketOverviewKeys = new[] + { + "TicketsNew", + "TicketsActive", + "TicketsCritical", + "TicketsNewInfo", + "IncidentNew", + "IncidentActive", + "IncidentCritical", + "IncidentNewInfo", + "UnassignedTickets", + "UnassignedTicketsCritical" + }; + + public class cF4SDTicket : cF4SDTicketSummary + { + public enum enumTicketCreationSource + { + Unknown = 0, + Mail = 1, + Phone = 2, + F4SD = 3 + } + + public class cTicketJournalItem + { + public DateTime CreationDate { get; set; } + public string Header { get; set; } + public string CreatedBy { get; set; } + public string DescriptionHtml { get; set; } + public string Description { get; set; } + public bool IsVisibleForUser { get; set; } + public Guid ActivityObjectId { get; set; } + public Guid JournalId { get; set; } + } + + public Guid AffectedUserId { get; set; } + public Guid AssetId { get; set; } + public DateTime CreationDate { get; set; } + public DateTime? ClosingDate { get; set; } + public int CreationSourceId { get; set; } + public string CreationSource { get; set; } + + public string Description { get; set; } + public string DescriptionHtml { get; set; } + public int PriorityId { get; set; } + public string Priority { get; set; } + public Guid CategoryId { get; set; } + public string Category { get; set; } + public string CategoryHierarchical { get; set; } + public string CIName { get; set; } + public string DirectLinkEdit { get; set; } + public Guid AssetCIId { get; set; } + public int AssetSKUAssetGroupId { get; set; } + public string AssetSKUAssetGroup { get; set; } + public int AssetSKUTypeId { get; set; } + public string AssetSKUType { get; set; } + public string DirectLinkPreview { get; set; } + public string DirectLinkClose { get; set; } + public string AffectedUser { get; set; } + public string SolutionHtml { get; set; } + public string Solution { get; set; } + public string AssetDomain { get; set; } + public string Urgency { get; set; } + public int UrgencyId { get; set; } + public string Impact { get; set; } + public int ImpactId { get; set; } + + } + + public class TicketOverviewCountsResult + { + [JsonProperty("counts")] + public Dictionary Counts { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public class TicketOverviewCountsByRoleResult + { + [JsonProperty("generatedAtUtc")] + public DateTime GeneratedAtUtc { get; set; } = DateTime.UtcNow; + + [JsonProperty("countsByRole")] + public Dictionary> CountsByRole { get; set; } + = new Dictionary>(StringComparer.OrdinalIgnoreCase); + } + + public class TicketOverviewRelationDto + { + public enumF4sdSearchResultClass Type { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public Guid id { get; set; } + public enumF4sdSearchResultStatus Status { get; set; } = enumF4sdSearchResultStatus.Unknown; + public Dictionary Infos { get; set; } = null; + public List Identities { get; set; } = null; + } + + private sealed class TicketOverviewEntry + { + public Guid TicketId { get; set; } + public string TicketNumber { get; set; } + public string Summary { get; set; } + public Guid InitiatorId { get; set; } + public string InitiatorDisplayName { get; set; } + public string InitiatorAccount { get; set; } + public string InitiatorDomain { get; set; } + public string InitiatorSid { get; set; } + public Guid RecipientId { get; set; } + public Guid RecipientRoleId { get; set; } + public int State { get; set; } + public DateTime CreatedDate { get; set; } + public bool NewInformationReceived { get; set; } + public bool ReactionTimeEscalated { get; set; } + public bool SolutionTimeEscalated { get; set; } + public bool IsIncident { get; set; } + public string ActivityType { get; set; } + } + + [Obsolete] + internal async Task getTicketOverviewCounts( + string sid, + string scope, + IEnumerable keys, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + var normalizedKeys = (keys ?? Array.Empty()) + .Where(k => !string.IsNullOrWhiteSpace(k)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (normalizedKeys.Count == 0) + { + normalizedKeys.AddRange(TicketOverviewKeys); + } + + var entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues); + List unassignedEntries = null; + if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey)) + { + unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(sid, queueoption, queues); + } + + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var key in normalizedKeys) + { + if (!useRoleScope && IsUnassignedOverviewKey(key)) + { + counts[key] = (unassignedEntries ?? new List()) + .Count(entry => MatchesTicketOverviewKey(entry, key)); + } + else + { + counts[key] = entries.Count(entry => MatchesTicketOverviewKey(entry, key)); + } + } + + return new TicketOverviewCountsResult { Counts = counts }; + } + catch (Exception E) + { + LogException(E); + return new TicketOverviewCountsResult(); + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task getTicketOverviewCounts( + Guid userId, + string scope, + IEnumerable keys, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + var normalizedKeys = (keys ?? Array.Empty()) + .Where(k => !string.IsNullOrWhiteSpace(k)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (normalizedKeys.Count == 0) + { + normalizedKeys.AddRange(TicketOverviewKeys); + } + + var entries = await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues); + List unassignedEntries = null; + if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey)) + { + unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues); + } + + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var key in normalizedKeys) + { + if (!useRoleScope && IsUnassignedOverviewKey(key)) + { + counts[key] = (unassignedEntries ?? new List()) + .Count(entry => MatchesTicketOverviewKey(entry, key)); + } + else + { + counts[key] = entries.Count(entry => MatchesTicketOverviewKey(entry, key)); + } + } + + return new TicketOverviewCountsResult { Counts = counts }; + } + catch (Exception E) + { + LogException(E); + return new TicketOverviewCountsResult(); + } + finally + { + LogMethodEnd(CM); + } + } + + [Obsolete] + internal async Task getTicketOverviewCountsByRoles( + string sid, + IEnumerable roleGuids, + IEnumerable keys, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var normalizedKeys = (keys ?? Array.Empty()) + .Where(k => !string.IsNullOrWhiteSpace(k)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (normalizedKeys.Count == 0) + { + normalizedKeys.AddRange(TicketOverviewKeys); + } + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, roleGuids); + if (roleIds.Count == 0) + return new TicketOverviewCountsByRoleResult(); + + var entries = await LoadTicketOverviewEntriesByRoleIds(roleIds, queueoption, queues); + var entriesByRole = entries + .GroupBy(entry => entry.RecipientRoleId) + .ToDictionary(group => group.Key, group => group.ToList()); + + var countsByRole = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var roleId in roleIds) + { + var roleEntries = entriesByRole.TryGetValue(roleId, out var list) + ? list + : new List(); + + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var key in normalizedKeys) + { + counts[key] = roleEntries.Count(entry => MatchesTicketOverviewKey(entry, key)); + } + + countsByRole[roleId.ToString()] = counts; + } + + return new TicketOverviewCountsByRoleResult + { + GeneratedAtUtc = DateTime.UtcNow, + CountsByRole = countsByRole + }; + } + catch (Exception E) + { + LogException(E); + return new TicketOverviewCountsByRoleResult(); + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task getTicketOverviewCountsByRoles( + Guid userId, + IEnumerable roleGuids, + IEnumerable keys, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var normalizedKeys = (keys ?? Array.Empty()) + .Where(k => !string.IsNullOrWhiteSpace(k)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (normalizedKeys.Count == 0) + { + normalizedKeys.AddRange(TicketOverviewKeys); + } + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, roleGuids); + if (roleIds.Count == 0) + return new TicketOverviewCountsByRoleResult(); + + var entries = await LoadTicketOverviewEntriesByRoleIds(roleIds, queueoption, queues); + var entriesByRole = entries + .GroupBy(entry => entry.RecipientRoleId) + .ToDictionary(group => group.Key, group => group.ToList()); + + var countsByRole = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var roleId in roleIds) + { + var roleEntries = entriesByRole.TryGetValue(roleId, out var list) + ? list + : new List(); + + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var key in normalizedKeys) + { + counts[key] = roleEntries.Count(entry => MatchesTicketOverviewKey(entry, key)); + } + + countsByRole[roleId.ToString()] = counts; + } + + return new TicketOverviewCountsByRoleResult + { + GeneratedAtUtc = DateTime.UtcNow, + CountsByRole = countsByRole + }; + } + catch (Exception E) + { + LogException(E); + return new TicketOverviewCountsByRoleResult(); + } + finally + { + LogMethodEnd(CM); + } + } + + [Obsolete] + internal async Task> getTicketOverviewRelations( + string sid, + string scope, + string key, + int count, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + if (string.IsNullOrWhiteSpace(key)) + return new List(); + + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + List entries; + if (!useRoleScope && IsUnassignedOverviewKey(key)) + { + entries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(sid, queueoption, queues); + } + else + { + entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues); + } + + var filtered = entries + .Where(entry => MatchesTicketOverviewKey(entry, key)) + .OrderByDescending(entry => entry.CreatedDate) + .ToList(); + + if (count > 0) + filtered = filtered.Take(count).ToList(); + + var relations = new List(filtered.Count); + foreach (var entry in filtered) + { + var relation = new TicketOverviewRelationDto + { + Type = enumF4sdSearchResultClass.Ticket, + DisplayName = entry.TicketNumber ?? string.Empty, + Name = entry.TicketNumber ?? string.Empty, + id = entry.TicketId, + Status = enumF4sdSearchResultStatus.Active, + Infos = new Dictionary + { + ["Summary"] = entry.Summary ?? string.Empty, + ["StatusId"] = ConvertM42State(entry.State), + ["ActivityType"] = entry.ActivityType ?? string.Empty, + ["UserDisplayName"] = entry.InitiatorDisplayName ?? string.Empty, + ["UserAccount"] = entry.InitiatorAccount ?? string.Empty, + ["UserDomain"] = entry.InitiatorDomain ?? string.Empty, + ["UserSid"] = entry.InitiatorSid ?? string.Empty, + ["Sids"] = entry.InitiatorSid ?? string.Empty, + ["UserId"] = entry.InitiatorId != Guid.Empty ? entry.InitiatorId.ToString() : string.Empty, + ["UserGuid"] = entry.InitiatorId != Guid.Empty ? entry.InitiatorId.ToString() : string.Empty + }, + Identities = new List + { + new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = entry.TicketId }, + new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = entry.InitiatorId } + } + }; + relations.Add(relation); + } + + return relations; + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task> getTicketOverviewRelations( + Guid userId, + string scope, + string key, + int count, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + if (string.IsNullOrWhiteSpace(key)) + return new List(); + + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + List entries; + if (!useRoleScope && IsUnassignedOverviewKey(key)) + { + entries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues); + } + else + { + entries = await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues); + } + + var filtered = entries + .Where(entry => MatchesTicketOverviewKey(entry, key)) + .OrderByDescending(entry => entry.CreatedDate) + .ToList(); + + if (count > 0) + filtered = filtered.Take(count).ToList(); + + var relations = new List(filtered.Count); + foreach (var entry in filtered) + { + var relation = new TicketOverviewRelationDto + { + Type = enumF4sdSearchResultClass.Ticket, + DisplayName = entry.TicketNumber ?? string.Empty, + Name = entry.TicketNumber ?? string.Empty, + id = entry.TicketId, + Status = enumF4sdSearchResultStatus.Active, + Infos = new Dictionary + { + ["Summary"] = entry.Summary ?? string.Empty, + ["StatusId"] = ConvertM42State(entry.State), + ["ActivityType"] = entry.ActivityType ?? string.Empty, + ["UserDisplayName"] = entry.InitiatorDisplayName ?? string.Empty, + ["UserAccount"] = entry.InitiatorAccount ?? string.Empty, + ["UserDomain"] = entry.InitiatorDomain ?? string.Empty, + ["UserSid"] = entry.InitiatorSid ?? string.Empty, + ["Sids"] = entry.InitiatorSid ?? string.Empty, + ["UserId"] = entry.InitiatorId != Guid.Empty ? entry.InitiatorId.ToString() : string.Empty, + ["UserGuid"] = entry.InitiatorId != Guid.Empty ? entry.InitiatorId.ToString() : string.Empty + }, + Identities = new List + { + new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = entry.TicketId }, + new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = entry.InitiatorId } + } + }; + relations.Add(relation); + } + + return relations; + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + [Obsolete] + private async Task> LoadTicketOverviewEntries( + string sid, + bool useRoleScope, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + if (string.IsNullOrWhiteSpace(sid)) + return new List(); + + var filter = await BuildTicketOverviewFilterAsync(sid, useRoleScope, queueoption, queues); + return await LoadTicketOverviewEntriesByFilter(filter); + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private async Task> LoadTicketOverviewEntries( + Guid userId, + bool useRoleScope, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + if (userId == Guid.Empty) + return new List(); + + var filter = await BuildTicketOverviewFilterAsync(userId, useRoleScope, queueoption, queues); + return await LoadTicketOverviewEntriesByFilter(filter); + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + [Obsolete] + private async Task> LoadTicketOverviewUnassignedEntriesForPersonalScope( + string sid, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + if (string.IsNullOrWhiteSpace(sid)) + return new List(); + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null); + var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues); + if (string.IsNullOrWhiteSpace(filter)) + return new List(); + + filter += " AND Recipient IS NULL"; + return await LoadTicketOverviewEntriesByFilter(filter); + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private async Task> LoadTicketOverviewUnassignedEntriesForPersonalScope( + Guid userId, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + if (userId == Guid.Empty) + return new List(); + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null); + var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues); + if (string.IsNullOrWhiteSpace(filter)) + return new List(); + + filter += " AND Recipient IS NULL"; + return await LoadTicketOverviewEntriesByFilter(filter); + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private async Task> LoadTicketOverviewEntriesByRoleIds( + IEnumerable roleIds, + int queueoption, + List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues); + return await LoadTicketOverviewEntriesByFilter(filter); + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private async Task> LoadTicketOverviewEntriesByFilter(string filter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + if (string.IsNullOrWhiteSpace(filter)) + return new List(); + + var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + "[Expression-ObjectID] as EOID" + + ", TicketNumber" + + ", Subject" + + ", Initiator as InitiatorId" + + ", Initiator.LastName + ISNULL(', ' + Initiator.FirstName, '') as Initiator" + + ", Initiator.PrimaryAccount.T(SPSAccountClassAD).NBAccountName as InitiatorAccount" + + ", Initiator.PrimaryAccount.T(SPSAccountClassAD).Domain.NT4Name as InitiatorDomain" + + ", Initiator.PrimaryAccount.T(SPSAccountClassAD).Sid as InitiatorSid" + + ", Recipient as RecipientId" + + ", RecipientRole.T(SPSSecurityClassRole).ID as RecipientRoleId" + + ", T(SPSCommonClassBase).State as State" + + ", CreatedDate" + + ", NewInformationReceived" + + ", ReactionTimeEscalated" + + ", SolutionTimeEscalated" + + ", UsedInTypeSPSActivityTypeTicket as TicketEoid" + + ", UsedInTypeSPSActivityTypeIncident as IncidentEoid" + + ", UsedInTypeSPSActivityTypeAlert as AlertEoid" + + ", UsedInTypeSPSActivityTypeGroupTicket as ProblemEoid" + + ", UsedInTypeSPSActivityTypeServiceRequest as ServiceRequestEoid" + + ", UsedInTypeSPSActivityTypeBase as TaskEoid" + + ", UsedInTypeSVMChangeRequestType as ChangeEoid", + filter); + + if (tbl?.Rows == null || tbl.Rows.Count == 0) + return new List(); + + var entries = new List(tbl.Rows.Count); + foreach (DataRow row in tbl.Rows) + { + var ticketId = getGuidFromObject(row["EOID"]); + if (ticketId == Guid.Empty) + continue; + + var ticketEoid = getGuidFromObject(row["TicketEoid"]); + var incidentEoid = getGuidFromObject(row["IncidentEoid"]); + var alertEoid = getGuidFromObject(row["AlertEoid"]); + var problemEoid = getGuidFromObject(row["ProblemEoid"]); + var serviceRequestEoid = getGuidFromObject(row["ServiceRequestEoid"]); + var taskEoid = getGuidFromObject(row["TaskEoid"]); + var changeEoid = getGuidFromObject(row["ChangeEoid"]); + var isIncident = incidentEoid != Guid.Empty; + var activityType = ResolveTicketOverviewActivityType( + ticketEoid, + incidentEoid, + alertEoid, + problemEoid, + serviceRequestEoid, + taskEoid, + changeEoid); + + entries.Add(new TicketOverviewEntry + { + TicketId = ticketId, + TicketNumber = getStringFromObject(row["TicketNumber"]), + Summary = getStringFromObject(row["Subject"]), + InitiatorId = getGuidFromObject(row["InitiatorId"]), + InitiatorDisplayName = getStringFromObject(row["Initiator"]), + InitiatorAccount = getStringFromObject(row["InitiatorAccount"]), + InitiatorDomain = getStringFromObject(row["InitiatorDomain"]), + InitiatorSid = getStringFromObject(row["InitiatorSid"]), + RecipientId = getGuidFromObject(row["RecipientId"]), + RecipientRoleId = getGuidFromObject(row["RecipientRoleId"]), + State = getIntFromObject(row["State"]), + CreatedDate = getDateTimeFromObject(row["CreatedDate"]), + NewInformationReceived = GetBoolValue(row["NewInformationReceived"]), + ReactionTimeEscalated = GetBoolValue(row["ReactionTimeEscalated"]), + SolutionTimeEscalated = GetBoolValue(row["SolutionTimeEscalated"]), + IsIncident = isIncident, + ActivityType = activityType + }); + } + + return entries; + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private static string ResolveTicketOverviewActivityType( + Guid ticketEoid, + Guid incidentEoid, + Guid alertEoid, + Guid problemEoid, + Guid serviceRequestEoid, + Guid taskEoid, + Guid changeEoid) + { + if (incidentEoid != Guid.Empty) + return "SPSActivityTypeIncident"; + if (ticketEoid != Guid.Empty) + return "SPSActivityTypeTicket"; + if (alertEoid != Guid.Empty) + return "SPSActivityTypeAlert"; + if (problemEoid != Guid.Empty) + return "SPSActivityTypeGroupTicket"; + if (serviceRequestEoid != Guid.Empty) + return "SPSActivityTypeServiceRequest"; + if (taskEoid != Guid.Empty) + return "SPSActivityTypeBase"; + if (changeEoid != Guid.Empty) + return "SVMChangeRequestType"; + + return null; + } + + [Obsolete] + private async Task BuildTicketOverviewFilterAsync( + string sid, + bool useRoleScope, + int queueoption, + List queues) + { + var filter = BuildTicketOverviewBaseFilter(queueoption, queues); + + if (!useRoleScope) + { + var recipientFilter = $"Recipient.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'"; + filter += $" AND ({recipientFilter})"; + return filter; + } + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null); + return BuildTicketOverviewFilterForRoleIds(roleIds, filter); + } + + private async Task BuildTicketOverviewFilterAsync( + Guid userId, + bool useRoleScope, + int queueoption, + List queues) + { + var filter = BuildTicketOverviewBaseFilter(queueoption, queues); + + if (!useRoleScope) + { + var recipientFilter = $"Recipient = '{Escape(userId.ToString("D"))}'"; + filter += $" AND ({recipientFilter})"; + return filter; + } + + var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null); + return BuildTicketOverviewFilterForRoleIds(roleIds, filter); + } + + private string BuildTicketOverviewBaseFilter(int queueoption, List queues) + { + var filter = "T(SPSCommonClassBase).State <> 204"; + + if (ticketAndServiceRequestEnabled()) + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)"; + } + else + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)"; + } + + filter = AppendQueueFilter(filter, queueoption, queues); + return filter; + } + + private string BuildTicketOverviewFilterForRoleIds( + IEnumerable roleIds, + string baseFilter = null, + int queueoption = 0, + List queues = null) + { + var filter = string.IsNullOrWhiteSpace(baseFilter) + ? BuildTicketOverviewBaseFilter(queueoption, queues) + : baseFilter; + + var roleClause = BuildRoleIdInClause(roleIds); + if (string.IsNullOrWhiteSpace(roleClause)) + return null; + + filter += $" AND {roleClause}"; + return filter; + } + + private static string BuildRoleIdInClause(IEnumerable roleIds) + { + var roleIdList = (roleIds ?? Enumerable.Empty()) + .Where(id => id != Guid.Empty) + .Distinct() + .Select(id => $"'{id}'") + .ToList(); + + if (roleIdList.Count == 0) + return null; + + return $"RecipientRole.T(SPSSecurityClassRole).ID IN ({string.Join(", ", roleIdList)})"; + } + + private string AppendQueueFilter(string filter, int queueoption, List queues) + { + if (queues != null && queues.Count > 0) + { + var escapedNames = queues + .Select(q => $"'{Escape(q.QueueName)}'") + .ToList(); + var escapedIds = queues + .Select(q => $"'{Escape(q.QueueID.ToString())}'") + .ToList(); + + string nameList = string.Join(", ", escapedNames); + string idList = string.Join(", ", escapedIds); + + switch (queueoption) + { + case 1: + filter += + $" AND (" + + "Queue IS NULL" + + $" OR Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList})" + + ")"; + break; + case 2: + filter += + $" AND (" + + "Queue IS NOT NULL" + + $" AND (Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList}))" + + ")"; + break; + case 3: + filter += " AND Queue IS NULL"; + break; + default: + break; + } + } + else if (queueoption == 3) + { + filter += " AND Queue IS NULL"; + } + + return filter; + } + + [Obsolete] + private async Task> ResolveTicketOverviewRoleIdsAsync(string sid, IEnumerable roleGuids) + { + var roleIds = (roleGuids ?? Enumerable.Empty()) + .Where(id => id != Guid.Empty) + .Distinct() + .ToList(); + + if (roleIds.Count > 0) + return roleIds; + + if (string.IsNullOrWhiteSpace(sid)) + return new List(); + + var userId = getUserBySid(sid); + if (userId == Guid.Empty) + return new List(); + + var roles = await getRoleMembershipById(userId) ?? new List(); + return roles + .Where(role => role != null && role.Id != Guid.Empty) + .Select(role => role.Id) + .Distinct() + .ToList(); + } + + private async Task> ResolveTicketOverviewRoleIdsAsync(Guid userId, IEnumerable roleGuids) + { + var roleIds = (roleGuids ?? Enumerable.Empty()) + .Where(id => id != Guid.Empty) + .Distinct() + .ToList(); + + if (roleIds.Count > 0) + return roleIds; + + if (userId == Guid.Empty) + return new List(); + + var roles = await getRoleMembershipById(userId) ?? new List(); + return roles + .Where(role => role != null && role.Id != Guid.Empty) + .Select(role => role.Id) + .Distinct() + .ToList(); + } + private static bool IsUnassignedOverviewKey(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return false; + + switch (key.Trim()) + { + case "UnassignedTickets": + case "UnassignedTicketsCritical": + return true; + default: + return false; + } + } + + private static bool MatchesTicketOverviewKey(TicketOverviewEntry entry, string key) + { + if (entry == null || string.IsNullOrWhiteSpace(key)) + return false; + + var isTicket = !entry.IsIncident; + var hasPerson = entry.RecipientId != Guid.Empty; + var hasRole = entry.RecipientRoleId != Guid.Empty; + var isCritical = entry.ReactionTimeEscalated || entry.SolutionTimeEscalated; + var isNew = entry.State == 200; + var isActive = entry.State == 201 || entry.State == 202 || entry.State == 203; + + switch (key.Trim()) + { + case "TicketsNew": + return isTicket && (entry.State == 200 || entry.State == 201); + case "TicketsActive": + return isTicket && isActive; + case "TicketsCritical": + return isTicket && isCritical; + case "TicketsNewInfo": + return isTicket && entry.NewInformationReceived; + case "IncidentNew": + return entry.IsIncident && (entry.State == 200 || entry.State == 201); + case "IncidentActive": + return entry.IsIncident && isActive; + case "IncidentCritical": + return entry.IsIncident && isCritical; + case "IncidentNewInfo": + return entry.IsIncident && entry.NewInformationReceived; + case "UnassignedTickets": + return !hasPerson && hasRole && isNew; + case "UnassignedTicketsCritical": + return !hasPerson && hasRole && isCritical; + default: + return false; + } + } + + private static bool GetBoolValue(object value) + { + if (value == null || value is DBNull) + return false; + if (value is bool boolValue) + return boolValue; + if (value is int intValue) + return intValue != 0; + if (value is long longValue) + return longValue != 0; + + if (bool.TryParse(value.ToString(), out var parsedBool)) + return parsedBool; + if (int.TryParse(value.ToString(), out var parsedInt)) + return parsedInt != 0; + + return false; + } + + private static string ConvertM42State(int state) + { + switch (state) + { + case 200: + return "New"; + case 201: + case 202: + return "InProgress"; + case 203: + return "OnHold"; + case 204: + return "Closed"; + default: + return "Unknown"; + } + } + private (int, string) GetActivityState(Guid activityEOId) + { + var tbl3 = FragmentRequestBase.SimpleLoad(SPSCommonClassBaseID, + "State.Value as State, State.DisplayString as StateDisp", $"[Expression-ObjectID] = '{activityEOId}'"); + if (tbl3?.Rows == null || tbl3.Rows.Count <= 0) + { + LogEntry($"SPSCommonClassBase fragment not found: ClassId={SPSCommonClassBaseID}, ObjectId={activityEOId}", LogLevels.Debug); + return (0, ""); + } + var state = getIntFromObject(tbl3.Rows[0]["State"]); + var stateDisp = getStringFromObject(tbl3.Rows[0]["StateDisp"]); + return (state, stateDisp); + } + private string Escape(string input) + { + return input?.Replace("'", "''"); + } + + [Obsolete] + private string GetActivityFilter( + string userSid, + int hours, + bool ticketAndServiceRequestEnabled, + int queueoption, + List queues + ) + { + // Baseline-Filter auf User und Datum + var filter = $"Initiator.Accounts.T(SPSAccountClassAd).Sid = '{Escape(userSid)}'"; + + string fStartDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow.AddHours(-hours), true); + string fEndDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow, true); + + // Incident vs. Ticket/ServiceRequest + if (ticketAndServiceRequestEnabled) + { + filter += + " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)"; + } + else + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)"; + } + + // Offene bzw. kürzlich geschlossene Objekte + filter += + " AND (T(SPSCommonClassBase).State <> 204" + + $" OR (ClosedDate > {fStartDate} AND ClosedDate < {fEndDate}))"; + + // Queue-Filter nur, wenn tatsächlich Queues übergeben wurden + if (queues != null && queues.Count > 0) + { + // URL-escaping für Name und ID + var escapedNames = queues + .Select(q => $"'{Escape(q.QueueName)}'") + .ToList(); + var escapedIds = queues + .Select(q => $"'{Escape(q.QueueID.ToString())}'") + .ToList(); + + string nameList = string.Join(", ", escapedNames); + string idList = string.Join(", ", escapedIds); + + switch (queueoption) + { + // 1 = entweder keine Queue oder eine der übergebenen Queues (Name oder ID) + case 1: + filter += + $" AND (" + + "Queue IS NULL" + + $" OR Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList})" + + ")"; + break; + + // 2 = nur die übergebenen Queues (Name oder ID) + case 2: + filter += + $" AND (" + + "Queue IS NOT NULL" + + $" AND (Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList}))" + + ")"; + break; + + // 3 = nur Objekte ohne Queue + case 3: + filter += " AND Queue IS NULL"; + break; + + // 0 oder andere = keine zusätzliche Einschränkung + default: + break; + } + } + else if (queueoption == 3) + { + // Ausnahme: wenn keine Queues übergeben, aber Option 3 = nur ohne Queue + filter += " AND Queue IS NULL"; + } + + return filter; + } + + private string GetActivityFilter( + Guid userId, + int hours, + bool ticketAndServiceRequestEnabled, + int queueoption, + List queues + ) + { + // Baseline-Filter auf User und Datum + var filter = $"Initiator = '{Escape(userId.ToString("D"))}'"; + + string fStartDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow.AddHours(-hours), true); + string fEndDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow, true); + + // Incident vs. Ticket/ServiceRequest + if (ticketAndServiceRequestEnabled) + { + filter += + " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)"; + } + else + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)"; + } + + // Offene bzw. kürzlich geschlossene Objekte + filter += + " AND (T(SPSCommonClassBase).State <> 204" + + $" OR (ClosedDate > {fStartDate} AND ClosedDate < {fEndDate}))"; + + // Queue-Filter nur, wenn tatsächlich Queues übergeben wurden + if (queues != null && queues.Count > 0) + { + // URL-escaping für Name und ID + var escapedNames = queues + .Select(q => $"'{Escape(q.QueueName)}'") + .ToList(); + var escapedIds = queues + .Select(q => $"'{Escape(q.QueueID.ToString())}'") + .ToList(); + + string nameList = string.Join(", ", escapedNames); + string idList = string.Join(", ", escapedIds); + + switch (queueoption) + { + // 1 = entweder keine Queue oder eine der übergebenen Queues (Name oder ID) + case 1: + filter += + $" AND (" + + "Queue IS NULL" + + $" OR Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList})" + + ")"; + break; + + // 2 = nur die übergebenen Queues (Name oder ID) + case 2: + filter += + $" AND (" + + "Queue IS NOT NULL" + + $" AND (Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList}))" + + ")"; + break; + + // 3 = nur Objekte ohne Queue + case 3: + filter += " AND Queue IS NULL"; + break; + + // 0 oder andere = keine zusätzliche Einschränkung + default: + break; + } + } + else if (queueoption == 3) + { + // Ausnahme: wenn keine Queues übergeben, aber Option 3 = nur ohne Queue + filter += " AND Queue IS NULL"; + } + + return filter; + } + + internal async Task getDirectLinkCreateTicket(string sid, string assetname) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + // get global settings + bool TicketAndServiceRequestEnabled = ticketAndServiceRequestEnabled(); + + var user = getUserBySid(sid); + var asset = getAssetByName(assetname); + dynamic presetParamsDyn = new ExpandoObject(); + + presetParamsDyn.SPSActivityClassBase = new ExpandoObject(); + presetParamsDyn.SPSActivityClassBase.Subject = placeHolderSubject; + presetParamsDyn.SPSActivityClassBase.DescriptionHTML = placeHolderDescription; + if (user != Guid.Empty) + presetParamsDyn.SPSActivityClassBase.Initiator = user; + if (asset != null) + { + presetParamsDyn.SPSActivityClassIncident = new ExpandoObject(); + presetParamsDyn.SPSActivityClassIncident.Asset = asset.Id; + } + var type = TicketAndServiceRequestEnabled ? "SPSActivityTypeTicket" : "SPSActivityTypeIncident"; + var presetParams = HttpUtility.UrlEncode(JsonConvert.SerializeObject(presetParamsDyn)); + var directLink = string.Format(TicketDirectLinkCreateTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, type, presetParams); + return new DirectLink() + { + Link = directLink, + DescriptionParameter = placeHolderDescription, + SubjectParameter = placeHolderSubject + }; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private static bool ticketAndServiceRequestEnabled() + { + var configProvider = F4SDM42WebApiController.defaultInstance._globalConfigurationProvider; + + // using reflection because signature of "ReloadSettings" changed in ESM v12 + var method = configProvider.GetType().GetMethod("ReloadSettings"); + + if (method != null) + { + var defaultParameters = method.GetParameters().Select(p => p.HasDefaultValue ? p.DefaultValue : null).ToArray(); + method.Invoke(configProvider, defaultParameters); + } + + return configProvider.ServiceDeskConfiguration.TicketAndServiceRequestEnabled; + } + + internal async Task> getTicketDetails(List ticketObjectIds) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + var Filter = AsqlHelper.BuildInCondition("[Expression-ObjectId]", ticketObjectIds); + LogEntry($"ASql Filter: {Filter}"); + + var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + "[Expression-ObjectId] as EOID" + + ", TicketNumber" + + ", Subject" + + ", Category.Name as Category" + + ", Category as CategoryId" + + ", CreatedDate, ClosedDate" + + ", Initiator as InitiatorId" + + ", Initiator.LastName + ISNULL(', ' + Initiator.FirstName, '') as Initiator" + + ", DescriptionHTML" + + ", Impact" + + ", Impact.DisplayString as ImpactDisp" + + ", Urgency" + + ", Urgency.DisplayString as UrgencyDisp" + + ", Priority" + + ", Priority.Description as PrioDisp" + + ", SolutionHTML", Filter); + + if (tbl == null || tbl.Rows == null) + { + LogEntry($"No activity entry list found with [Expression-ObjectId]='{string.Join(", ", ticketObjectIds)}'", LogLevels.Warning); + return null; + } + + var tickets = new List(); + + foreach (DataRow Entry in tbl.Rows) + { + var ActivityEOID = getGuidFromObject(Entry["EOID"]); + if (ActivityEOID == Guid.Empty) + { + LogEntry($"No expression object id found for activity entry", LogLevels.Warning); + continue; + } + + var InitiatorId = getGuidFromObject(Entry["InitiatorId"]); + var Initiator = getStringFromObject(Entry["Initiator"]); + if (InitiatorId == Guid.Empty) + { + LogEntry($"No Initiator id found for activity entry", LogLevels.Warning); + } + + var TicketNumber = getStringFromObject(Entry["TicketNumber"]); + if (string.IsNullOrEmpty(TicketNumber)) + { + LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning); + continue; + } + + var CategoryId = getGuidFromObject(Entry["CategoryId"]); + if (CategoryId == Guid.Empty) + { + LogEntry($"No Initiator object id found for activity entry", LogLevels.Warning); + continue; + } + + var Category = getStringFromObject(Entry["Category"]); + if (string.IsNullOrEmpty(Category)) + { + LogEntry($"No Category found for activity entry", LogLevels.Warning); + continue; + } + + var CreatedDate = getDateTimeFromObject(Entry["CreatedDate"]); + if (CreatedDate == DateTime.MinValue) + { + LogEntry($"No CreationDate found for activity entry", LogLevels.Warning); + continue; + } + + var ClosedDate = getDateTimeFromObject(Entry["ClosedDate"]); + var DescriptionHtml = getStringFromObject(Entry["DescriptionHTML"]); + var Description = DescriptionHtml != string.Empty ? Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(DescriptionHtml) : string.Empty; + + var SolutionHtml = getStringFromObject(Entry["SolutionHTML"]); + var Solution = SolutionHtml != string.Empty ? Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(SolutionHtml) : string.Empty; + + var Subject = getStringFromObject(Entry["Subject"]); + if (string.IsNullOrEmpty(Subject)) + { + LogEntry($"No Subject found for activity entry", LogLevels.Warning); + continue; + } + + var PriorityId = getIntFromObject(Entry["Priority"]); + var Priority = getStringFromObject(Entry["PrioDisp"]); + + var UrgencyId = getIntFromObject(Entry["Urgency"]); + var Urgency = getStringFromObject(Entry["UrgencyDisp"]); + + var ImpactId = getIntFromObject(Entry["Impact"]); + var Impact = getStringFromObject(Entry["ImpactDisp"]); + + LogEntry($"get category information: {Filter}"); + Filter = string.Format("Recursive(Children).T(SPSScCategoryClassBase).Id='{0}'", CategoryId); + var tbl1 = FragmentRequestBase.SimpleLoad(SPSScCategoryClassBaseID, "Name", Filter); + if (tbl1?.Rows == null || tbl1.Rows.Count <= 0) + { + LogEntry($"No Category entry list found with Initiator='{CategoryId}'", LogLevels.Warning); + return null; + } + + var CategoryHierarchical = string.Join(" > ", tbl1.Rows.Cast().Reverse().Select(row => getStringFromObject(row["Name"]))); + + var tbl2 = FragmentRequestBase.SimpleLoad(SPSActivityClassIncidentID, "EntryBy as EntryBy, EntryBy.DisplayString as EntryByDisp, COALESCE(AssetAffected, Asset) as Asset, COALESCE(AssetAffected.T(SPSComputerClassAD).Domain.NT4Name, Asset.T(SPSComputerClassAD).Domain.NT4Name) as AssetDomain, COALESCE(COALESCE(AssetAffected.T(SPSComputerClassBase).Name, AssetAffected.T(SPSAssetClassSIMCard).PhoneNumber, AssetAffected.Name, AssetAffected.objectid), COALESCE(Asset.T(SPSComputerClassBase).Name, Asset.T(SPSAssetClassSIMCard).PhoneNumber, Asset.Name, Asset.objectid)) as AssetName", $"[Expression-ObjectID] = '{ActivityEOID}'"); + + if (tbl2?.Rows == null || tbl2.Rows.Count <= 0) + { + LogEntry($"SPSActivityClassIncident fragment not found: ClassId={SPSActivityClassIncidentID}, ObjectId={ActivityEOID}", LogLevels.Debug); + continue; + } + + var AssetId = getGuidFromObject(tbl2.Rows[0]["Asset"]); + var Asset = getStringFromObject(tbl2.Rows[0]["AssetName"]); + var AssetDomain = getStringFromObject(tbl2.Rows[0]["AssetDomain"]); + var EntryBy = getIntFromObject(tbl2.Rows[0]["EntryBy"]); + var EntryByDisp = getStringFromObject(tbl2.Rows[0]["EntryByDisp"]); + + M42Asset AssetObject = null; + if (AssetId != Guid.Empty) + AssetObject = getAssetsByAsql($"ID = '{AssetId}'").FirstOrDefault(); + + var tbl3 = FragmentRequestBase.SimpleLoad(SPSCommonClassBaseID, "State, State.DisplayString as StateDisp, SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.TypeID) as CIName", $"[Expression-ObjectID] = '{ActivityEOID}'"); + if (tbl3?.Rows == null || tbl3.Rows.Count <= 0) + { + LogEntry($"SPSCommonClassBase fragment not found: ClassId={SPSCommonClassBaseID}, ObjectId={ActivityEOID}", LogLevels.Debug); + continue; + } + + var State = getIntFromObject(tbl3.Rows[0]["State"]); + var StateDisp = getStringFromObject(tbl3.Rows[0]["StateDisp"]); + var CIName = getStringFromObject(tbl3.Rows[0]["CIName"]); + + dynamic viewOptionsDyn = new ExpandoObject(); + viewOptionsDyn.objectId = ActivityEOID; + viewOptionsDyn.type = CIName; + viewOptionsDyn.viewType = "action"; + viewOptionsDyn.actionId = TicketCloseActionId; + var viewOptions = HttpUtility.UrlEncode(JsonConvert.SerializeObject(viewOptionsDyn)); + + var ticketDirectLinkPreview = string.Format(TicketDirectLinkPreviewTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID); + var ticketDirectLinkEdit = string.Format(TicketDirectLinkEditTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID); + var ticketDirectLinkClose = string.Format(IncidentDirectLinkCloseTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, ActivityEOID, viewOptions); + + LogEntry($"Activity found: ObjectID={ActivityEOID}, TicketNumber={TicketNumber}, Subject={Subject}, State={State}", LogLevels.Debug); + var ticket = new cF4SDTicket() + { + TicketObjectId = ActivityEOID, + Name = TicketNumber, + CIName = CIName, + StatusId = State, + Status = StateDisp, + Summary = Subject, + AffectedUserId = InitiatorId, + AffectedUser = Initiator, + CreationDate = CreatedDate, + ClosingDate = ClosedDate, + DescriptionHtml = DescriptionHtml, + Description = Description, + SolutionHtml = SolutionHtml, + Solution = Solution, + Priority = Priority, + PriorityId = PriorityId, + CreationSourceId = EntryBy, + CreationSource = EntryByDisp, + CategoryHierarchical = CategoryHierarchical, + CategoryId = CategoryId, + Category = Category, + DirectLinkPreview = ticketDirectLinkPreview, + Urgency = Urgency, + UrgencyId = UrgencyId, + Impact = Impact, + ImpactId = ImpactId + //JournalItems = GetJournalEntriesByEOID(ActivityEOID) + }; + + if (AssetObject != null) + { + ticket.AssetId = AssetId; + ticket.AssetName = Asset; + ticket.AssetDomain = AssetDomain; + ticket.AssetCIId = AssetObject.CIId; + ticket.AssetCIName = AssetObject.CIName; + ticket.AssetSKUAssetGroupId = AssetObject.SKUAssetGroupId; + ticket.AssetSKUAssetGroup = AssetObject.SKUAssetGroup; + ticket.AssetSKUTypeId = AssetObject.SKUTypeId; + ticket.AssetSKUType = AssetObject.SKUType; + } + + if (ticket.StatusId != 204) + ticket.DirectLinkEdit = ticketDirectLinkEdit; + + if ((CIName.ToUpper() == "SPSACTIVITYTYPEINCIDENT" || CIName.ToUpper() == "SPSACTIVITYTYPESERVICEREQUEST") && ticket.StatusId != 204) + ticket.DirectLinkClose = ticketDirectLinkClose; + + tickets.Add(ticket); + } + + tickets = tickets.OrderBy(x => x.CreationDate).ToList(); + tickets.Reverse(); + return tickets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task> GetJournalEntries(Guid activityEOID) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + List journalEntries = new List(); + var entries = F4SDM42WebApiController.defaultInstance._journalService.GetJournalList(activityEOID, false, 0, 50, "{2}", 120); + LogEntry($"{entries.Length} Journal entries found for ObjectID={activityEOID}", LogLevels.Debug); + for (int i = 0; i < entries.Length; i++) + { + Matrix42.Contracts.Platform.Data.JournalEntryInfo item = entries[i]; + LogEntry($"Journal entry {i + 1}/{entries.Length}: ID={item.Id}, CreatedDate={item.CreatedDate}, CreatedBy={item.Creator}, Header={item.Header}", LogLevels.Debug); + journalEntries.Add(new cF4SDTicket.cTicketJournalItem() + { + JournalId = item.Id, + ActivityObjectId = activityEOID, + CreatedBy = item.Creator, + CreationDate = (DateTime)item.CreatedDate, + DescriptionHtml = item.Text, + Description = Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(item.Text), + Header = item.Header, + IsVisibleForUser = item.VisibleInPortal + }); + } + return journalEntries; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task updateActivitySolution(Guid objectId, string solutionHtml) + { + + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + if (objectId == Guid.Empty) + return false; + + var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, "ID", $"[Expression-ObjectID] = '{objectId}'"); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"SPSActivityClassBase fragment not found: ClassId={SPSActivityClassBaseID}, ObjectId={objectId}", LogLevels.Debug); + return false; + } + + var fragmentId = getGuidFromObject(tbl.Rows[0]["ID"]); + SPSFragment activityFragment = ObjectBroker.GetFragment(SPSActivityClassBaseID, fragmentId); + activityFragment["SolutionHTML"] = solutionHtml; + ObjectBroker.UpdateFragment(SPSActivityClassBaseID, activityFragment); + return true; + } + catch (Exception E) + { + LogException(E); + return false; + } + finally + { + LogMethodEnd(CM); + } + } + + private List getAssetsByAsql(string Filter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + // get SPSAssetClassBase + if (string.IsNullOrEmpty(Filter)) + return null; + List assets = new List(); + LogEntry($"ASql Filter: {Filter}"); + var tbl = FragmentRequestBase.SimpleLoad(SPSAssetClassBaseID + , "Id" + + ", COALESCE(T(SPSComputerClassBase).Name, T(SPSAssetClassSIMCard).PhoneNumber, Name) as AssetName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSCommonClassBase).TypeID) as CIName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Id, t.ID=base.T(SPSCommonClassBase).TypeID) as CIId" + + ", SKU.Type as SKUTypeId" + + ", SKU.Type.DisplayString as SKUType" + + ", SKU.Type.AssetGroup as SKUAssetGroupId" + + ", SUBQUERY(SPSAssetPickupTypeCategory AS ag, ag.DisplayString, ag.Value=base.SKU.Type.AssetGroup) as SKUAssetGroup" + + ", T(SPSComputerClassAD).Domain.NT4Name as DomainName" + + ", T(SPSComputerClassAD).Sid as Sid" + , Filter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"SPSAssetClassBase fragment not found: ClassId={SPSActivityClassIncidentID}, Filter={Filter}", LogLevels.Debug); + return null; + } + else + { + foreach (DataRow row in tbl.Rows) + { + var asset = new M42Asset() + { + Id = getGuidFromObject(row["Id"]), + Name = getStringFromObject(row["AssetName"]), + CIName = getStringFromObject(row["CIName"]), + CIId = getGuidFromObject(row["CIId"]), + SKUTypeId = getIntFromObject(row["SKUTypeId"]), + SKUType = getStringFromObject(row["SKUType"]), + SKUAssetGroupId = getIntFromObject(row["SKUAssetGroupId"]), + SKUAssetGroup = getStringFromObject(row["SKUAssetGroup"]), + DomainName = getStringFromObject(row["DomainName"]), + Sid = getStringFromObject(row["Sid"]), + }; + assets.Add(asset); + LogEntry($"Asset found: Id={asset.Id}, Name={asset.Name}, CIName={asset.CIName}", LogLevels.Debug); + } + } + return assets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private M42Asset getAssetByName(string assetname) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + // get SPSAssetClassBase + if (string.IsNullOrEmpty(assetname)) + return null; + var Filter = string.Format("T(SPSComputerClassBase).Name = '{0}' OR T(SPSAssetClassSIMCard).PhoneNumber = '{0}' OR Name = '{0}'", assetname); + return getAssetsByAsql(Filter)?.First(); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + private Guid getUserBySid(string sid) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + string sidPattern = @"^S-\d-\d+-(\d+-){1,14}\d+$"; + if (string.IsNullOrEmpty(sid) || !Regex.IsMatch(sid, sidPattern)) + { + LogEntry($"Invalid or empty SID: '{sid}'", LogLevels.Warning); + return Guid.Empty; + } + + var users = getUsersByAsql(string.Format("Accounts.T(SPSAccountClassAD).Sid = '{0}' OR PrimaryAccount.T(SPSAccountClassAD).Sid = '{0}'", sid)); + return users != null ? users.First().Id : Guid.Empty; + } + catch (Exception E) + { + LogException(E); + return Guid.Empty; + } + finally + { + LogMethodEnd(CM); + } + } + + + private List getUsersByAsql(string asqlFilter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var Persons = new List(); + + + LogEntry($"ASql Filter: {asqlFilter}"); + + var tbl = FragmentRequestBase.SimpleLoad(SPSUserClassBaseID, + "Id as Id" + + ", [Expression-ObjectID] as UserEOID" + + ", T(SPSCommonClassBase).State as State" + + ", LastName + ISNULL(', ' + FirstName,'') as Name" + + ", PreferredMailCulture.Locale as Locale" + + ", T(SPSCommonClassBase).State.DisplayString as StateDisp", + asqlFilter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no user entry list found with asql='{asqlFilter}'", LogLevels.Warning); + return null; + } + foreach (DataRow Entry in tbl.Rows) + { + // get the id + var UserId = getGuidFromObject(Entry["ID"]); + if (UserId == Guid.Empty) + { + LogEntry($"no id found for user entry", LogLevels.Warning); + continue; + } + var UserEOID = getGuidFromObject(Entry["UserEOID"]); + if (UserEOID == Guid.Empty) + { + LogEntry($"no expression object id found for user entry", LogLevels.Warning); + continue; + } + var Name = getStringFromObject(Entry["Name"]); + if (string.IsNullOrEmpty(Name)) + { + LogEntry($"no Name found for entry", LogLevels.Debug); + } + var Locale = getStringFromObject(Entry["Locale"]); + if (string.IsNullOrEmpty(Locale)) + { + LogEntry($"no locale found for entry", LogLevels.Debug); + } + var StateDisp = getStringFromObject(Entry["StateDisp"]); + if (string.IsNullOrEmpty(StateDisp)) + { + LogEntry($"no statedisp found for entry", LogLevels.Debug); + } + + + + var State = getIntFromObject(Entry["State"]); + LogEntry($"User found: ID={UserId}, ObjectID={UserEOID}, State={State}", LogLevels.Debug); + if (State != 2023) + LogEntry($"User is not active", LogLevels.Debug); + Persons.Add(new M42User() + { + Id = UserId, + ObjectId = UserEOID, + Name = Name, + State = State, + Locale = Locale, + LanguageId = new CultureInfo(Locale).LCID % 1024, + StateDisp = StateDisp + }); + } + return Persons; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + private List getAccountsByAsql(string asqlFilter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var result = new List(); + + + LogEntry($"ASql Filter: {asqlFilter}"); + + var tbl = FragmentRequestBase.SimpleLoad(SPSAccountClassBaseID, + "Id as Id" + + ", [Expression-ObjectID] as EOID" + + ", T(SPSCommonClassBase).State as State" + + ", T(SPSAccountClassAD).Sid as Sid" + + ", T(SPSAccountClassAD).UserPrincipalName as UserPrincipalName" + + ", Owner ", + asqlFilter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no account entry list found with asql='{asqlFilter}'", LogLevels.Warning); + return null; + } + foreach (DataRow Entry in tbl.Rows) + { + // get the id + var Id = getGuidFromObject(Entry["ID"]); + if (Id == Guid.Empty) + { + LogEntry($"no id found for entry", LogLevels.Warning); + continue; + } + var EOID = getGuidFromObject(Entry["EOID"]); + if (EOID == Guid.Empty) + { + LogEntry($"no expression object id found for entry", LogLevels.Warning); + continue; + } + var Owner = getGuidFromObject(Entry["Owner"]); + if (Owner == Guid.Empty) + { + LogEntry($"no owner found for entry", LogLevels.Warning); + } + var Sid = getStringFromObject(Entry["Sid"]); + if (string.IsNullOrEmpty(Sid)) + { + LogEntry($"no Sid found for entry", LogLevels.Debug); + } + var UserPrincipalName = getStringFromObject(Entry["UserPrincipalName"]); + if (string.IsNullOrEmpty(UserPrincipalName)) + { + LogEntry($"no UserPrincipalName found for entry", LogLevels.Debug); + } + + + var State = getIntFromObject(Entry["State"]); + LogEntry($"Account found: ID={Id}, ObjectID={EOID}, State={State}", LogLevels.Debug); + result.Add(new M42Account() + { + Id = Id, + EOID = EOID, + State = State, + UserPrincipalName = UserPrincipalName, + Sid = Sid, + Owner = Owner + }); + } + return result; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + internal HttpResponseMessage privGetLog(string download, int maxLines, HttpRequestMessage request, string filter) + { + var response = new HttpResponseMessage(); + + + + if (download == "1") + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var logger = (DefaultLogger.Manager as cLogManagerFile); + var logFileName = logger.GetLogFileName(); + MemoryStream memoryStream = new MemoryStream(); + + ZipArchive zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true); + string[] files = Directory.GetFiles(Path.GetDirectoryName(logFileName), $"*.log"); + foreach (string fileName in files) + { + using (var tmpInStream = new FileStream(logFileName, FileMode.Open, + FileAccess.Read, FileShare.ReadWrite)) + { + ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(Path.GetFileName(fileName)); + using (Stream destination = zipArchiveEntry.Open()) + { + tmpInStream.CopyTo(destination); + } + } + } + zipArchive.Dispose(); + memoryStream.Position = 0L; + HttpResponseMessage httpResponseMessage = request.CreateResponse(HttpStatusCode.OK); + httpResponseMessage.Content = new StreamContent(memoryStream); + httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") + { + FileName = Path.GetFileNameWithoutExtension(logFileName) + ".zip" + }; + httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + + return httpResponseMessage; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + + } + else + { + var logger = (DefaultLogger.Manager as cLogManagerFile); + var logFileName = logger.GetLogFileName(); + using (var inStream = new FileStream(logFileName, FileMode.Open, + FileAccess.Read, FileShare.ReadWrite)) + { + var lines = ReadLines(() => inStream, + Encoding.UTF8) + .ToList(); + var sb = new StringBuilder(); + sb.AppendLine(""); + var htmlLines = new List(); + foreach (var line in lines) + { + var lineSplit = line.Split('\t'); + if (string.IsNullOrEmpty(filter) || !string.IsNullOrEmpty(filter) && lineSplit.Length > 4 && filter.ToLowerInvariant().Split(',').Contains(lineSplit[3].ToLowerInvariant())) + htmlLines.Add(""); + } + if (maxLines > 0) + htmlLines = htmlLines.Skip(Math.Max(0, htmlLines.Count() - maxLines)).ToList(); + foreach (var line in htmlLines) + { + sb.AppendLine(line); + } + sb.AppendLine("
" + string.Join("", lineSplit) + "
"); + + response.Content = new StringContent(sb.ToString()); + response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html") + { + CharSet = Encoding.UTF8.HeaderName + }; + response.Content.Headers.Add("CodePage", Encoding.UTF8.CodePage.ToString()); + return response; + } + } + } + + internal List privGetLog2() + { + var response = new HttpResponseMessage(); + { + var logger = (DefaultLogger.Manager as cLogManagerFile); + var logFileName = logger.GetLogFileName(); + using (var inStream = new FileStream(logFileName, FileMode.Open, + FileAccess.Read, FileShare.ReadWrite)) + { + var lines = ReadLines(() => inStream, + Encoding.UTF8) + .ToList(); + + var entries = new List(); + DateTime dateValue; + for (int i = 0; i < lines.Count; i++) + { + string line = lines[i]; + try + { + var lineSplit = line.Split('\t'); + if (lineSplit.Length >= 5) + { + if (string.IsNullOrEmpty(lineSplit[0])) + { + entries.Last().Message += Environment.NewLine + lineSplit[4]; + continue; + } + DateTime.TryParseExact(lineSplit[0], "yyyy-MM-dd HH:mm:ss:fffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateValue); + entries.Add(new cM42LogEntry() + { + LineNumber = i + 1, + date = dateValue, + ProcessId = lineSplit[1], + logLvl = lineSplit[3], + Theme = lineSplit[2], + Message = lineSplit[4], + }); + } + } + catch (Exception) + { + + } + } + return entries; + } + } + } + + private int getIntFromObject(object o, int Default = 0) + { + try + { + if (o == null) + return Default; + if (o is DBNull) + return Default; + if (o is int @int) + return @int; + if (o is long int1) + return (int)int1; + if (int.TryParse(o.ToString(), out var r)) + return r; + } + catch { } + return Default; + } + + private DateTime getDateTimeFromObject(object o) + { + try + { + if (o == null) + return DateTime.MinValue; + if (o is DBNull) + return DateTime.MinValue; + if (o is DateTime @dateTime) + return @dateTime; + if (o is string dstr && DateTime.TryParse(o.ToString(), out var r)) + return r; + } + catch { } + return DateTime.MinValue; + } + + private string getStringFromObject(object o, string Default = "") + { + try + { + if (o == null) + return Default; + if (o is DBNull) + return Default; + return o.ToString(); + } + catch { } + return Default; + } + + private Guid getGuidFromObject(object o) + { + try + { + if (o == null) + return Guid.Empty; + if (o is DBNull) + return Guid.Empty; + if (o is Guid G) + return G; + if (Guid.TryParse(o.ToString(), out var r)) + return r; + } + catch { } + return Guid.Empty; + } + + internal cM42LogEntry privGetLog2(int id) + { + return privGetLog2().Find(x => x.LineNumber == id); + } + + internal async Task> getRoleMembershipById(Guid UserId) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + var RoleMemberships = new List(); + try + { + await Task.Delay(0); + var asqlFilter = string.Format("Members.Id = '{0}'", UserId.ToString("D")); + LogEntry($"ASql Filter: {asqlFilter}"); + + FragmentRequestBase fragmentRequestBase = new FragmentRequestBase(SPSSecurityClassRole, ColumnSelectOption.List, "Id as Id" + + ", Name as Name"); + fragmentRequestBase.Where = asqlFilter; + var langExt = new FragmentRequestExtensionLanguage(); + langExt.AddCultureRequest(new CultureInfo("en")); + fragmentRequestBase.AddExtension(langExt); + + using (SPSTransactionScope sPSTransactionScope = new SPSTransactionScope(SPSTransactionScopeOption.Required, new SPSTransactionOptions(IsolationLevel.ReadUncommitted))) + { + fragmentRequestBase.Load(); + sPSTransactionScope.Complete(); + } + + // if requested language is already "en" use the english table + var tbl = fragmentRequestBase.DataSet.Tables[1]; + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + tbl = fragmentRequestBase.DataSet.Tables[0]; + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no role membership found with asql='{asqlFilter}'", LogLevels.Debug); + return RoleMemberships; + } + foreach (DataRow Entry in tbl.Rows) + { + if (Entry.Table.Columns.Contains("LCID") && (int)Entry["LCID"] != 9) + continue; + // get the id + var IDColumn = Entry.Table.Columns.Contains("Owner") ? "Owner" : "ID"; + var RoleId = getGuidFromObject(Entry[IDColumn]); + if (RoleId == Guid.Empty) + { + LogEntry($"no id found for role entry", LogLevels.Warning); + continue; + } + var Name = getStringFromObject(Entry["Name"]); + if (string.IsNullOrEmpty(Name)) + { + LogEntry($"no Name found for entry", LogLevels.Debug); + } + LogEntry($"Role Membership found: ID={RoleId}, Name={Name}", LogLevels.Debug); + RoleMemberships.Add(new M42Role() { Id = RoleId, Name = Name }); + } + return RoleMemberships; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + /* + internal async Task> getRoleMembershipById(Guid UserId) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + var RoleMemberships = new List(); + try + { + await Task.Delay(0); + var asqlFilter = string.Format("Members.Id = '{0}'", UserId.ToString()); + LogEntry($"ASql Filter: {asqlFilter}"); + var tbl = FragmentRequestBase.SimpleLoad(SPSSecurityClassRole, + "Id as Id" + + ", Name as Name", + asqlFilter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no role membership found with asql='{asqlFilter}'", LogLevels.Debug); + return RoleMemberships; + } + foreach (DataRow Entry in tbl.Rows) + { + // get the id + var RoleId = getGuidFromObject(Entry["ID"]); + if (RoleId == Guid.Empty) + { + LogEntry($"no id found for role entry", LogLevels.Warning); + continue; + } + var Name = getStringFromObject(Entry["Name"]); + if (string.IsNullOrEmpty(Name)) + { + LogEntry($"no Name found for entry", LogLevels.Debug); + } + LogEntry($"Role Membership found: ID={RoleId}, Name={Name}", LogLevels.Debug); + RoleMemberships.Add(new M42Role() { Id = RoleId, Name = Name }); + } + return RoleMemberships; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + */ + internal async Task UserPermissionsInfo(string filter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + var RoleMemberships = new List(); + try + { + await Task.Delay(0); + + var retVal = new UserPermissionsInfo(); + List m42Users = getUsersByAsql(filter); + if (m42Users != null && m42Users.Count == 1) + { + retVal.User = m42Users[0]; + + + var user = F4SDM42WebApiController.defaultInstance._userProfile.GetInteractiveUserInfo(retVal.User.Id); + if (user != null) + { + retVal.User.Id = user.Id; + retVal.User.Currency = user.Currency; + retVal.User.Email = user.Email; + retVal.User.FirstName = user.FirstName; + retVal.User.LastName = user.LastName; + retVal.User.Phone = user.Phone; + retVal.User.Photo = user.Photo; + } + + retVal.Roles = await getRoleMembershipById(retVal.User.Id); + retVal.User.IsAdmin = retVal.Roles?.Where(i => i.Id == Administrators).FirstOrDefault() != null; + return retVal; + } + return null; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private enum eM42EntryBy + { + Phone = 0, + Email = 1, + Portal = 2, + CatalogOrder = 3, + EventTrigger = 4, + Feedback = 5, + VisualSupportAgent = 6, + ComplianceAlert = 7, + F4SD = 20010, + } + + public class DirectLink + { + public string Link { get; set; } + public string SubjectParameter { get; set; } + public string DescriptionParameter { get; set; } + } + + public class M42Asset + { + public Guid Id { get; set; } + public string Name { get; set; } + public string CIName { get; set; } + public string SKUType { get; set; } + public string SKUAssetGroup { get; set; } + public Guid CIId { get; set; } + public int SKUTypeId { get; internal set; } + public int SKUAssetGroupId { get; internal set; } + public string DomainName { get; internal set; } + public string Sid { get; internal set; } + } + } + + public class M42User + { + public Guid Id { get; internal set; } + public Guid ObjectId { get; internal set; } + public string Name { get; internal set; } + public string FirstName { get; internal set; } + public string LastName { get; internal set; } + public string Email { get; internal set; } + public string Photo { get; internal set; } + public string Phone { get; internal set; } + public string Currency { get; internal set; } + public string Locale { get; internal set; } + public int LanguageId { get; internal set; } + public bool IsAdmin { get; internal set; } + public int State { get; internal set; } + public string StateDisp { get; internal set; } + } + + internal class M42Account + { + public Guid Id { get; internal set; } + public Guid EOID { get; internal set; } + public Guid Owner { get; internal set; } + public int State { get; internal set; } + public string UserPrincipalName { get; internal set; } + public string Sid { get; internal set; } + } + + public class M42Role + { + public Guid Id { get; internal set; } + public string Name { get; internal set; } + + } + + public class UserPermissionsInfo + { + public M42User User { get; internal set; } + public List Roles { get; internal set; } + public UserPermissionsInfo() + { + User = new M42User(); + Roles = new List(); + } + } +} diff --git a/Legacy/F4SDM42WebApi/F4SDM42WebApiController.cs b/Legacy/F4SDM42WebApi/F4SDM42WebApiController.cs new file mode 100644 index 0000000..f28546d --- /dev/null +++ b/Legacy/F4SDM42WebApi/F4SDM42WebApiController.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.OData; +using System.Web.OData.Query; + +using Matrix42.Common; +using Matrix42.Contracts.ServiceManagement.ServiceContracts; +using Matrix42.Pandora.Contracts; +using Matrix42.Services.Description.Contracts; +using update4u.SPS.Utility.GlobalConfiguration; + +using C4IT.F4SDM; +using C4IT.FASD.Base; +using C4IT.Logging; + +using static C4IT.Logging.cLogManager; + +namespace C4IT.F4SD +{ + [RoutePrefix("api/C4ITF4SDWebApi")] + public partial class F4SDM42WebApiController : ApiController + { + public static bool IsInitialized { get; private set; } = false; + + public static F4SDM42WebApiController defaultInstance; + //private readonly IIncidentService _incidentService; + internal readonly GlobalConfigurationProvider _globalConfigurationProvider; + public readonly IJournalService _journalService; + //public readonly IObjectService _objectService; + //public readonly IFragmentService _fragmentService; + internal readonly IEntityDataService _entityDataService; + internal readonly IPandoraUserProfile _userProfile; + + + private readonly F4SDHelperService _f4stHelperService; + public string BaseUrl => $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}"; + public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi"; + public F4SDM42WebApiController( + //IObjectService objectService, + //IIncidentService incidentService, + IJournalService journalService + //IFragmentService fragmentService, + , IEntityDataService entityDataService + , IPandoraUserProfile userProfile + ) + { + defaultInstance = this; + //_objectService = objectService; + //_fragmentService = fragmentService; + //_incidentService = Guard.NullArgument(incidentService, "incidentService"); + + _entityDataService = entityDataService; + _journalService = journalService; + _globalConfigurationProvider = GlobalConfigurationProvider.Instance; + _f4stHelperService = new F4SDHelperService(); + _userProfile = userProfile; + } + + private static object initLock = new object(); + protected override void Initialize(HttpControllerContext controllerContext) + { + base.Initialize(controllerContext); + try + { + //System.Diagnostics.Debugger.Launch(); + lock (initLock) + { + if (IsInitialized || F4SDM42LogsWebApiController.IsInitialized) + return; + var Ass = Assembly.GetExecutingAssembly(); + var LM = cLogManagerFile.CreateInstance(LocalMachine: true, A: Ass); + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + cLogManager.DefaultLogger.LogAssemblyInfo(Ass); + IsInitialized = true; + LogMethodEnd(CM); + } + } + catch { }; + } + + [Route("getDirectLinkCreateTicket"), HttpGet] + public async Task getDirectLinkCreateTicket(string sid = "", string assetname = "") + { + await Task.Delay(0); + return await _f4stHelperService.getDirectLinkCreateTicket(sid, assetname); + } + + [Route("getDirectLinkF4SD"), HttpGet] + public async Task getDirectLinkF4SD(Guid EOID, string Type) + { + return await _f4stHelperService.getDirectLinkF4SD(EOID, Type); + } + + [Obsolete] + [Route("getTicketList"), HttpGet] + public async Task> getTicketList( + string sid, + int hours, + int queueoption = 0, + string queues = "" + ) + { + var decodedPairs = ParseQueues(queues); + + // Nun weiterreichen an Service + return await _f4stHelperService.getTicketListByUser( + sid, + hours, + queueoption, + decodedPairs + ); + } + + [Route("getTicketListForUser"), HttpGet] + public async Task> getTicketListForUser( + Guid userId, + int hours, + int queueoption = 0, + string queues = "" + ) + { + var decodedPairs = ParseQueues(queues); + + // Nun weiterreichen an Service + return await _f4stHelperService.getTicketListByUser( + userId, + hours, + queueoption, + decodedPairs + ); + } + + + [Route("getTicketDetails"), HttpGet] + public async Task getTicketDetails(Guid objectId) + { + var tickets = await _f4stHelperService.getTicketDetails(new List() { objectId }); + if (tickets.Count > 0) + return tickets[0]; + else + return null; + } + + [Route("getTicketHistory"), HttpGet] + public async Task> getTicketHistory(Guid objectId) + { + return await _f4stHelperService.GetJournalEntries(objectId); + } + + [Obsolete] + [Route("getTicketOverviewCounts"), HttpGet] + public async Task getTicketOverviewCounts( + string sid, + string scope = "personal", + string keys = "", + int queueoption = 0, + string queues = "" + ) + { + var parsedKeys = (keys ?? string.Empty) + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(key => key.Trim()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .ToList(); + + var decodedQueues = ParseQueues(queues); + return await _f4stHelperService.getTicketOverviewCounts(sid, scope, parsedKeys, queueoption, decodedQueues); + } + + + [Route("getTicketOverviewCountsForUser"), HttpGet] + public async Task getTicketOverviewCountsForUser( + Guid userId, + string scope = "personal", + string keys = "", + int queueoption = 0, + string queues = "" + ) + { + var parsedKeys = (keys ?? string.Empty) + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(key => key.Trim()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .ToList(); + + var decodedQueues = ParseQueues(queues); + return await _f4stHelperService.getTicketOverviewCounts(userId, scope, parsedKeys, queueoption, decodedQueues); + } + + [Obsolete] + [Route("getTicketOverviewCountsByRoles"), HttpPost] + public async Task getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request) + { + var parsedKeys = (request?.Keys ?? new List()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .Select(key => key.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var roleGuids = (request?.RoleGuids ?? new List()) + .Where(roleId => roleId != Guid.Empty) + .Distinct() + .ToList(); + + var decodedQueues = ParseQueues(request?.Queues ?? string.Empty); + return await _f4stHelperService.getTicketOverviewCountsByRoles( + request?.Sid, + roleGuids, + parsedKeys, + request?.QueueOption ?? 0, + decodedQueues + ); + } + + [Route("getTicketOverviewCountsByRolesForUser"), HttpPost] + public async Task getTicketOverviewCountsByRolesForUser([FromBody] TicketOverviewCountsByRolesForUserRequest request) + { + var parsedKeys = (request?.Keys ?? new List()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .Select(key => key.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var roleGuids = (request?.RoleGuids ?? new List()) + .Where(roleId => roleId != Guid.Empty) + .Distinct() + .ToList(); + + var decodedQueues = ParseQueues(request?.Queues ?? string.Empty); + return await _f4stHelperService.getTicketOverviewCountsByRoles( + request.userId, + roleGuids, + parsedKeys, + request?.QueueOption ?? 0, + decodedQueues + ); + } + + [Obsolete] + [Route("getTicketOverviewRelations"), HttpGet] + public async Task> getTicketOverviewRelations( + string sid, + string scope = "personal", + string key = "", + int count = 0, + int queueoption = 0, + string queues = "" + ) + { + var decodedQueues = ParseQueues(queues); + return await _f4stHelperService.getTicketOverviewRelations(sid, scope, key, count, queueoption, decodedQueues); + } + + [Route("getTicketOverviewRelationsForUser"), HttpGet] + public async Task> getTicketOverviewRelations( + Guid userId, + string scope = "personal", + string key = "", + int count = 0, + int queueoption = 0, + string queues = "" + ) + { + var decodedQueues = ParseQueues(queues); + return await _f4stHelperService.getTicketOverviewRelations(userId, scope, key, count, queueoption, decodedQueues); + } + + + /* + [Route("updateActivitySolution/{objectId}"), HttpPost] + public async Task updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml) + { + return new HttpResponseMessage + { + StatusCode = await _f4stHelperService.updateActivitySolution(objectId, SolutionHtml) ? HttpStatusCode.NoContent : HttpStatusCode.BadRequest, + }; + } + */ + [Route("getPickup/{name}"), HttpGet] + //[CacheOutput(UseETAG = true)] + public async Task getPickup(string name, [FromUri] EntityEnumerationVisibilityMode mode = EntityEnumerationVisibilityMode.None, [FromUri] Int32 group = -1) + { + await Task.Delay(0); + EntityEnumeration enumerationTemp = _entityDataService.GetEnumeration(name, mode); + var vals = enumerationTemp.Values; + + if (group > -1) + { + vals = vals.Where(row => !row.Extentions.TryGetValue("StateGroup", out var stateGroup) || (ConvertHelper.ParseInt(stateGroup, 0) == group)).ToArray(); + } + + string[] columns = new string[] { "position" }; + foreach (var item in vals) + { + item.Extentions = item.Extentions.Where(x => columns.Contains(x.Key.ToLower())).ToDictionary(x => x.Key, x => x.Value); + } + + EntityEnumeration enumeration = new EntityEnumeration + { + Name = enumerationTemp.Name, + Values = vals.ToArray() + }; + //CacheOutputAttribute.RegisterResponseEtag($"enum_{enumeration.Name}_{(int)mode}", $"{enumeration.Name}_{(int)mode}", cultureInvariant: false, userInvariant: true, val); + return Request.CreateResponse(HttpStatusCode.OK, enumeration); + } + + [Route("getMyRoleMemberships", Order = 2), HttpGet] + public async Task getMyRoleMemberships() + { + var User = _userProfile.GetInteractiveUserInfo(); + var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { User.Id }); + return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + } + + [Route("getRoleMemberships", Order = 1), HttpGet] + public async Task getRoleMemberships([FromUri] GetRoleMembershipsRequest req) + { + var filter = ""; + + if (req.Id != null && req.Id.Value != Guid.Empty) + { + filter = AsqlHelper.BuildInCondition("ID", new Guid[] { req.Id.Value }); + } + else if (!string.IsNullOrEmpty(req.Sid)) + { + filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).Sid", new string[] { req.Sid }); + } + else if (!string.IsNullOrEmpty(req.Upn)) + { + filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).UserPrincipalName", new string[] { req.Upn }); + } + + if (!string.IsNullOrEmpty(filter)) + { + return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + } + else + { + return null; + } + } + + [Route("getRoleMemberships/{userId}", Order = 1), HttpGet] + public async Task getRoleMembershipForUser(Guid userId) + { + var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { userId }); + return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + } + + [Obsolete] + public class GetRoleMembershipsRequest + { + public Guid? Id { get; set; } + public string Sid { get; set; } + public string Upn { get; set; } + public GetRoleMembershipsRequest() { } + } + + [Obsolete] + public class TicketOverviewCountsByRolesRequest + { + public string Sid { get; set; } + public List RoleGuids { get; set; } = new List(); + public List Keys { get; set; } = new List(); + public int? QueueOption { get; set; } + public string Queues { get; set; } + } + + public class TicketOverviewCountsByRolesForUserRequest + { + public Guid userId { get; set; } + public List RoleGuids { get; set; } = new List(); + public List Keys { get; set; } = new List(); + public int? QueueOption { get; set; } + public string Queues { get; set; } + } + + private static List ParseQueues(string queues) + { + return (queues ?? string.Empty) + .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(part => + { + var decodedPart = HttpUtility.UrlDecode(part)?.Trim(); + if (string.IsNullOrWhiteSpace(decodedPart)) + return null; + + var name = decodedPart; + string idStr = null; + + var separatorIndex = decodedPart.IndexOf(':'); + if (separatorIndex >= 0) + { + name = decodedPart.Substring(0, separatorIndex).Trim(); + idStr = decodedPart.Substring(separatorIndex + 1).Trim(); + } + + var queue = new cApiM42TicketQueueInfo(); + if (!string.IsNullOrWhiteSpace(name)) + queue.QueueName = name; + + if (!string.IsNullOrWhiteSpace(idStr)) + { + if (!Guid.TryParse(idStr, out var guid)) + return null; + + queue.QueueID = guid; + queue.QueueName = null; + } + + if (queue.QueueID == Guid.Empty && string.IsNullOrWhiteSpace(queue.QueueName)) + return null; + + return queue; + }) + .Where(q => q != null) + .ToList(); + } + + [Route("isAlive"), HttpGet] + public HttpResponseMessage isAlive() + { + return new HttpResponseMessage(HttpStatusCode.NoContent); + } + + [Route("loglevel"), HttpGet] + public async Task setDebugMode(string debug = "0") + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + DefaultLogger.Manager.Level = debug == "1" || debug.Equals("true", StringComparison.OrdinalIgnoreCase) ? LogLevels.Debug : LogLevels.Info; + return DefaultLogger.Manager.Level.ToString(); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + [Route("log"), HttpGet] + public HttpResponseMessage getLog(string download = "0", int count = 50, string filter = "") + { + try + { + return _f4stHelperService.privGetLog(download, count, Request, filter); + } + catch (Exception E) + { + LogException(E); + return null; + } + } + } + + [RoutePrefix("api/C4ITF4SDWebApi/Logs")] + public partial class F4SDM42LogsWebApiController : ApiController + { + private readonly F4SDHelperService _f4stHelperService; + + public static bool IsInitialized { get; private set; } = false; + + private static object initLock = new object(); + protected override void Initialize(HttpControllerContext controllerContext) + { + base.Initialize(controllerContext); + try + { + lock (initLock) + { + if (IsInitialized || F4SDM42WebApiController.IsInitialized) + return; + var Ass = Assembly.GetExecutingAssembly(); + var LM = cLogManagerFile.CreateInstance(LocalMachine: true, A: Ass); + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + cLogManager.DefaultLogger.LogAssemblyInfo(Ass); + IsInitialized = true; + LogMethodEnd(CM); + } + } + catch { }; + } + + public F4SDM42LogsWebApiController() + { + _f4stHelperService = new F4SDHelperService(); + } + + [Route(""), HttpGet] + [EnableQuery] + public IEnumerable getLog2(ODataQueryOptions queryOptions) + { + try + { + IQueryable queryable = _f4stHelperService.privGetLog2().AsQueryable(); + if (queryOptions.Filter != null) + { + queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast(); + } + return queryable; + } + catch (Exception E) + { + LogException(E); + return null; + } + } + + [Route("$count")] + [HttpGet] + public int Log2Count(ODataQueryOptions queryOptions) + { + IQueryable queryable = _f4stHelperService.privGetLog2().AsQueryable(); + if (queryOptions.Filter != null) + { + queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast(); + } + return queryable.Count(); + } + + [Route("{id}")] + [OperationType(OperationType.GetObject)] + public cM42LogEntry GetClass(int id) + { + return _f4stHelperService.privGetLog2(id); + } + } + + public class cGetPropertyBody + { + public string TableName { get; set; } + public List Columns { get; set; } = new List(); + public cGetPropertyBody() { } + + } +} diff --git a/Legacy/F4SDM42WebApi/Properties/AssemblyInfo.cs b/Legacy/F4SDM42WebApi/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8df0cac --- /dev/null +++ b/Legacy/F4SDM42WebApi/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("C4IT - F4SD - WebApi for M42")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d8cbffca-0b43-4acc-80ea-c944e7420cee")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/Legacy/F4SDM42WebApi/cM42LogEntry.cs b/Legacy/F4SDM42WebApi/cM42LogEntry.cs new file mode 100644 index 0000000..c732a3b --- /dev/null +++ b/Legacy/F4SDM42WebApi/cM42LogEntry.cs @@ -0,0 +1,21 @@ +using Matrix42.Services.Description.Contracts; +using System; + +namespace C4IT.F4SDM +{ + [DisplayName(Name = "Log Entry", Type = DisplayNameTypes.Static)] + public class cM42LogEntry + { + public string DisplayName + { + get => string.Format("Line {0}", LineNumber.ToString()); + } + public DateTime date { get; set; } + public string ProcessId { get; set; } + public string logLvl { get; set; } + public string Theme { get; set; } + public string Message { get; set; } + [Identifier] + public int LineNumber { get; internal set; } + } +} diff --git a/Legacy/M42LegacyExtension.targets b/Legacy/M42LegacyExtension.targets new file mode 100644 index 0000000..183cab1 --- /dev/null +++ b/Legacy/M42LegacyExtension.targets @@ -0,0 +1,48 @@ + + + $(MSBuildThisFileDirectory)BasePackage\Assemblies\svc\bin + $(MSBuildThisFileDirectory)PackageTemplate + $(MSBuildThisFileDirectory)..\artifacts\Legacy\$(Configuration) + C4IT - F4SD M42 ESM Integration Legacy 12.1.3-25.x + $(MSBuildThisFileDirectory)..\F4SDM42WebApi\BuildM42Package.ps1 + false + $(MSBuildThisFileDirectory)..\..\..\Common Code\Tools\signtool.exe + http://rfc3161timestamp.globalsign.com/advanced + C4IT - F4SD - WebApi for M42 Legacy + /a + <_M42SignCertificateArgs Condition="'$(M42SignCertificateFile)' != '' AND '$(M42SignCertificatePassword)' != ''">/f "$(M42SignCertificateFile)" /p "$(M42SignCertificatePassword)" + <_M42SignCertificateArgs Condition="'$(M42SignCertificateFile)' != '' AND '$(M42SignCertificatePassword)' == ''">/f "$(M42SignCertificateFile)" + <_M42SignCertificateArgs Condition="'$(_M42SignCertificateArgs)' == '' AND '$(M42SignCertificateThumbprint)' != ''">/sha1 "$(M42SignCertificateThumbprint)" + <_M42SignCertificateArgs Condition="'$(_M42SignCertificateArgs)' == ''">$(M42SignOptions) + + + + + + <_M42LegacyOutputs Include="$(TargetDir)C4ITF4SDM42WebApi.dll;$(TargetDir)C4ITF4SDM42WebApi.pdb;$(TargetDir)C4ITF4SDM42WebApiHelper.dll;$(TargetDir)C4ITF4SDM42WebApiHelper.pdb" /> + <_M42LegacyExistingOutputs Include="@(_M42LegacyOutputs)" Condition="Exists('%(Identity)')" /> + + + + + + + <_M42LegacyAssembliesToSign Include="$(M42LegacyAssembliesDir)\*.dll" /> + + + + <_M42LegacyAssembliesToSignArgs>@(_M42LegacyAssembliesToSign->'"%(FullPath)"', ' ') + + + + + + + + + + <_M42LegacyPackageVersion>@(_M42LegacyAssemblyIdentity->'%(Version)') + + + + diff --git a/Legacy/M42Libraries/12.1.3/Matrix42.Common.dll b/Legacy/M42Libraries/12.1.3/Matrix42.Common.dll new file mode 100644 index 0000000..e938b92 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Matrix42.Common.dll differ diff --git a/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.Common.dll b/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.Common.dll new file mode 100644 index 0000000..53f09f8 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.Common.dll differ diff --git a/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.Platform.dll b/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.Platform.dll new file mode 100644 index 0000000..977fa3d Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.Platform.dll differ diff --git a/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.ServiceManagement.dll b/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.ServiceManagement.dll new file mode 100644 index 0000000..e9b175a Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Matrix42.Contracts.ServiceManagement.dll differ diff --git a/Legacy/M42Libraries/12.1.3/Matrix42.Pandora.Contracts.dll b/Legacy/M42Libraries/12.1.3/Matrix42.Pandora.Contracts.dll new file mode 100644 index 0000000..366aac0 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Matrix42.Pandora.Contracts.dll differ diff --git a/Legacy/M42Libraries/12.1.3/Matrix42.Services.Description.Contracts.dll b/Legacy/M42Libraries/12.1.3/Matrix42.Services.Description.Contracts.dll new file mode 100644 index 0000000..7d38937 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Matrix42.Services.Description.Contracts.dll differ diff --git a/Legacy/M42Libraries/12.1.3/Newtonsoft.Json.dll b/Legacy/M42Libraries/12.1.3/Newtonsoft.Json.dll new file mode 100644 index 0000000..4395f61 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/Newtonsoft.Json.dll differ diff --git a/Legacy/M42Libraries/12.1.3/System.Web.Http.dll b/Legacy/M42Libraries/12.1.3/System.Web.Http.dll new file mode 100644 index 0000000..e1dbdd1 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/System.Web.Http.dll differ diff --git a/Legacy/M42Libraries/12.1.3/System.Web.OData.dll b/Legacy/M42Libraries/12.1.3/System.Web.OData.dll new file mode 100644 index 0000000..d68e666 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/System.Web.OData.dll differ diff --git a/Legacy/M42Libraries/12.1.3/update4u.SPS.DataLayer.dll b/Legacy/M42Libraries/12.1.3/update4u.SPS.DataLayer.dll new file mode 100644 index 0000000..e61466c Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/update4u.SPS.DataLayer.dll differ diff --git a/Legacy/M42Libraries/12.1.3/update4u.SPS.Utility.dll b/Legacy/M42Libraries/12.1.3/update4u.SPS.Utility.dll new file mode 100644 index 0000000..ccdee59 Binary files /dev/null and b/Legacy/M42Libraries/12.1.3/update4u.SPS.Utility.dll differ diff --git a/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/F4SD.min.js b/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/F4SD.min.js new file mode 100644 index 0000000..e758900 --- /dev/null +++ b/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/F4SD.min.js @@ -0,0 +1,83 @@ +(function (w) { + 'use strict'; + var icons = 'c4it-f4sd-icons'; + w.mx = w.mx || {}; + w.mx.workspacesConfig = w.mx.workspacesConfig || {}; + w.mx.workspacesConfig.modules = w.mx.workspacesConfig.modules || {}; + w.mx.workspacesConfig.modules.add = w.mx.workspacesConfig.modules.add || function (name, config) { + w.mx.workspacesConfig.modules[name] = config; + }; + + w.mx.workspacesConfig.modules.add('mx.C4IT.F4SD', { + name: 'mx.C4IT.F4SD', + config: ['$mdIconProvider', function ($mdIconProvider) { + $mdIconProvider.iconSet(icons, 'workspaces/C4IT_F4SD/F4SDIcons.svg'); + + w.mx = w.mx || {}; + w.mx.components = w.mx.components || {}; + w.mx.components.Icons = w.mx.components.Icons || {}; + + w.mx.components.Icons.unshift({ + 'id': icons, + 'name': 'C4IT F4SD Icons', + 'icons': [{ + 'SVG': true, + 'id': icons + ':icon-f4sd', + 'name': 'F4SD Icon', + 'keywords': ['c4it', 'custom', 'f4sd'] + }, { + 'SVG': true, + 'id': icons + ':icon-f4sd-coloured', + 'name': 'F4SD Icon (coloured)', + 'keywords': ['c4it', 'custom', 'f4sd'] + } + ] + }); + } + ] + }); +})(window); +(function (w) { + 'use strict'; + + angular.module("mx.C4IT.F4SD").controller("mx.C4IT.F4SD.Actions.callF4SD", ["mx.shell.Config", "mx.SolutionBuilderAgent.Http", "mx.shell.NotificationService", "mx.internationalization", function (shellConfig, $http, notificationService, i18n) { + var vm = this; + this.restHost = shellConfig.settings.restHosts.default; + this.messageF4SDOpened = i18n.get('c4it.f4sd.action-open-called') || 'F4SD is beeing opened...'; + this.execute = function (conf, para) { + // called from context menu grid || in dialog + var eoid = conf[0]['Sys-ObjectId'] || conf[0].ID; + + // get action parameters + try { + vm.controllerParams = new URLSearchParams(para.controllerParams); + } catch (error) { + console.log(error); + notificationService.error("Action config parameters format exception"); + return; + } + + // get f4sd url + const uri = new URL("/api/c4itf4sdwebapi/getdirectlinkf4sd/", vm.restHost); + uri.searchParams.append("type", vm.controllerParams.get("type") || para.name); + uri.searchParams.append("eoid", eoid); + $http.get(uri.pathname + uri.search).then(function (n) { + try { + const F4SDUrl = new URL(n); + if (F4SDUrl.protocol == "f4sdsend:" && F4SDUrl.pathname == "//localhost/") { + if (vm.controllerParams.has("showNotification") && vm.controllerParams.get("showNotification") == 1) + notificationService.info(vm.messageF4SDOpened); + window.location.href = n; + } + } catch (error) { + console.log(error); // => TypeError, "Failed to construct URL: Invalid URL" + notificationService.error("Service not available"); + } + }, function errorCallback(response) { + console.log(response); + notificationService.error("Service not available"); + }); + }; + } + ]); +})(window); diff --git a/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/F4SDIcons.svg b/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/F4SDIcons.svg new file mode 100644 index 0000000..0f9b980 --- /dev/null +++ b/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/F4SDIcons.svg @@ -0,0 +1,21 @@ + diff --git a/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/workspace.json b/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/workspace.json new file mode 100644 index 0000000..406350c --- /dev/null +++ b/Legacy/PackageTemplate/Files/WM/workspaces/C4IT_F4SD/workspace.json @@ -0,0 +1,7 @@ +{ + "description": "C4IT - F4SD - M42 ESM Integration", + "version": "1.0.1", + "resources": [ + "F4SD.min.js" + ] +} \ No newline at end of file diff --git a/Legacy/PackageTemplate/install.xml b/Legacy/PackageTemplate/install.xml new file mode 100644 index 0000000..3ca65ae --- /dev/null +++ b/Legacy/PackageTemplate/install.xml @@ -0,0 +1,28 @@ + + +