Commit 00d30059 by Mac Stephens

Implement URL-based JWT login (VCL-issued codes), migrate server/client to etask…

Implement URL-based JWT login (VCL-issued codes), migrate server/client to etask + web_tasks/web_tasks_url, add task-id API flow with blank-row creation, and update Web Core app to load/save grid via GetTaskItems without the login form. Still testing.
parent d3897a78
...@@ -18,3 +18,9 @@ emT3webClient/Win32/Debug/ ...@@ -18,3 +18,9 @@ emT3webClient/Win32/Debug/
*.local *.local
*.identcache *.identcache
emT3VCLDemo/__history/
emT3VCLDemo/Win64x/Debug/
kgOrdersServer/bin/static/
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{F0FD637A-3831-4B55-80F8-F15510E0463F}</ProjectGuid>
<ProjectVersion>20.3</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<MainSource>emT3VCLDemo.cpp</MainSource>
<AppType>Application</AppType>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win64x</Platform>
<ProjectName Condition="'$(ProjectName)'==''">emT3VCLDemo</ProjectName>
<TargetedPlatforms>1048579</TargetedPlatforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64x' and '$(Base)'=='true') or '$(Base_Win64x)'!=''">
<Base_Win64x>true</Base_Win64x>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
<Cfg_1>true</Cfg_1>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
<Cfg_1_Win32>true</Cfg_1_Win32>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64)'!=''">
<Cfg_1_Win64>true</Cfg_1_Win64>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64x' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64x)'!=''">
<Cfg_1_Win64x>true</Cfg_1_Win64x>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
<Cfg_2>true</Cfg_2>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
<Cfg_2_Win32>true</Cfg_2_Win32>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win64)'!=''">
<Cfg_2_Win64>true</Cfg_2_Win64>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64x' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win64x)'!=''">
<Cfg_2_Win64x>true</Cfg_2_Win64x>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_CBuilderOutput>JPHNE</DCC_CBuilderOutput>
<DynamicRTL>true</DynamicRTL>
<UsePackages>true</UsePackages>
<IntermediateOutputDir>.\$(Platform)\$(Config)</IntermediateOutputDir>
<FinalOutputDir>.\$(Platform)\$(Config)</FinalOutputDir>
<BCC_wpar>false</BCC_wpar>
<BCC_OptimizeForSpeed>true</BCC_OptimizeForSpeed>
<BCC_ExtendedErrorInfo>true</BCC_ExtendedErrorInfo>
<ILINK_TranslatedLibraryPath>$(BDSLIB)\$(PLATFORM)\release\$(LANGDIR);$(ILINK_TranslatedLibraryPath)</ILINK_TranslatedLibraryPath>
<ProjectType>CppVCLApplication</ProjectType>
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
<AllPackageLibs>rtl.lib;vcl.lib;unidac290.lib;dac290.lib;dbrtl.lib</AllPackageLibs>
<_TCHARMapping>wchar_t</_TCHARMapping>
<Multithreaded>true</Multithreaded>
<Icon_MainIcon>$(BDS)\bin\cbuilder_PROJECTICON.ico</Icon_MainIcon>
<UWP_CppLogo44>$(BDS)\bin\Artwork\Windows\UWP\cppreg_UwpDefault_44.png</UWP_CppLogo44>
<UWP_CppLogo150>$(BDS)\bin\Artwork\Windows\UWP\cppreg_UwpDefault_150.png</UWP_CppLogo150>
<SanitizedProjectName>emT3VCLDemo</SanitizedProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<PackageImports>adortl;AdvChartPkg;AdvChartPkgDE;appanalytics;bcbie;bcbsmp;bindcomp;bindcompdbx;bindcompfmx;bindcompvcl;bindcompvclsmp;bindcompvclwinx;bindengine;CamRemoteD12;CEF4Delphi;CloudService;crcontrols290;CustomIPTransport;cxADOAdaptersRS29;cxExportRS29;cxFireDACAdaptersRS29;cxGridEMFRS29;cxGridRS29;cxLibraryRS29;cxPivotGridChartRS29;cxPivotGridOLAPRS29;cxPivotGridRS29;cxSchedulerGridRS29;cxSchedulerRibbonStyleEventEditorRS29;cxSchedulerRS29;cxSchedulerTreeBrowserRS29;cxSchedulerWebServiceStorageRS29;cxTreeListdxBarPopupMenuRS29;cxTreeListRS29;cxVerticalGridRS29;dac290;dacfmx290;dacvcl290;dbexpress;dbrtl;dbxcds;DbxClientDriver;DbxCommonDriver;DBXInterBaseDriver;DBXMySQLDriver;DBXSqliteDriver;dsnap;dsnapcon;dsnapxml;dxADOEMFRS29;dxADOServerModeRS29;dxBarDBNavRS29;dxBarExtDBItemsRS29;dxBarExtItemsRS29;dxBarRS29;dxChartControlRS29;dxCloudServiceLibraryRS29;dxComnRS29;dxCoreRS29;dxdborRS29;dxdbtrRS29;dxDBXServerModeRS29;dxDockingRS29;dxEMFRS29;dxFireDACEMFRS29;dxFireDACServerModeRS29;dxFlowChartAdvancedCustomizeFormRS29;dxFlowChartDesignerRS29;dxFlowChartLayoutsRS29;dxFlowChartRS29;dxGanttControlRS29;dxGaugeControlRS29;dxGDIPlusRS29;dxHttpIndyRequestRS29;dxMapControlRS29;dxmdsRS29;dxNavBarRS29;dxOrgChartAdvancedCustomizeFormRS29;dxorgcRS29;dxPDFViewerRS29;dxPSCoreRS29;dxPScxCommonRS29;dxPScxExtCommonRS29;dxPScxGridLnkRS29;dxPScxPCProdRS29;dxPScxPivotGridLnkRS29;dxPScxSchedulerLnkRS29;dxPScxTLLnkRS29;dxPScxVGridLnkRS29;dxPSdxChartControlLnkRS29;dxPSdxDBOCLnkRS29;dxPSdxDBTVLnkRS29;dxPSdxFCLnkRS29;dxPSdxGaugeControlLnkRS29;dxPSdxLCLnkRS29;dxPSdxMapControlLnkRS29;dxPSdxOCLnkRS29;dxPSdxPDFViewerLnkRS29;dxPSdxSpreadSheetLnkRS29;dxPSLnksRS29;dxPsPrVwAdvRS29;dxPSPrVwRibbonRS29;dxPSRichEditControlLnkRS29;dxRibbonCustomizationFormRS29;dxRibbonRS29;dxRichEditControlCoreRS29;dxRichEditControlRS29;dxRichEditCoreRS29;dxRichEditDocumentModelRS29;dxServerModeRS29;dxSkinsCoreRS29;dxSpellCheckerRS29;dxSpreadSheetConditionalFormattingDialogsRS29;dxSpreadSheetCoreConditionalFormattingDialogsRS29;dxSpreadSheetCoreRS29;dxSpreadSheetReportDesignerRS29;dxSpreadSheetRS29;dxTabbedMDIRS29;dxTileControlRS29;dxtrmdRS29;dxWizardControlRS29;EMComponents;fcstudiowin;FireDAC;FireDACADSDriver;FireDACCommon;FireDACCommonDriver;FireDACCommonODBC;FireDACIBDriver;FireDACMSAccDriver;FireDACMySQLDriver;FireDACPgDriver;FireDACSqliteDriver;FlexCel_Core;FlexCel_Pdf;FlexCel_Render;FlexCel_XlsAdapter;fmx;FMX_FlexCel_Components;FMX_FlexCel_Core;fmxase;fmxdae;fmxFireDAC;fmxobj;FMXTMSFNCCorePkg;FMXTMSFNCDashboardPackPkg;FMXTMSFNCMapsPkg;FMXTMSFNCUIPackPkg;FMXTMSFNCWebSocketPkg;fqb29;fqbADO29;fqbDBX29;fqbFD29;frADODataLibrary29;frControlsLibrary29;frCoreLibrary29;frFDDataLibrary29;frGraphicsLibrary29;frLanguageArabic29;frLanguageBrazil129;frLanguageBrazil229;frLanguageBrazil29;frLanguageBulgarian29;frLanguageCatalon29;frLanguageChinese29;frLanguageCroatian29;frLanguageCzech29;frLanguageDanish29;frLanguageDutch29;frLanguageFarsi29;frLanguageFrench29;frLanguageGerman29;frLanguageGreek29;frLanguageHebrew29;frLanguageHungarian29;frLanguageIndonesian29;frLanguageItalian29;frLanguageJapanese29;frLanguageLatvian29;frLanguageLithuanian29;frLanguagePolish29;frLanguagePortuguese29;frLanguageRomanian29;frLanguageRussian29;frLanguageSerbian29;frLanguageSlovak29;frLanguageSlovene29;frLanguageSpanish29;frLanguageSwedish29;frLanguageSwiss29;frLanguageTaiwan29;frLanguageTurkish29;frLanguageUkrainian29;frLocalizationLibrary29;frx29;frxADO29;frxADOQueryBuilder29;frxDB29;frxDBX29;frxDBXQueryBuilder29;frxe29;frxFD29;frxFDQueryBuilder29;frxHTML29;frxIntIO29;frxIntIOIndy29;frxPDF29;frxQueryBuilder29;fs29;fsADO29;fsDB29;fsFD29;gtACEExpD29;gtAdvGridExpD29;gtDocEngD29;gtFRExpD29;gtHtmVwExpD29;gtPDFkitD12ProP;gtQRExpD29;gtRaveExpD29;gtRBExpD29;gtRichVwExpD29;gtScaleRichVwExpD29;gtXPressExpD29;IndyCore;IndyIPClient;IndyIPCommon;IndyIPServer;IndyProtocols;IndySystem;inet;inetdb;inetdbxpress;inetstn;ipstudiowin;ipstudiowinclient;ipstudiowinwordxp;lz290;Package1;PKIECtrl29;PKIEDB29;QRD290_W64;qrdADO290;QRDBASE290_W64;QRWRun290;RESTBackendComponents;RESTComponents;rtl;SigPlus;Skia;soapmidas;soaprtl;soapserver;TatukGIS_DK11_RX12;TatukGIS_DK11_RX12_FMX;TatukGIS_DK11_RX12_VCL;tethering;TMSCloudPkg;TMSCloudPkgDE;TMSCryptoPkg;TMSCryptoPkgDE;TMSDiagram;TMSFNCCorePkg;TMSFNCPushNotificationsPkg;TMSVCLUIPackPkg;TMSVCLUIPackPkgEx;TMSVCLUIPackPkgWiz;TMSVCLUIPackPkgXls;TMSWorkflow;unidac290;unidacfmx290;unidacvcl290;vacommpkg;vacommpkgde;vcl;VCL_FlexCel_Components;VCL_FlexCel_Core;VCL_TMSVCLGridExcelBridge;vclactnband;vclCryptoPressStreamD29;vcldb;vcldsnap;vcledge;vclFireDAC;vclie;vclimg;VCLRESTComponents;VclSmp;vclSQLMemTabled29;VCLTMSFNCCorePkg;VCLTMSFNCDashboardPackPkg;VCLTMSFNCMapsPkg;VCLTMSFNCUIPackPkg;VCLTMSFNCWebSocketPkg;vcltouch;vclwinx;vclx;VirtualTreesR;vquery290;WPViewPDF_DT;WPViewPDF_RT;xmlrtl;$(PackageImports)</PackageImports>
<IncludePath>$(BDSINCLUDE)\windows\vcl;$(IncludePath)</IncludePath>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
<BCC_PCHName_Clang>emT3VCLDemoPCH2.h</BCC_PCHName_Clang>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<PackageImports>adortl;AdvChartPkg;appanalytics;bcbie;bcbsmp;bindcomp;bindcompdbx;bindcompfmx;bindcompvcl;bindcompvclsmp;bindcompvclwinx;bindengine;CamRemoteD12;CloudService;CustomIPTransport;cxADOAdaptersRS29;cxExportRS29;cxFireDACAdaptersRS29;cxGridEMFRS29;cxGridRS29;cxLibraryRS29;cxPivotGridChartRS29;cxPivotGridOLAPRS29;cxPivotGridRS29;cxSchedulerGridRS29;cxSchedulerRibbonStyleEventEditorRS29;cxSchedulerRS29;cxSchedulerTreeBrowserRS29;cxSchedulerWebServiceStorageRS29;cxTreeListdxBarPopupMenuRS29;cxTreeListRS29;cxVerticalGridRS29;dbexpress;dbrtl;dbxcds;DbxClientDriver;DbxCommonDriver;DBXInterBaseDriver;DBXMySQLDriver;DBXSqliteDriver;dsnap;dsnapcon;dsnapxml;dxADOEMFRS29;dxADOServerModeRS29;dxBarDBNavRS29;dxBarExtDBItemsRS29;dxBarExtItemsRS29;dxBarRS29;dxChartControlRS29;dxCloudServiceLibraryRS29;dxComnRS29;dxCoreRS29;dxdborRS29;dxdbtrRS29;dxDBXServerModeRS29;dxDockingRS29;dxEMFRS29;dxFireDACEMFRS29;dxFireDACServerModeRS29;dxFlowChartAdvancedCustomizeFormRS29;dxFlowChartDesignerRS29;dxFlowChartLayoutsRS29;dxFlowChartRS29;dxGanttControlRS29;dxGaugeControlRS29;dxGDIPlusRS29;dxHttpIndyRequestRS29;dxMapControlRS29;dxmdsRS29;dxNavBarRS29;dxOrgChartAdvancedCustomizeFormRS29;dxorgcRS29;dxPDFViewerRS29;dxPSCoreRS29;dxPScxCommonRS29;dxPScxExtCommonRS29;dxPScxGridLnkRS29;dxPScxPCProdRS29;dxPScxPivotGridLnkRS29;dxPScxSchedulerLnkRS29;dxPScxTLLnkRS29;dxPScxVGridLnkRS29;dxPSdxChartControlLnkRS29;dxPSdxDBOCLnkRS29;dxPSdxDBTVLnkRS29;dxPSdxFCLnkRS29;dxPSdxGaugeControlLnkRS29;dxPSdxLCLnkRS29;dxPSdxMapControlLnkRS29;dxPSdxOCLnkRS29;dxPSdxPDFViewerLnkRS29;dxPSdxSpreadSheetLnkRS29;dxPSLnksRS29;dxPsPrVwAdvRS29;dxPSPrVwRibbonRS29;dxPSRichEditControlLnkRS29;dxRibbonCustomizationFormRS29;dxRibbonRS29;dxRichEditControlCoreRS29;dxRichEditControlRS29;dxRichEditCoreRS29;dxRichEditDocumentModelRS29;dxServerModeRS29;dxSkinsCoreRS29;dxSpellCheckerRS29;dxSpreadSheetConditionalFormattingDialogsRS29;dxSpreadSheetCoreConditionalFormattingDialogsRS29;dxSpreadSheetCoreRS29;dxSpreadSheetReportDesignerRS29;dxSpreadSheetRS29;dxTabbedMDIRS29;dxTileControlRS29;dxtrmdRS29;dxWizardControlRS29;EMComponents;fcstudiowin;FireDAC;FireDACADSDriver;FireDACCommon;FireDACCommonDriver;FireDACCommonODBC;FireDACIBDriver;FireDACMSAccDriver;FireDACMySQLDriver;FireDACPgDriver;FireDACSqliteDriver;FlexCel_Core;FlexCel_Pdf;FlexCel_Render;FlexCel_XlsAdapter;fmx;FMX_FlexCel_Components;FMX_FlexCel_Core;fmxase;fmxdae;fmxFireDAC;fmxobj;FMXTMSFNCCorePkg;FMXTMSFNCDashboardPackPkg;FMXTMSFNCMapsPkg;FMXTMSFNCUIPackPkg;FMXTMSFNCWebSocketPkg;frLanguageArabic29;frLanguageBrazil129;frLanguageBrazil229;frLanguageBrazil29;frLanguageBulgarian29;frLanguageCatalon29;frLanguageChinese29;frLanguageCroatian29;frLanguageCzech29;frLanguageDanish29;frLanguageDutch29;frLanguageFarsi29;frLanguageFrench29;frLanguageGerman29;frLanguageGreek29;frLanguageHebrew29;frLanguageHungarian29;frLanguageIndonesian29;frLanguageItalian29;frLanguageJapanese29;frLanguageLatvian29;frLanguageLithuanian29;frLanguagePolish29;frLanguagePortuguese29;frLanguageRomanian29;frLanguageRussian29;frLanguageSerbian29;frLanguageSlovak29;frLanguageSlovene29;frLanguageSpanish29;frLanguageSwedish29;frLanguageSwiss29;frLanguageTaiwan29;frLanguageTurkish29;frLanguageUkrainian29;frxADOQueryBuilder29;frxDBXQueryBuilder29;frxFDQueryBuilder29;IndyCore;IndyIPClient;IndyIPCommon;IndyIPServer;IndyProtocols;IndySystem;inet;inetdb;inetdbxpress;inetstn;ipstudiowin;ipstudiowinclient;lz290;PKIECtrl29;PKIEDB29;QRD290_W64;qrdADO290;QRDBASE290_W64;QRWRun290;RESTBackendComponents;RESTComponents;rtl;Skia;soapmidas;soaprtl;soapserver;tethering;TMSCloudPkg;TMSCryptoPkg;TMSCryptoPkgDE;TMSDiagram;TMSFNCCorePkg;TMSFNCPushNotificationsPkg;TMSVCLUIPackPkg;TMSVCLUIPackPkgEx;TMSVCLUIPackPkgWiz;TMSVCLUIPackPkgXls;TMSWorkflow;vacommpkg;vacommpkgde;vcl;VCL_FlexCel_Components;VCL_FlexCel_Core;VCL_TMSVCLGridExcelBridge;vclactnband;vcldb;vcldsnap;vcledge;vclFireDAC;vclie;vclimg;VCLRESTComponents;VclSmp;VCLTMSFNCCorePkg;VCLTMSFNCDashboardPackPkg;VCLTMSFNCMapsPkg;VCLTMSFNCUIPackPkg;VCLTMSFNCWebSocketPkg;vcltouch;vclwinx;vclx;VirtualTreesR;WPViewPDF_RT;xmlrtl;$(PackageImports)</PackageImports>
<IncludePath>$(BDSINCLUDE)\windows\vcl;$(IncludePath)</IncludePath>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
<BCC_PCHName_Clang>emT3VCLDemoPCH2.h</BCC_PCHName_Clang>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64x)'!=''">
<PackageImports>adortl;AdvChartPkg;bindcomp;bindcompdbx;bindcompfmx;bindcompvcl;bindcompvclsmp;bindcompvclwinx;bindengine;CloudService;CustomIPTransport;cxADOAdaptersRS29;cxExportRS29;cxFireDACAdaptersRS29;cxGridEMFRS29;cxGridRS29;cxLibraryRS29;cxPivotGridChartRS29;cxPivotGridOLAPRS29;cxPivotGridRS29;cxSchedulerGridRS29;cxSchedulerRibbonStyleEventEditorRS29;cxSchedulerRS29;cxSchedulerTreeBrowserRS29;cxSchedulerWebServiceStorageRS29;cxTreeListdxBarPopupMenuRS29;cxTreeListRS29;cxVerticalGridRS29;dbexpress;dbrtl;dbxcds;DbxClientDriver;DbxCommonDriver;DBXInterBaseDriver;DBXMySQLDriver;DBXSqliteDriver;dsnap;dsnapcon;dsnapxml;dxADOEMFRS29;dxADOServerModeRS29;dxBarDBNavRS29;dxBarExtDBItemsRS29;dxBarExtItemsRS29;dxBarRS29;dxChartControlRS29;dxCloudServiceLibraryRS29;dxComnRS29;dxCoreRS29;dxdborRS29;dxdbtrRS29;dxDBXServerModeRS29;dxDockingRS29;dxEMFRS29;dxFireDACEMFRS29;dxFireDACServerModeRS29;dxFlowChartAdvancedCustomizeFormRS29;dxFlowChartDesignerRS29;dxFlowChartLayoutsRS29;dxFlowChartRS29;dxGanttControlRS29;dxGaugeControlRS29;dxGDIPlusRS29;dxHttpIndyRequestRS29;dxMapControlRS29;dxmdsRS29;dxNavBarRS29;dxOrgChartAdvancedCustomizeFormRS29;dxorgcRS29;dxPDFViewerRS29;dxPSCoreRS29;dxPScxCommonRS29;dxPScxExtCommonRS29;dxPScxGridLnkRS29;dxPScxPCProdRS29;dxPScxPivotGridLnkRS29;dxPScxSchedulerLnkRS29;dxPScxTLLnkRS29;dxPScxVGridLnkRS29;dxPSdxChartControlLnkRS29;dxPSdxDBOCLnkRS29;dxPSdxDBTVLnkRS29;dxPSdxFCLnkRS29;dxPSdxGaugeControlLnkRS29;dxPSdxLCLnkRS29;dxPSdxMapControlLnkRS29;dxPSdxOCLnkRS29;dxPSdxPDFViewerLnkRS29;dxPSdxSpreadSheetLnkRS29;dxPSLnksRS29;dxPsPrVwAdvRS29;dxPSPrVwRibbonRS29;dxPSRichEditControlLnkRS29;dxRibbonCustomizationFormRS29;dxRibbonRS29;dxRichEditControlCoreRS29;dxRichEditControlRS29;dxRichEditCoreRS29;dxRichEditDocumentModelRS29;dxServerModeRS29;dxSkinsCoreRS29;dxSpellCheckerRS29;dxSpreadSheetConditionalFormattingDialogsRS29;dxSpreadSheetCoreConditionalFormattingDialogsRS29;dxSpreadSheetCoreRS29;dxSpreadSheetReportDesignerRS29;dxSpreadSheetRS29;dxTabbedMDIRS29;dxTileControlRS29;dxtrmdRS29;dxWizardControlRS29;EMComponents;fcstudiowin;FireDAC;FireDACADSDriver;FireDACCommon;FireDACCommonDriver;FireDACCommonODBC;FireDACIBDriver;FireDACMSAccDriver;FireDACMySQLDriver;FireDACPgDriver;FireDACSqliteDriver;FlexCel_Core;FlexCel_Pdf;FlexCel_Render;FlexCel_XlsAdapter;fmx;FMX_FlexCel_Components;FMX_FlexCel_Core;fmxase;fmxdae;fmxFireDAC;fmxobj;FMXTMSFNCCorePkg;FMXTMSFNCDashboardPackPkg;FMXTMSFNCMapsPkg;FMXTMSFNCUIPackPkg;FMXTMSFNCWebSocketPkg;frLanguageArabic29;frLanguageBrazil129;frLanguageBrazil229;frLanguageBrazil29;frLanguageBulgarian29;frLanguageCatalon29;frLanguageChinese29;frLanguageCroatian29;frLanguageCzech29;frLanguageDanish29;frLanguageDutch29;frLanguageFarsi29;frLanguageFrench29;frLanguageGerman29;frLanguageGreek29;frLanguageHebrew29;frLanguageHungarian29;frLanguageIndonesian29;frLanguageItalian29;frLanguageJapanese29;frLanguageLatvian29;frLanguageLithuanian29;frLanguagePolish29;frLanguagePortuguese29;frLanguageRomanian29;frLanguageRussian29;frLanguageSerbian29;frLanguageSlovak29;frLanguageSlovene29;frLanguageSpanish29;frLanguageSwedish29;frLanguageSwiss29;frLanguageTaiwan29;frLanguageTurkish29;frLanguageUkrainian29;frxADOQueryBuilder29;frxDBXQueryBuilder29;frxFDQueryBuilder29;IndyCore;IndyIPClient;IndyIPCommon;IndyIPServer;IndyProtocols;IndySystem;inet;inetdb;inetdbxpress;inetstn;ipstudiowin;ipstudiowinclient;lz290;QRD290_W64;QRDBASE290_W64;QRWRun290;RESTBackendComponents;RESTComponents;rtl;Skia;soapmidas;soaprtl;soapserver;tethering;TMSCryptoPkg;TMSFNCCorePkg;TMSFNCPushNotificationsPkg;TMSVCLUIPackPkg;TMSVCLUIPackPkgEx;TMSVCLUIPackPkgXls;vacommpkg;vacommpkgde;vcl;VCL_FlexCel_Components;VCL_FlexCel_Core;VCL_TMSVCLGridExcelBridge;vclactnband;vclCryptoPressStreamD29;vcldb;vcldsnap;vcledge;vclFireDAC;vclie;vclimg;VCLRESTComponents;VclSmp;VCLTMSFNCCorePkg;VCLTMSFNCDashboardPackPkg;VCLTMSFNCMapsPkg;VCLTMSFNCUIPackPkg;VCLTMSFNCWebSocketPkg;vcltouch;vclwinx;vclx;VirtualTreesR;xmlrtl;$(PackageImports)</PackageImports>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
<BCC_EnableBatchCompilation>true</BCC_EnableBatchCompilation>
<BCC_PCHName_Clang>emT3VCLDemoPCH2.h</BCC_PCHName_Clang>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<BCC_OptimizeForSpeed>false</BCC_OptimizeForSpeed>
<BCC_DisableOptimizations>true</BCC_DisableOptimizations>
<DCC_Optimize>false</DCC_Optimize>
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
<Defines>_DEBUG;$(Defines)</Defines>
<BCC_InlineFunctionExpansion>false</BCC_InlineFunctionExpansion>
<BCC_UseRegisterVariables>None</BCC_UseRegisterVariables>
<DCC_Define>DEBUG</DCC_Define>
<BCC_DebugLineNumbers>true</BCC_DebugLineNumbers>
<TASM_DisplaySourceLines>true</TASM_DisplaySourceLines>
<BCC_StackFrames>true</BCC_StackFrames>
<ILINK_FullDebugInfo>true</ILINK_FullDebugInfo>
<TASM_Debugging>Full</TASM_Debugging>
<BCC_SourceDebuggingOn>true</BCC_SourceDebuggingOn>
<BCC_EnableCPPExceptions>true</BCC_EnableCPPExceptions>
<BCC_DisableFramePtrElimOpt>true</BCC_DisableFramePtrElimOpt>
<BCC_DisableSpellChecking>true</BCC_DisableSpellChecking>
<CLANG_UnwindTables>true</CLANG_UnwindTables>
<ILINK_LibraryPath>$(BDSLIB)\$(PLATFORM)\debug;$(ILINK_LibraryPath)</ILINK_LibraryPath>
<ILINK_TranslatedLibraryPath>$(BDSLIB)\$(PLATFORM)\debug\$(LANGDIR);$(ILINK_TranslatedLibraryPath)</ILINK_TranslatedLibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<BCC_UseClassicCompiler>false</BCC_UseClassicCompiler>
<ILINK_LibraryPath>$(BDSLIB)\$(PLATFORM)$(CC_SUFFIX)\debug;$(ILINK_LibraryPath)</ILINK_LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64x)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<LinkPackageImports>rtl.bpi;vcl.bpi;unidac290.bpi;dac290.bpi;dbrtl.bpi</LinkPackageImports>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<Defines>NDEBUG;$(Defines)</Defines>
<TASM_Debugging>None</TASM_Debugging>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<BCC_UseClassicCompiler>false</BCC_UseClassicCompiler>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win64x)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
</PropertyGroup>
<ItemGroup>
<CppCompile Include="emT3VCLDemo.cpp">
<BuildOrder>0</BuildOrder>
</CppCompile>
<PCHCompile Include="emT3VCLDemoPCH2.h">
<BuildOrder>1</BuildOrder>
<PCH>true</PCH>
</PCHCompile>
<CppCompile Include="uMain.cpp">
<Form>fMain</Form>
<FormType>dfm</FormType>
<DependentOn>uMain.h</DependentOn>
<BuildOrder>2</BuildOrder>
</CppCompile>
<FormResources Include="uMain.dfm"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
<BuildConfiguration Include="Debug">
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>CPlusPlusBuilder.Personality.12</Borland.Personality>
<Borland.ProjectType>CppVCLApplication</Borland.ProjectType>
<BorlandProject>
<CPlusPlusBuilder.Personality>
<ProjectProperties>
<ProjectProperties Name="AutoShowDeps">False</ProjectProperties>
<ProjectProperties Name="ManagePaths">True</ProjectProperties>
<ProjectProperties Name="VerifyPackages">True</ProjectProperties>
<ProjectProperties Name="IndexFiles">False</ProjectProperties>
</ProjectProperties>
<Source>
<Source Name="MainSource">emT3VCLDemo.cpp</Source>
</Source>
</CPlusPlusBuilder.Personality>
<Deployment Version="5">
<DeployFile Condition="'$(DynamicRTL)'=='true'" LocalName="$(BDS)\Redist\osx32\libcgcrtl.dylib" Class="DependencyModule">
<Platform Name="OSX32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true'" LocalName="$(BDS)\Redist\osx32\libcgstl.dylib" Class="DependencyModule">
<Platform Name="OSX32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(UsingDelphiRTL)'=='true'" LocalName="$(BDS)\bin64\borlndmm.dll" Class="DependencyModule">
<Platform Name="Win64">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'!='true'" LocalName="$(BDS)\bin64\cc64290.dll" Class="DependencyModule">
<Platform Name="Win64">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'=='true'" LocalName="$(BDS)\bin64\cc64290mt.dll" Class="DependencyModule">
<Platform Name="Win64">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(UsingDelphiRTL)'=='true'" LocalName="$(BDS)\bin\borlndmm.dll" Class="DependencyModule">
<Platform Name="Win32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'!='true'" LocalName="$(BDS)\bin\cc32290.dll" Class="DependencyModule">
<Platform Name="Win32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'=='true'" LocalName="$(BDS)\bin\cc32290mt.dll" Class="DependencyModule">
<Platform Name="Win32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'!='true'" LocalName="$(BDS)\bin\cc32c290.dll" Class="DependencyModule">
<Platform Name="Win32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'=='true'" LocalName="$(BDS)\bin\cc32c290mt.dll" Class="DependencyModule">
<Platform Name="Win32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName=".\Win64x\Debug\emT3VCLDemo.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win64x">
<RemoteName>emT3VCLDemo.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName=".\Win64x\Debug\emT3VCLDemo.pdb" Configuration="Debug" Class="DebugSymbols">
<Platform Name="Win64x">
<RemoteName>emT3VCLDemo.pdb</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidFileProvider">
<Platform Name="Android">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiFile">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeMipsFile">
<Platform Name="Android">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDef">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDefV21">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStyles">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV21">
<Platform Name="Android">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV31">
<Platform Name="Android">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV35">
<Platform Name="Android">
<RemoteDir>res\values-v35</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v35</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconBackground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconForeground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconMonochrome">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconV33">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Colors">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_ColorsDark">
<Platform Name="Android">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon144">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon192">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon24">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage426">
<Platform Name="Android">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage470">
<Platform Name="Android">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage640">
<Platform Name="Android">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage960">
<Platform Name="Android">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Strings">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedNotificationIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplash">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashDark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31Dark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyFramework">
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyModule">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="DependencyPackage">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Name="File">
<Platform Name="Android">
<Operation>0</Operation>
</Platform>
<Platform Name="Android64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug">
<Platform Name="OSX64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXEntitlements">
<Platform Name="OSX32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXInfoPList">
<Platform Name="OSX32">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements">
<Platform Name="iOSDevice32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSInfoPList">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSLaunchScreen">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_CppLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_CppLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
</Deployment>
<Platforms>
<Platform value="Win32">True</Platform>
<Platform value="Win64">True</Platform>
<Platform value="Win64x">True</Platform>
</Platforms>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>
<Import Project="$(BDS)\Bin\CodeGear.Cpp.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Cpp.Targets')"/>
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
</Project>
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <tchar.h>
//---------------------------------------------------------------------------
USEFORM("uMain.cpp", fMain);
//---------------------------------------------------------------------------
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
try
{
Application->Initialize();
Application->MainFormOnTaskBar = true;
Application->CreateForm(__classid(TfMain), &fMain);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
//---------------------------------------------------------------------------
#include <vcl.h>
#include <Shellapi.h>
#include <System.NetEncoding.hpp>
#pragma hdrstop
#include "uMain.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "MySQLUniProvider"
#pragma link "UniProvider"
#pragma resource "*.dfm"
TfMain *fMain;
//---------------------------------------------------------------------------
__fastcall TfMain::TfMain(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TfMain::FormCreate(TObject *Sender)
{
Randomize();
LogLine("App started");
}
//---------------------------------------------------------------------------
void __fastcall TfMain::btnOpenTaskItemsClick(TObject *Sender)
{
const UnicodeString userId = edtUserId->Text.Trim();
const UnicodeString taskId = edtTaskId->Text.Trim();
const UnicodeString baseUrl = edtWebUrl->Text.Trim();
const int expSeconds = StrToIntDef(edtExpSeconds->Text.Trim(), 60);
if (userId.IsEmpty() || taskId.IsEmpty() || baseUrl.IsEmpty())
{
LogLine("Missing required input (userId, taskId, or webUrl)");
return;
}
const int codeInt = 100000 + Random(900000);
const UnicodeString urlCode = IntToStr(codeInt);
LogLine("Generated URL_CODE=" + urlCode);
try
{
if (!ucETask->Connected)
{
LogLine("Connecting to MariaDB...");
ucETask->Connect();
LogLine("Connected");
}
LogLine("Inserting row into web_tasks_url...");
uqWebTasksUrl->Close();
uqWebTasksUrl->SQL->Text =
"insert into web_tasks_url (USER_ID, TASK_ID, URL_CODE, URL_TIME, URL_TIME_EXP) "
"values (:USER_ID, :TASK_ID, :URL_CODE, NOW(), :URL_TIME_EXP)";
uqWebTasksUrl->ParamByName("USER_ID")->AsString = userId;
uqWebTasksUrl->ParamByName("TASK_ID")->AsString = taskId;
uqWebTasksUrl->ParamByName("URL_CODE")->AsString = urlCode;
uqWebTasksUrl->ParamByName("URL_TIME_EXP")->AsInteger = expSeconds;
uqWebTasksUrl->ExecSQL();
LogLine("Insert OK");
}
catch (const Exception &e)
{
LogLine("DB ERROR: " + e.Message);
return;
}
const UnicodeString launchUrl = BuildLaunchUrl(baseUrl, userId, taskId, urlCode);
if (launchUrl.IsEmpty())
{
LogLine("Launch URL build failed");
return;
}
LogLine("Launching browser:");
LogLine(launchUrl);
ShellExecute(0, L"open", launchUrl.w_str(), 0, 0, SW_SHOWNORMAL);
}
//---------------------------------------------------------------------------
void TfMain::LogLine(const String &message)
{
const String stamp = FormatDateTime("yyyy-mm-dd hh:nn:ss.zzz", Now());
memoLog->Lines->Add(stamp + " " + message);
memoLog->SelStart = memoLog->Text.Length();
}
//---------------------------------------------------------------------------
String TfMain::BuildLaunchUrl(const String &baseUrl, const String &userId, const String &taskId, const String &code)
{
String cleanBaseUrl = baseUrl.Trim();
if (cleanBaseUrl.IsEmpty())
return "";
String sep = cleanBaseUrl.Pos("?") > 0 ? "&" : "?";
String qUserId = TNetEncoding::URL->Encode(userId);
String qTaskId = TNetEncoding::URL->Encode(taskId);
String qCode = TNetEncoding::URL->Encode(code);
return cleanBaseUrl + sep + "user_id=" + qUserId + "&task_id=" + qTaskId + "&code=" + qCode;
}
object fMain: TfMain
Left = 0
Top = 0
Caption = 'fMain'
ClientHeight = 441
ClientWidth = 624
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
OnCreate = FormCreate
TextHeight = 15
object lblWCBaseUrl: TLabel
Left = 20
Top = 14
Width = 131
Height = 15
Caption = 'Web Core Client Base Url'
end
object lblUserId: TLabel
Left = 368
Top = 14
Width = 36
Height = 15
Caption = 'User Id'
end
object lblTaskId: TLabel
Left = 434
Top = 14
Width = 36
Height = 15
HelpType = htKeyword
Caption = 'Task Id'
end
object Label2: TLabel
Left = 500
Top = 14
Width = 73
Height = 15
HelpType = htKeyword
Caption = 'Exp (Seconds)'
end
object btnOpenTaskItems: TButton
Left = 20
Top = 64
Width = 109
Height = 25
Caption = 'Open Task Items'
TabOrder = 0
OnClick = btnOpenTaskItemsClick
end
object memoLog: TMemo
Left = 20
Top = 102
Width = 581
Height = 311
Lines.Strings = (
'')
TabOrder = 1
end
object edtUserId: TEdit
Left = 368
Top = 32
Width = 60
Height = 23
TabOrder = 2
end
object edtTaskId: TEdit
Left = 434
Top = 32
Width = 60
Height = 23
TabOrder = 3
end
object edtWebUrl: TEdit
Left = 20
Top = 32
Width = 337
Height = 23
TabOrder = 4
Text = 'http://127.0.0.1:8000/emT3webClient/index.html'
end
object edtExpSeconds: TEdit
Left = 500
Top = 32
Width = 79
Height = 23
NumbersOnly = True
TabOrder = 5
end
object ucETask: TUniConnection
ProviderName = 'MySQL'
Database = 'eTask'
SpecificOptions.Strings = (
'MySQL.ConnectionTimeout=15'
'MySQL.UseUnicode=False'
'MySQL.Compress=False'
'MySQL.Protocol=mpDefault'
'MySQL.Embedded=False'
'MySQL.IPVersion=ivIPv4'
'MySQL.Interactive=False'
'MySQL.AnsiQuotesMode=aqDefault'
'MySQL.HttpAuthenticationType=atBasic'
'MySQL.HttpTrustServerCertificate=False'
'MySQL.ProxyPort=0')
Username = 'root'
Server = '192.168.102.129'
LoginPrompt = False
Left = 390
Top = 342
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end
object uqWebTasksUrl: TUniQuery
Connection = ucETask
Left = 478
Top = 342
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 438
Top = 284
end
end
//---------------------------------------------------------------------------
#ifndef uMainH
#define uMainH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Uni.hpp>
#include <MemDS.hpp>
#include <DBAccess.hpp>
#include <Data.DB.hpp>
#include "MySQLUniProvider.hpp"
#include "UniProvider.hpp"
//---------------------------------------------------------------------------
class TfMain : public TForm
{
__published: // IDE-managed Components
TButton *btnOpenTaskItems;
TMemo *memoLog;
TEdit *edtUserId;
TEdit *edtTaskId;
TEdit *edtWebUrl;
TEdit *edtExpSeconds;
TLabel *lblWCBaseUrl;
TLabel *lblUserId;
TLabel *lblTaskId;
TLabel *Label2;
TUniConnection *ucETask;
TUniQuery *uqWebTasksUrl;
TMySQLUniProvider *MySQLUniProvider1;
void __fastcall FormCreate(TObject *Sender);
void __fastcall btnOpenTaskItemsClick(TObject *Sender);
private:
void LogLine(const String &message);
String BuildLaunchUrl(const String &baseUrl, const String &userId, const String &taskId, const String &code);
public: // User declarations
__fastcall TfMain(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TfMain *fMain;
//---------------------------------------------------------------------------
#endif
...@@ -7,13 +7,11 @@ uses ...@@ -7,13 +7,11 @@ uses
XData.Web.Client; XData.Web.Client;
const const
TOKEN_NAME = 'KG_ORDERS_WEB_TOKEN'; TOKEN_NAME = 'EMT3_WEB_TOKEN';
type type
TOnLoginSuccess = reference to procedure; TOnLoginSuccess = reference to procedure;
TOnLoginError = reference to procedure(AMsg: string); TOnLoginError = reference to procedure(AMsg: string);
TOnProfileSuccess = reference to procedure;
TOnProfileError = reference to procedure(AMsg: string);
TAuthService = class TAuthService = class
private private
...@@ -23,8 +21,9 @@ type ...@@ -23,8 +21,9 @@ type
public public
constructor Create; reintroduce; constructor Create; reintroduce;
destructor Destroy; override; destructor Destroy; override;
procedure Login(AUser, APassword: string; ASuccess: TOnLoginSuccess;
AError: TOnLoginError); procedure Login(const userId, taskId, urlCode: string; ASuccess: TOnLoginSuccess; AError: TOnLoginError);
procedure Logout; procedure Logout;
function GetToken: string; function GetToken: string;
function Authenticated: Boolean; function Authenticated: Boolean;
...@@ -42,7 +41,7 @@ type ...@@ -42,7 +41,7 @@ type
class function DecodePayload(AToken: string): string; class function DecodePayload(AToken: string): string;
end; end;
function AuthService: TAuthService; function AuthService: TAuthService;
implementation implementation
...@@ -55,9 +54,7 @@ var ...@@ -55,9 +54,7 @@ var
function AuthService: TAuthService; function AuthService: TAuthService;
begin begin
if not Assigned(_AuthService) then if not Assigned(_AuthService) then
begin
_AuthService := TAuthService.Create; _AuthService := TAuthService.Create;
end;
Result := _AuthService; Result := _AuthService;
end; end;
...@@ -91,8 +88,7 @@ begin ...@@ -91,8 +88,7 @@ begin
Result := window.localStorage.getItem(TOKEN_NAME); Result := window.localStorage.getItem(TOKEN_NAME);
end; end;
procedure TAuthService.Login(AUser, APassword: string; ASuccess: TOnLoginSuccess; procedure TAuthService.Login(const userId, taskId, urlCode: string; ASuccess: TOnLoginSuccess; AError: TOnLoginError);
AError: TOnLoginError);
procedure OnLoad(Response: TXDataClientResponse); procedure OnLoad(Response: TXDataClientResponse);
var var
...@@ -109,14 +105,14 @@ procedure TAuthService.Login(AUser, APassword: string; ASuccess: TOnLoginSuccess ...@@ -109,14 +105,14 @@ procedure TAuthService.Login(AUser, APassword: string; ASuccess: TOnLoginSuccess
end; end;
begin begin
if (AUser = '') or (APassword = '') then if (userId = '') or (taskId = '') or (urlCode = '') then
begin begin
AError('Please enter a username and a password'); AError('Missing URL parameters. Please reopen from emt3.');
Exit; Exit;
end; end;
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.Login', [AUser, APassword], 'IAuthService.Login', [userId, taskId, urlCode],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
...@@ -124,6 +120,10 @@ end; ...@@ -124,6 +120,10 @@ end;
procedure TAuthService.Logout; procedure TAuthService.Logout;
begin begin
DeleteToken; DeleteToken;
window.localStorage.removeItem('EMT3_USER_ID');
window.localStorage.removeItem('EMT3_TASK_ID');
window.localStorage.removeItem('EMT3_CODE');
end; end;
procedure TAuthService.SetToken(AToken: string); procedure TAuthService.SetToken(AToken: string);
...@@ -140,17 +140,9 @@ begin ...@@ -140,17 +140,9 @@ begin
ExpirationDate := TJwtHelper.TokenExpirationDate(GetToken); ExpirationDate := TJwtHelper.TokenExpirationDate(GetToken);
Result := EncodeDate( Result :=
ExpirationDate.FullYear, EncodeDate(ExpirationDate.FullYear, ExpirationDate.Month + 1, ExpirationDate.Date) +
ExpirationDate.Month + 1, EncodeTime(ExpirationDate.Hours, ExpirationDate.Minutes, ExpirationDate.Seconds, 0);
ExpirationDate.Date
) +
EncodeTime(
ExpirationDate.Hours,
ExpirationDate.Minutes,
ExpirationDate.Seconds,
0
);
end; end;
function TAuthService.TokenExpired: Boolean; function TAuthService.TokenExpired: Boolean;
...@@ -176,8 +168,7 @@ begin ...@@ -176,8 +168,7 @@ begin
Result := ''; Result := '';
asm asm
const parts = AToken.split('.'); const parts = AToken.split('.');
if (parts.length === 3) { // <- strict compare if (parts.length === 3) {
// JWTs use url-safe base64; convert before atob
Result = atob(parts[1].replace(/-/g,'+').replace(/_/g,'/')); Result = atob(parts[1].replace(/-/g,'+').replace(/_/g,'/'));
} }
end; end;
......
...@@ -17,11 +17,13 @@ type ...@@ -17,11 +17,13 @@ type
procedure AuthConnectionError(Error: TXDataWebConnectionError); procedure AuthConnectionError(Error: TXDataWebConnectionError);
private private
FUnauthorizedAccessProc: TUnauthorizedAccessProc; FUnauthorizedAccessProc: TUnauthorizedAccessProc;
FUserIdParam: string;
FTaskIdParam: string;
FCodeParam: string;
public public
const clientVersion = '0.0.1'; const clientVersion = '0.0.1';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc); procedure InitApp(SuccessProc: TSuccessProc; UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback); procedure SetClientConfig(Callback: TVersionCheckCallback);
end; end;
...@@ -52,11 +54,10 @@ begin ...@@ -52,11 +54,10 @@ begin
Args.Request.Headers.SetValue('Authorization', 'Bearer ' + AuthService.GetToken); Args.Request.Headers.SetValue('Authorization', 'Bearer ' + AuthService.GetToken);
end; end;
procedure TDMConnection.ApiConnectionResponse( procedure TDMConnection.ApiConnectionResponse(Args: TXDataWebConnectionResponse);
Args: TXDataWebConnectionResponse);
begin begin
if Args.Response.StatusCode = 401 then if (Args.Response.StatusCode = 401) and Assigned(FUnauthorizedAccessProc) then
FUnauthorizedAccessProc(Format('%d: %s',[Args.Response.StatusCode, Args.Response.ContentAsText])); FUnauthorizedAccessProc(Format('%d: %s', [Args.Response.StatusCode, Args.Response.ContentAsText]));
end; end;
procedure TDMConnection.AuthConnectionError(Error: TXDataWebConnectionError); procedure TDMConnection.AuthConnectionError(Error: TXDataWebConnectionError);
...@@ -83,13 +84,12 @@ begin ...@@ -83,13 +84,12 @@ begin
LoadConfig(@ConfigLoaded); LoadConfig(@ConfigLoaded);
end; end;
procedure TDMConnection.SetClientConfig(Callback: TVersionCheckCallback); procedure TDMConnection.SetClientConfig(Callback: TVersionCheckCallback);
begin begin
XDataWebClient1.Connection := AuthConnection; XDataWebClient1.Connection := AuthConnection;
XDataWebClient1.RawInvoke('IAuthService.VerifyVersion', [clientVersion], XDataWebClient1.RawInvoke(
'IAuthService.VerifyVersion', [clientVersion],
procedure(Response: TXDataClientResponse) procedure(Response: TXDataClientResponse)
var var
jsonResult: TJSObject; jsonResult: TJSObject;
...@@ -106,8 +106,12 @@ begin ...@@ -106,8 +106,12 @@ begin
Callback(False, error) Callback(False, error)
else else
Callback(True, ''); Callback(True, '');
end); end,
procedure(Error: TXDataClientError)
begin
Callback(False, Error.ErrorMessage);
end
);
end; end;
end. end.
...@@ -46596,7 +46596,6 @@ object FViewLogin: TFViewLogin ...@@ -46596,7 +46596,6 @@ object FViewLogin: TFViewLogin
Role = 'null' Role = 'null'
TabOrder = 2 TabOrder = 2
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnClick = btnLoginClick
end end
object pnlMessage: TWebPanel object pnlMessage: TWebPanel
Left = 240 Left = 240
...@@ -20,7 +20,6 @@ type ...@@ -20,7 +20,6 @@ type
XDataWebClient: TXDataWebClient; XDataWebClient: TXDataWebClient;
WebImageControl1: TWebImageControl; WebImageControl1: TWebImageControl;
lblClientVersion: TWebLabel; lblClientVersion: TWebLabel;
procedure btnLoginClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject); procedure btnCloseNotificationClick(Sender: TObject);
procedure WebFormShow(Sender: TObject); procedure WebFormShow(Sender: TObject);
private private
...@@ -43,26 +42,6 @@ uses ...@@ -43,26 +42,6 @@ uses
{$R *.dfm} {$R *.dfm}
procedure TFViewLogin.btnLoginClick(Sender: TObject);
procedure LoginSuccess;
begin
FLoginProc;
end;
procedure LoginError(AMsg: string);
begin
ShowNotification('Login Error: ' + AMsg);
end;
begin
AuthService.Login(
edtUsername.Text, edtPassword.Text,
@LoginSuccess,
@LoginError
);
end;
class procedure TFViewLogin.Display(LoginProc: TSuccessProc); class procedure TFViewLogin.Display(LoginProc: TSuccessProc);
begin begin
TFViewLogin.Display(LoginProc, ''); TFViewLogin.Display(LoginProc, '');
......
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
</div> </div>
<div class="modal-footer justify-content-center"> <div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary"> <button type="button" id="btn_modal_restart" class="btn btn-primary">
Back to Orders Restart
</button> </button>
</div> </div>
</div> </div>
......
...@@ -45,7 +45,6 @@ implementation ...@@ -45,7 +45,6 @@ implementation
uses uses
Auth.Service, Auth.Service,
View.Login,
View.Tasks, View.Tasks,
View.TasksHTML, View.TasksHTML,
View.TasksDataGrid, View.TasksDataGrid,
...@@ -62,11 +61,11 @@ begin ...@@ -62,11 +61,11 @@ begin
end; end;
procedure TFViewMain.WebFormCreate(Sender: TObject); procedure TFViewMain.WebFormCreate(Sender: TObject);
var
userName: string;
test: boolean;
begin begin
console.log('TFViewMain.WebFormCreate fired');
FChildForm := nil; FChildForm := nil;
console.log('About to ShowForm(TFTasksHTML), host=' + WebPanel1.ElementID);
ShowForm(TFTasksHTML); ShowForm(TFTasksHTML);
lblAppTitle.Caption := 'emT3web'; lblAppTitle.Caption := 'emT3web';
lblVersion.Caption := 'v' + DMConnection.clientVersion; lblVersion.Caption := 'v' + DMConnection.clientVersion;
...@@ -89,7 +88,7 @@ end; ...@@ -89,7 +88,7 @@ end;
procedure TFViewMain.ConfirmLogout; procedure TFViewMain.ConfirmLogout;
begin begin
ShowConfirmationModal( ShowConfirmationModal(
'Are you sure you want to log out?', 'End this session?.',
'Yes', 'Yes',
'No', 'No',
procedure(confirmed: Boolean) procedure(confirmed: Boolean)
......
...@@ -3,7 +3,7 @@ object FTasksHTML: TFTasksHTML ...@@ -3,7 +3,7 @@ object FTasksHTML: TFTasksHTML
Height = 480 Height = 480
CSSLibrary = cssBootstrap CSSLibrary = cssBootstrap
ElementFont = efCSS ElementFont = efCSS
OnCreate = WebFormCreate OnShow = WebFormShow
object btnReload: TWebButton object btnReload: TWebButton
Left = 78 Left = 78
Top = 88 Top = 88
......
...@@ -30,9 +30,10 @@ type ...@@ -30,9 +30,10 @@ type
xdwdsTaskstaskItemId: TStringField; xdwdsTaskstaskItemId: TStringField;
procedure btnAddRowClick(Sender: TObject); procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject); procedure btnReloadClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject); procedure WebFormShow(Sender: TObject);
private private
[async] procedure LoadTasks(const AProjectId: string); FTaskId: string;
[async] procedure LoadTasks(const ATaskId: string);
procedure RenderTable; procedure RenderTable;
procedure BindTableEditors; procedure BindTableEditors;
...@@ -44,7 +45,7 @@ type ...@@ -44,7 +45,7 @@ type
procedure GotoRowIndex(AIndex: Integer); procedure GotoRowIndex(AIndex: Integer);
function HtmlEncode(const s: string): string; function HtmlEncode(const s: string): string;
procedure SetProjectLabel(const AProjectId: string); procedure SetTaskLabel(const ATaskId: string);
[async] procedure SaveRow(AIndex: Integer); [async] procedure SaveRow(AIndex: Integer);
procedure EditorBlur(Event: TJSEvent); procedure EditorBlur(Event: TJSEvent);
...@@ -61,9 +62,20 @@ uses ...@@ -61,9 +62,20 @@ uses
{$R *.dfm} {$R *.dfm}
procedure TFTasksHTML.WebFormCreate(Sender: TObject);
procedure TFTasksHTML.WebFormShow(Sender: TObject);
begin begin
LoadTasks('WPR0001'); FTaskId := window.localStorage.getItem('EMT3_TASK_ID');
console.log('The task id is: ' + FTaskId);
if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing task_id. Please reopen from emt3.');
Exit;
end;
btnAddRow.Enabled := False;
LoadTasks(FTaskId);
end; end;
...@@ -191,23 +203,19 @@ end; ...@@ -191,23 +203,19 @@ end;
procedure TFTasksHTML.btnAddRowClick(Sender: TObject); procedure TFTasksHTML.btnAddRowClick(Sender: TObject);
begin begin
if not xdwdsTasks.Active then Utils.ShowErrorModal('Add row is not enabled yet.');
Exit;
xdwdsTasks.Append;
xdwdsTaskstaskId.AsString := 'NEW_TASK_ID';
xdwdsTasksreportedBy.AsString := '';
xdwdsTasksassignedTo.AsString := '';
xdwdsTasksstatus.AsString := '';
xdwdsTasks.Post;
RenderTable;
end; end;
procedure TFTasksHTML.btnReloadClick(Sender: TObject); procedure TFTasksHTML.btnReloadClick(Sender: TObject);
begin begin
LoadTasks('WPR0001'); if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing Task Id. Update url params or resend from emT3.');
Exit;
end;
LoadTasks(FTaskId);
end; end;
procedure TFTasksHTML.EnableAutoGrowTextAreas; procedure TFTasksHTML.EnableAutoGrowTextAreas;
...@@ -225,29 +233,37 @@ begin ...@@ -225,29 +233,37 @@ begin
end; end;
end; end;
procedure TFTasksHTML.SetProjectLabel(const AProjectId: string); procedure TFTasksHTML.SetTaskLabel(const ATaskId: string);
var var
el: TJSHTMLElement; el: TJSHTMLElement;
begin begin
el := TJSHTMLElement(document.getElementById('lbl_project_name')); el := TJSHTMLElement(document.getElementById('lbl_project_name'));
if Assigned(el) then if Assigned(el) then
el.innerText := 'Tasks - ' + AProjectId; el.innerText := 'Tasks - ' + ATaskId;
end; end;
[async] procedure TFTasksHTML.LoadTasks(const AProjectId: string); [async] procedure TFTasksHTML.LoadTasks(const ATaskId: string);
var var
response: TXDataClientResponse; response: TXDataClientResponse;
resultObj, taskObj: TJSObject; resultObj, taskObj: TJSObject;
tasksArray, itemsArray, flatItems: TJSArray; tasksArray, itemsArray, flatItems: TJSArray;
taskIndex, itemIndex: Integer; taskIndex, itemIndex: Integer;
begin begin
SetProjectLabel(AProjectId); SetTaskLabel(ATaskId);
Utils.ShowSpinner('spinner'); Utils.ShowSpinner('spinner');
try try
response := await(xdwcTasks.RawInvokeAsync( try
'IApiService.GetProjectTasks', [AProjectId] response := await(xdwcTasks.RawInvokeAsync(
)); 'IApiService.GetTaskItems', [ATaskId]
));
except
on E: EXDataClientRequestException do
begin
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
if not Assigned(response.Result) then if not Assigned(response.Result) then
Exit; Exit;
...@@ -275,6 +291,7 @@ begin ...@@ -275,6 +291,7 @@ begin
end; end;
end; end;
procedure TFTasksHTML.RenderTable; procedure TFTasksHTML.RenderTable;
var var
host: TJSHTMLElement; host: TJSHTMLElement;
...@@ -566,5 +583,7 @@ end; ...@@ -566,5 +583,7 @@ end;
end. end.
{ {
"AuthUrl" : "http://localhost:2004/kgOrders/auth/", "AuthUrl" : "http://localhost:2004/emsys/emt3/auth/",
"ApiUrl" : "http://localhost:2004/kgOrders/api/" "ApiUrl" : "http://localhost:2004/emsys/emt3/api/"
} }
...@@ -2,6 +2,9 @@ program emT3webClient; ...@@ -2,6 +2,9 @@ program emT3webClient;
uses uses
Vcl.Forms, Vcl.Forms,
System.SysUtils,
JS,
Web,
XData.Web.Connection, XData.Web.Connection,
WEBLib.Dialogs, WEBLib.Dialogs,
Auth.Service in 'Auth.Service.pas', Auth.Service in 'Auth.Service.pas',
...@@ -19,7 +22,57 @@ uses ...@@ -19,7 +22,57 @@ uses
{$R *.res} {$R *.res}
procedure DisplayLoginView(AMessage: string = ''); forward; procedure DisplayAccessDeniedModal(const ErrorMessage: string);
begin
asm
var dlg = document.createElement("dialog");
dlg.classList.add("shadow", "rounded", "border", "p-4");
dlg.style.maxWidth = "500px";
dlg.style.width = "90%";
dlg.style.fontFamily = "system-ui, sans-serif";
dlg.innerHTML =
"<h5 class='fw-bold mb-3 text-danger'>emT3web</h5>" +
"<p class='mb-3' style='white-space: pre-wrap;'>" + ErrorMessage + "</p>" +
"<div class='text-end'>" +
"<button id='actionBtn' class='btn btn-primary'></button></div>";
document.body.appendChild(dlg);
dlg.showModal();
var btn = document.getElementById("actionBtn");
if (
(ErrorMessage.indexOf("Version mismatch") >= 0) ||
(ErrorMessage.indexOf("old version") >= 0)
) {
btn.textContent = "Reload";
btn.addEventListener("click", function () {
location.reload(true);
});
} else {
btn.textContent = "Close";
btn.addEventListener("click", function () {
dlg.close();
dlg.remove();
});
}
end;
end;
procedure DisplayLoginView(AMessage: string = '');
begin
AuthService.Logout;
DMConnection.ApiConnection.Connected := False;
if Assigned(FViewMain) then
FViewMain.Free;
if AMessage = '' then
DisplayAccessDeniedModal('Access requires a valid emt3 link. Please reopen from emt3.')
else
DisplayAccessDeniedModal(AMessage);
end;
procedure DisplayMainView; procedure DisplayMainView;
...@@ -27,6 +80,7 @@ procedure DisplayMainView; ...@@ -27,6 +80,7 @@ procedure DisplayMainView;
begin begin
if Assigned(FViewLogin) then if Assigned(FViewLogin) then
FViewLogin.Free; FViewLogin.Free;
TFViewMain.Display(@DisplayLoginView); TFViewMain.Display(@DisplayLoginView);
end; end;
...@@ -37,25 +91,33 @@ begin ...@@ -37,25 +91,33 @@ begin
ConnectProc; ConnectProc;
end; end;
procedure DisplayLoginView(AMessage: string);
begin
AuthService.Logout;
DMConnection.ApiConnection.Connected := False;
if Assigned(FViewMain) then
FViewMain.Free;
TFViewLogin.Display(@DisplayMainView, AMessage);
end;
procedure UnauthorizedAccessProc(AMessage: string); procedure UnauthorizedAccessProc(AMessage: string);
begin begin
DisplayLoginView(AMessage); DisplayLoginView(AMessage);
end; end;
procedure SaveUrlParamsToStorage(const userId, taskId, code: string);
begin
if userId <> '' then
window.localStorage.setItem('EMT3_USER_ID', userId);
if taskId <> '' then
window.localStorage.setItem('EMT3_TASK_ID', taskId);
if code <> '' then
window.localStorage.setItem('EMT3_CODE', code);
end;
procedure StartApplication; procedure StartApplication;
var
UserIdParam: string;
TaskIdParam: string;
CodeParam: string;
begin begin
UserIdParam := Application.Parameters.Values['user_id'];
TaskIdParam := Application.Parameters.Values['task_id'];
CodeParam := Application.Parameters.Values['code'];
SaveUrlParamsToStorage(UserIdParam, TaskIdParam, CodeParam);
DMConnection.InitApp( DMConnection.InitApp(
procedure procedure
begin begin
...@@ -64,33 +126,30 @@ begin ...@@ -64,33 +126,30 @@ begin
begin begin
if Success then if Success then
begin begin
if (not AuthService.Authenticated) or AuthService.TokenExpired then if (UserIdParam <> '') and (TaskIdParam <> '') and (CodeParam <> '') then
DisplayLoginView begin
AuthService.Login(
UserIdParam, TaskIdParam, CodeParam,
procedure
begin
DisplayMainView;
end,
procedure(LoginError: string)
begin
DisplayLoginView('Invalid or expired link.' + sLineBreak + LoginError);
end
);
Exit;
end;
if AuthService.Authenticated and (not AuthService.TokenExpired) then
DisplayMainView
else else
DisplayMainView; DisplayLoginView;
end end
else else
begin begin
asm DisplayAccessDeniedModal(ErrorMessage);
var dlg = document.createElement("dialog");
dlg.classList.add("shadow", "rounded", "border", "p-4");
dlg.style.maxWidth = "500px";
dlg.style.width = "90%";
dlg.style.fontFamily = "system-ui, sans-serif";
dlg.innerHTML =
"<h5 class='fw-bold mb-3 text-danger'>kgOrders web app</h5>" +
"<p class='mb-3' style='white-space: pre-wrap;'>" + ErrorMessage + "</p>" +
"<div class='text-end'>" +
"<button id='refreshBtn' class='btn btn-primary'>Reload</button></div>";
document.body.appendChild(dlg);
dlg.showModal();
document.getElementById("refreshBtn").addEventListener("click", function () {
location.reload(true); // Hard refresh
});
end;
end; end;
end); end);
end, end,
...@@ -98,9 +157,6 @@ begin ...@@ -98,9 +157,6 @@ begin
); );
end; end;
begin begin
Application.Initialize; Application.Initialize;
Application.MainFormOnTaskbar := True; Application.MainFormOnTaskbar := True;
......
...@@ -141,27 +141,22 @@ ...@@ -141,27 +141,22 @@
<DCCReference Include="Utils.pas"/> <DCCReference Include="Utils.pas"/>
<DCCReference Include="View.Tasks.pas"> <DCCReference Include="View.Tasks.pas">
<Form>FTasks</Form> <Form>FTasks</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="View.TasksHTML.pas"> <DCCReference Include="View.TasksHTML.pas">
<Form>FTasksHTML</Form> <Form>FTasksHTML</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="View.TasksDataGrid.pas"> <DCCReference Include="View.TasksDataGrid.pas">
<Form>FTasksDataGrid</Form> <Form>FTasksDataGrid</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="View.TasksTabulator.pas"> <DCCReference Include="View.TasksTabulator.pas">
<Form>FTasksTabulator</Form> <Form>FTasksTabulator</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="View.TasksDBGrid.pas"> <DCCReference Include="View.TasksDBGrid.pas">
<Form>FTasksDBGrid</Form> <Form>FTasksDBGrid</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<None Include="index.html"/> <None Include="index.html"/>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<noscript>Your browser does not support JavaScript!</noscript> <noscript>Your browser does not support JavaScript!</noscript>
<link href="data:;base64,=" rel="icon"/> <link href="data:;base64,=" rel="icon"/>
<title>EM Systems webKGOrders App</title> <title>Em Systems - emT3 Web</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
......
...@@ -2,9 +2,9 @@ object ApiDatabase: TApiDatabase ...@@ -2,9 +2,9 @@ object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate OnCreate = DataModuleCreate
Height = 358 Height = 358
Width = 519 Width = 519
object ucEmT3: TUniConnection object ucETaskApi: TUniConnection
ProviderName = 'MySQL' ProviderName = 'MySQL'
Database = 'emt3_web_db' Database = 'eTask'
Username = 'root' Username = 'root'
Server = '192.168.102.129' Server = '192.168.102.129'
Connected = True Connected = True
...@@ -18,7 +18,7 @@ object ApiDatabase: TApiDatabase ...@@ -18,7 +18,7 @@ object ApiDatabase: TApiDatabase
Top = 66 Top = 66
end end
object uqUsers: TUniQuery object uqUsers: TUniQuery
Connection = ucEmT3 Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'SELECT USER_ID, NAME, STATUS from users ORDER BY NAME') 'SELECT USER_ID, NAME, STATUS from users ORDER BY NAME')
OnCalcFields = uqUsersCalcFields OnCalcFields = uqUsersCalcFields
...@@ -42,84 +42,10 @@ object ApiDatabase: TApiDatabase ...@@ -42,84 +42,10 @@ object ApiDatabase: TApiDatabase
Calculated = True Calculated = True
end end
end end
object uqProjectTasks: TUniQuery
Connection = ucEmT3
SQL.Strings = (
'SELECT *'
'FROM task_items'
'WHERE PROJECT_ID = :PROJECT_ID'
'ORDER BY TASK_ID, TASK_ITEM_ID;')
Active = True
Left = 308
Top = 142
ParamData = <
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end>
object uqProjectTasksTASK_ITEM_ID: TStringField
FieldName = 'TASK_ITEM_ID'
Required = True
Size = 7
end
object uqProjectTasksTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqProjectTasksPROJECT_ID: TStringField
FieldName = 'PROJECT_ID'
Required = True
Size = 7
end
object uqProjectTasksAPPLICATION: TStringField
FieldName = 'APPLICATION'
Size = 255
end
object uqProjectTasksAPP_VERSION: TStringField
FieldName = 'APP_VERSION'
Size = 50
end
object uqProjectTasksTASK_DATE: TDateField
FieldName = 'TASK_DATE'
end
object uqProjectTasksREPORTED_BY: TStringField
FieldName = 'REPORTED_BY'
Size = 50
end
object uqProjectTasksASSIGNED_TO: TStringField
FieldName = 'ASSIGNED_TO'
Size = 50
end
object uqProjectTasksSTATUS: TStringField
FieldName = 'STATUS'
Size = 100
end
object uqProjectTasksSTATUS_DATE: TDateField
FieldName = 'STATUS_DATE'
end
object uqProjectTasksFIXED_VERSION: TStringField
FieldName = 'FIXED_VERSION'
Size = 50
end
object uqProjectTasksFORM_SECTION: TStringField
FieldName = 'FORM_SECTION'
Size = 255
end
object uqProjectTasksISSUE: TStringField
FieldName = 'ISSUE'
Size = 1000
end
object uqProjectTasksNOTES: TStringField
FieldName = 'NOTES'
Size = 1000
end
end
object uqSaveTaskRow: TUniQuery object uqSaveTaskRow: TUniQuery
Connection = ucEmT3 Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'UPDATE task_items' 'UPDATE web_tasks'
'SET' 'SET'
' APPLICATION = :APPLICATION,' ' APPLICATION = :APPLICATION,'
' APP_VERSION = :APP_VERSION,' ' APP_VERSION = :APP_VERSION,'
...@@ -133,8 +59,8 @@ object ApiDatabase: TApiDatabase ...@@ -133,8 +59,8 @@ object ApiDatabase: TApiDatabase
' ISSUE = :ISSUE,' ' ISSUE = :ISSUE,'
' NOTES = :NOTES' ' NOTES = :NOTES'
'WHERE TASK_ITEM_ID = :TASK_ITEM_ID') 'WHERE TASK_ITEM_ID = :TASK_ITEM_ID')
Left = 308 Left = 306
Top = 208 Top = 198
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -197,4 +123,149 @@ object ApiDatabase: TApiDatabase ...@@ -197,4 +123,149 @@ object ApiDatabase: TApiDatabase
Value = nil Value = nil
end> end>
end end
object uqWebTasks: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select'
' TASK_ITEM_ID,'
' TASK_ID,'
' PROJECT_ID,'
' APPLICATION,'
' APP_VERSION,'
' TASK_DATE,'
' STATUS_DATE,'
' REPORTED_BY,'
' ASSIGNED_TO,'
' STATUS,'
' FIXED_VERSION,'
' FORM_SECTION,'
' ISSUE,'
' NOTES'
'from web_tasks'
'where TASK_ID = :TASK_ID'
'order by TASK_ITEM_ID')
Active = True
Left = 306
Top = 134
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqWebTasksTASK_ITEM_ID: TStringField
FieldName = 'TASK_ITEM_ID'
Required = True
Size = 7
end
object uqWebTasksTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqWebTasksPROJECT_ID: TStringField
FieldName = 'PROJECT_ID'
Required = True
Size = 7
end
object uqWebTasksAPPLICATION: TStringField
FieldName = 'APPLICATION'
Required = True
Size = 255
end
object uqWebTasksAPP_VERSION: TStringField
FieldName = 'APP_VERSION'
Required = True
Size = 50
end
object uqWebTasksTASK_DATE: TDateField
FieldName = 'TASK_DATE'
Required = True
end
object uqWebTasksSTATUS_DATE: TDateField
FieldName = 'STATUS_DATE'
Required = True
end
object uqWebTasksREPORTED_BY: TStringField
FieldName = 'REPORTED_BY'
Required = True
Size = 50
end
object uqWebTasksASSIGNED_TO: TStringField
FieldName = 'ASSIGNED_TO'
Required = True
Size = 50
end
object uqWebTasksSTATUS: TStringField
FieldName = 'STATUS'
Required = True
Size = 100
end
object uqWebTasksFIXED_VERSION: TStringField
FieldName = 'FIXED_VERSION'
Required = True
Size = 50
end
object uqWebTasksFORM_SECTION: TStringField
FieldName = 'FORM_SECTION'
Required = True
Size = 255
end
object uqWebTasksISSUE: TStringField
FieldName = 'ISSUE'
Required = True
Size = 1000
end
object uqWebTasksNOTES: TStringField
FieldName = 'NOTES'
Required = True
Size = 1000
end
end
object uqEnsureBlankRow: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'insert ignore into web_tasks ('
' TASK_ITEM_ID,'
' TASK_ID,'
' PROJECT_ID,'
' APPLICATION,'
' APP_VERSION,'
' TASK_DATE,'
' STATUS_DATE,'
' REPORTED_BY,'
' ASSIGNED_TO,'
' STATUS,'
' FIXED_VERSION,'
' FORM_SECTION,'
' ISSUE,'
' NOTES'
')'
'values ('
' :TASK_ID,'
' :TASK_ID,'
' coalesce((select PROJECT_ID from tasks where TASK_ID = :TASK_I' +
'D), '#39#39'),'
' '#39#39','
' '#39#39','
' curdate(),'
' curdate(),'
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39
')')
Left = 306
Top = 254
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
end
end end
...@@ -9,29 +9,30 @@ uses ...@@ -9,29 +9,30 @@ uses
type type
TApiDatabase = class(TDataModule) TApiDatabase = class(TDataModule)
ucEmT3: TUniConnection; ucETaskApi: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider; MySQLUniProvider1: TMySQLUniProvider;
uqUsers: TUniQuery; uqUsers: TUniQuery;
uqUsersUSER_ID: TIntegerField; uqUsersUSER_ID: TIntegerField;
uqUsersNAME: TStringField; uqUsersNAME: TStringField;
uqUsersSTATUS: TStringField; uqUsersSTATUS: TStringField;
uqUsersREPRESENTATIVE: TStringField; uqUsersREPRESENTATIVE: TStringField;
uqProjectTasks: TUniQuery;
uqProjectTasksTASK_ITEM_ID: TStringField;
uqProjectTasksTASK_ID: TStringField;
uqProjectTasksPROJECT_ID: TStringField;
uqProjectTasksAPPLICATION: TStringField;
uqProjectTasksAPP_VERSION: TStringField;
uqProjectTasksTASK_DATE: TDateField;
uqProjectTasksREPORTED_BY: TStringField;
uqProjectTasksASSIGNED_TO: TStringField;
uqProjectTasksSTATUS: TStringField;
uqProjectTasksSTATUS_DATE: TDateField;
uqProjectTasksFIXED_VERSION: TStringField;
uqProjectTasksFORM_SECTION: TStringField;
uqProjectTasksISSUE: TStringField;
uqProjectTasksNOTES: TStringField;
uqSaveTaskRow: TUniQuery; uqSaveTaskRow: TUniQuery;
uqWebTasks: TUniQuery;
uqWebTasksTASK_ITEM_ID: TStringField;
uqWebTasksTASK_ID: TStringField;
uqWebTasksPROJECT_ID: TStringField;
uqWebTasksAPPLICATION: TStringField;
uqWebTasksAPP_VERSION: TStringField;
uqWebTasksTASK_DATE: TDateField;
uqWebTasksSTATUS_DATE: TDateField;
uqWebTasksREPORTED_BY: TStringField;
uqWebTasksASSIGNED_TO: TStringField;
uqWebTasksSTATUS: TStringField;
uqWebTasksFIXED_VERSION: TStringField;
uqWebTasksFORM_SECTION: TStringField;
uqWebTasksISSUE: TStringField;
uqWebTasksNOTES: TStringField;
uqEnsureBlankRow: TUniQuery;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
...@@ -54,9 +55,9 @@ uses ...@@ -54,9 +55,9 @@ uses
procedure TApiDatabase.DataModuleCreate(Sender: TObject); procedure TApiDatabase.DataModuleCreate(Sender: TObject);
begin begin
Logger.Log( 5, 'TApiDatabase.DataModuleCreate' ); Logger.Log( 5, 'TApiDatabase.DataModuleCreate' );
LoadDatabaseSettings( ucEmT3, 'emT3webServer.ini' ); LoadDatabaseSettings( ucETaskApi, 'emT3webServer.ini' );
try try
ucEmT3.Connect; ucETaskApi.Connect;
except except
on E: Exception do on E: Exception do
begin begin
......
...@@ -10,13 +10,16 @@ object ApiServerModule: TApiServerModule ...@@ -10,13 +10,16 @@ object ApiServerModule: TApiServerModule
ModelName = 'Api' ModelName = 'Api'
EntitySetPermissions = <> EntitySetPermissions = <>
SwaggerOptions.Enabled = True SwaggerOptions.Enabled = True
SwaggerOptions.AuthMode = Jwt
SwaggerUIOptions.Enabled = True SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True SwaggerUIOptions.TryItOutEnabled = True
Left = 80 Left = 80
Top = 142 Top = 142
object XDataServerJWT: TSparkleJwtMiddleware object XDataServerJWT: TSparkleJwtMiddleware
ForbidAnonymousAccess = True
OnGetSecret = XDataServerJWTGetSecret OnGetSecret = XDataServerJWTGetSecret
OnForbidRequest = XDataServerJWTForbidRequest
end end
object XDataServerCompress: TSparkleCompressMiddleware object XDataServerCompress: TSparkleCompressMiddleware
end end
......
...@@ -25,6 +25,8 @@ type ...@@ -25,6 +25,8 @@ type
XDataServerCompress: TSparkleCompressMiddleware; XDataServerCompress: TSparkleCompressMiddleware;
XDataServerCORS: TSparkleCorsMiddleware; XDataServerCORS: TSparkleCorsMiddleware;
XDataServerLogging: TSparkleLoggingMiddleware; XDataServerLogging: TSparkleLoggingMiddleware;
procedure XDataServerJWTForbidRequest(Sender: TObject; Context:
THttpServerContext; var Forbid: Boolean);
procedure XDataServerLoggingMiddlewareCreate(Sender: TObject; procedure XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware); var Middleware: IHttpServerMiddleware);
procedure XDataServerJWTGetSecret(Sender: TObject; var Secret: string); procedure XDataServerJWTGetSecret(Sender: TObject; var Secret: string);
...@@ -86,6 +88,27 @@ begin ...@@ -86,6 +88,27 @@ begin
Logger.Log(1, Format('Api server module listening at "%s"', [XDataServer.BaseUrl])); Logger.Log(1, Format('Api server module listening at "%s"', [XDataServer.BaseUrl]));
end; end;
procedure TApiServerModule.XDataServerJWTForbidRequest(Sender: TObject;
Context: THttpServerContext; var Forbid: Boolean);
var
Path: string;
begin
Path := Context.Request.Uri.Path;
if SameText(Context.Request.Method, 'OPTIONS') then
Forbid := False;
if Path.Contains('/swaggerui') then
Forbid := False;
if Path.Contains('/openapi/swagger.json') then
Forbid := False;
if Forbid then
Logger.Log(1, '[JWT] ForbidRequest fired (token missing/invalid/expired?)');
end;
procedure TApiServerModule.XDataServerLoggingMiddlewareCreate(Sender: TObject; procedure TApiServerModule.XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware); var Middleware: IHttpServerMiddleware);
begin begin
......
...@@ -77,7 +77,7 @@ type ...@@ -77,7 +77,7 @@ type
[ServiceContract, Model(API_MODEL)] [ServiceContract, Model(API_MODEL)]
IApiService = interface(IInvokable) IApiService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}'] ['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
[HttpGet] function GetProjectTasks(projectId: string): TTasksList; function GetTaskItems(taskId: string): TTasksList;
[HttpPost] function SaveTaskRow(const Item: TTaskRowSave): Boolean; [HttpPost] function SaveTaskRow(const Item: TTaskRowSave): Boolean;
end; end;
......
...@@ -3,9 +3,14 @@ unit Api.ServiceImpl; ...@@ -3,9 +3,14 @@ unit Api.ServiceImpl;
interface interface
uses uses
XData.Server.Module, System.Generics.Collections, XData.Server.Module,
XData.Service.Common, System.Variants, System.DateUtils, XData.Service.Common,
Api.Service, Api.Database, Common.Logging, System.SysUtils; System.Variants,
System.DateUtils,
Api.Service,
Api.Database,
Common.Logging,
System.SysUtils;
type type
[ServiceImplementation] [ServiceImplementation]
...@@ -13,104 +18,133 @@ type ...@@ -13,104 +18,133 @@ type
strict private strict private
ApiDB: TApiDatabase; ApiDB: TApiDatabase;
private private
procedure EnsureBlankWebTaskRow(const taskId: string);
function SaveTaskRow(const Item: TTaskRowSave): Boolean; function SaveTaskRow(const Item: TTaskRowSave): Boolean;
public public
procedure AfterConstruction; override; procedure AfterConstruction; override;
procedure BeforeDestruction; override; procedure BeforeDestruction; override;
function GetProjectTasks(projectId: string): TTasksList; function GetTaskItems(taskId: string): TTasksList;
end; end;
implementation implementation
procedure TApiService.AfterConstruction; procedure TApiService.AfterConstruction;
begin begin
inherited; inherited;
ApiDB := TApiDatabase.Create(nil); ApiDB := TApiDatabase.Create(nil);
Logger.Log(4, 'ApiService.AfterConstruction - ApiDB created');
end; end;
procedure TApiService.BeforeDestruction; procedure TApiService.BeforeDestruction;
begin begin
Logger.Log(4, 'ApiService.BeforeDestruction - freeing ApiDB');
ApiDB.Free; ApiDB.Free;
inherited; inherited;
end; end;
function TApiService.GetTaskItems(taskId: string): TTasksList;
function TApiService.GetProjectTasks(projectId: string): TTasksList;
var var
taskMap: TDictionary<string, TTask>;
task: TTask; task: TTask;
item: TTaskItem; item: TTaskItem;
taskId: string;
begin begin
Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId]));
Result := TTasksList.Create; Result := TTasksList.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result); TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
taskMap := TDictionary<string, TTask>.Create;
try try
ApiDB.uqProjectTasks.Close; ApiDB.uqWebTasks.Close;
ApiDB.uqProjectTasks.ParamByName('PROJECT_ID').AsString := projectId; ApiDB.uqWebTasks.ParamByName('TASK_ID').AsString := taskId;
ApiDB.uqProjectTasks.Open; ApiDB.uqWebTasks.Open;
while not ApiDB.uqProjectTasks.Eof do if ApiDB.uqWebTasks.IsEmpty then
begin begin
taskId := ApiDB.uqProjectTasksTASK_ID.AsString; Logger.Log(4, Format('ApiService.GetTaskItems - no rows for TASK_ID="%s", ensuring blank row', [taskId]));
EnsureBlankWebTaskRow(taskId);
if not taskMap.TryGetValue(taskId, task) then ApiDB.uqWebTasks.Close;
begin ApiDB.uqWebTasks.ParamByName('TASK_ID').AsString := taskId;
task := TTask.Create; ApiDB.uqWebTasks.Open;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(task); end;
task.taskId := taskId; if ApiDB.uqWebTasks.IsEmpty then
task.projectId := ApiDB.uqProjectTasksPROJECT_ID.AsString; begin
Logger.Log(2, Format('ApiService.GetTaskItems - still no rows after ensure blank for TASK_ID="%s"', [taskId]));
Result.count := 0;
Exit;
end;
task := TTask.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(task);
task.taskId := taskId;
task.projectId := ApiDB.uqWebTasksPROJECT_ID.AsString;
taskMap.Add(taskId, task); Result.data.Add(task);
Result.data.Add(task);
end;
while not ApiDB.uqWebTasks.Eof do
begin
item := TTaskItem.Create; item := TTaskItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(item); TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);
item.taskItemId := ApiDB.uqProjectTasksTASK_ITEM_ID.AsString; item.taskItemId := ApiDB.uqWebTasksTASK_ITEM_ID.AsString;
item.taskId := ApiDB.uqProjectTasksTASK_ID.AsString; item.taskId := ApiDB.uqWebTasksTASK_ID.AsString;
item.projectId := ApiDB.uqProjectTasksPROJECT_ID.AsString; item.projectId := ApiDB.uqWebTasksPROJECT_ID.AsString;
item.application := ApiDB.uqProjectTasksAPPLICATION.AsString; item.application := ApiDB.uqWebTasksAPPLICATION.AsString;
item.version := ApiDB.uqProjectTasksAPP_VERSION.AsString; item.version := ApiDB.uqWebTasksAPP_VERSION.AsString;
if ApiDB.uqProjectTasksTASK_DATE.IsNull then if ApiDB.uqWebTasksTASK_DATE.IsNull then
item.taskDate := 0 item.taskDate := 0
else else
item.taskDate := ApiDB.uqProjectTasksTASK_DATE.AsDateTime; item.taskDate := ApiDB.uqWebTasksTASK_DATE.AsDateTime;
item.reportedBy := ApiDB.uqProjectTasksREPORTED_BY.AsString; item.reportedBy := ApiDB.uqWebTasksREPORTED_BY.AsString;
item.assignedTo := ApiDB.uqProjectTasksASSIGNED_TO.AsString; item.assignedTo := ApiDB.uqWebTasksASSIGNED_TO.AsString;
item.status := ApiDB.uqProjectTasksSTATUS.AsString; item.status := ApiDB.uqWebTasksSTATUS.AsString;
if ApiDB.uqProjectTasksSTATUS_DATE.IsNull then if ApiDB.uqWebTasksSTATUS_DATE.IsNull then
item.statusDate := Null item.statusDate := Null
else else
item.statusDate := ApiDB.uqProjectTasksSTATUS_DATE.AsDateTime; item.statusDate := ApiDB.uqWebTasksSTATUS_DATE.AsDateTime;
item.fixedVersion := ApiDB.uqProjectTasksFIXED_VERSION.AsString; item.fixedVersion := ApiDB.uqWebTasksFIXED_VERSION.AsString;
item.formSection := ApiDB.uqProjectTasksFORM_SECTION.AsString; item.formSection := ApiDB.uqWebTasksFORM_SECTION.AsString;
item.issue := ApiDB.uqProjectTasksISSUE.AsString; item.issue := ApiDB.uqWebTasksISSUE.AsString;
item.notes := ApiDB.uqProjectTasksNOTES.AsString; item.notes := ApiDB.uqWebTasksNOTES.AsString;
task.items.Add(item); task.items.Add(item);
ApiDB.uqProjectTasks.Next; ApiDB.uqWebTasks.Next;
end; end;
Result.count := Result.data.Count; Result.count := Result.data.Count;
finally Logger.Log(4, Format('ApiService.GetTaskItems - returned %d task(s)', [Result.count]));
taskMap.Free; except
on E: Exception do
begin
Logger.Log(2, 'ApiService.GetTaskItems - ERROR: ' + E.Message);
raise;
end;
end; end;
end; end;
procedure TApiService.EnsureBlankWebTaskRow(const taskId: string);
begin
Logger.Log(4, Format('ApiService.EnsureBlankWebTaskRow - TASK_ID="%s"', [taskId]));
try
ApiDB.uqEnsureBlankRow.Close;
ApiDB.uqEnsureBlankRow.ParamByName('TASK_ID').AsString := taskId;
ApiDB.uqEnsureBlankRow.ExecSQL;
except
on E: Exception do
begin
Logger.Log(2, 'ApiService.EnsureBlankWebTaskRow - ERROR: ' + E.Message);
raise;
end;
end;
end;
function TApiService.SaveTaskRow(const Item: TTaskRowSave): Boolean; function TApiService.SaveTaskRow(const Item: TTaskRowSave): Boolean;
...@@ -124,41 +158,52 @@ function TApiService.SaveTaskRow(const Item: TTaskRowSave): Boolean; ...@@ -124,41 +158,52 @@ function TApiService.SaveTaskRow(const Item: TTaskRowSave): Boolean;
var var
d: TDateTime; d: TDateTime;
begin begin
ApiDB.uqSaveTaskRow.Close; Logger.Log(4, Format('ApiService.SaveTaskRow - TASK_ITEM_ID="%s"', [Item.taskItemId]));
ApiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsString := Item.taskItemId; try
ApiDB.uqSaveTaskRow.Close;
ApiDB.uqSaveTaskRow.ParamByName('APPLICATION').AsString := Item.application; ApiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsString := Item.taskItemId;
ApiDB.uqSaveTaskRow.ParamByName('APP_VERSION').AsString := Item.version;
if ParseDateOrZero(Item.taskDate, d) then ApiDB.uqSaveTaskRow.ParamByName('APPLICATION').AsString := Item.application;
ApiDB.uqSaveTaskRow.ParamByName('TASK_DATE').AsDateTime := d ApiDB.uqSaveTaskRow.ParamByName('APP_VERSION').AsString := Item.version;
else
ApiDB.uqSaveTaskRow.ParamByName('TASK_DATE').Clear;
ApiDB.uqSaveTaskRow.ParamByName('REPORTED_BY').AsString := Item.reportedBy; if ParseDateOrZero(Item.taskDate, d) then
ApiDB.uqSaveTaskRow.ParamByName('ASSIGNED_TO').AsString := Item.assignedTo; ApiDB.uqSaveTaskRow.ParamByName('TASK_DATE').AsDateTime := d
else
ApiDB.uqSaveTaskRow.ParamByName('TASK_DATE').Clear;
ApiDB.uqSaveTaskRow.ParamByName('STATUS').AsString := Item.status; ApiDB.uqSaveTaskRow.ParamByName('REPORTED_BY').AsString := Item.reportedBy;
ApiDB.uqSaveTaskRow.ParamByName('ASSIGNED_TO').AsString := Item.assignedTo;
if ParseDateOrZero(Item.statusDate, d) then ApiDB.uqSaveTaskRow.ParamByName('STATUS').AsString := Item.status;
ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := d
else
ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').Clear;
ApiDB.uqSaveTaskRow.ParamByName('FIXED_VERSION').AsString := Item.fixedVersion; if ParseDateOrZero(Item.statusDate, d) then
ApiDB.uqSaveTaskRow.ParamByName('FORM_SECTION').AsString := Item.formSection; ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := d
ApiDB.uqSaveTaskRow.ParamByName('ISSUE').AsString := Item.issue; else
ApiDB.uqSaveTaskRow.ParamByName('NOTES').AsString := Item.notes; ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := Date;
ApiDB.uqSaveTaskRow.ExecSQL; ApiDB.uqSaveTaskRow.ParamByName('FIXED_VERSION').AsString := Item.fixedVersion;
ApiDB.uqSaveTaskRow.ParamByName('FORM_SECTION').AsString := Item.formSection;
ApiDB.uqSaveTaskRow.ParamByName('ISSUE').AsString := Item.issue;
ApiDB.uqSaveTaskRow.ParamByName('NOTES').AsString := Item.notes;
Result := True; ApiDB.uqSaveTaskRow.ExecSQL;
end;
Result := True;
Logger.Log(4, 'ApiService.SaveTaskRow - OK');
except
on E: Exception do
begin
Logger.Log(2, 'ApiService.SaveTaskRow - ERROR: ' + E.Message);
raise;
end;
end;
end;
initialization initialization
RegisterServiceType(TypeInfo(IApiService)); RegisterServiceType(TypeInfo(IApiService));
RegisterServiceType(TApiService); RegisterServiceType(TApiService);
end. end.
...@@ -3,31 +3,116 @@ object AuthDatabase: TAuthDatabase ...@@ -3,31 +3,116 @@ object AuthDatabase: TAuthDatabase
OnDestroy = DataModuleDestroy OnDestroy = DataModuleDestroy
Height = 249 Height = 249
Width = 433 Width = 433
object uq: TUniQuery object uqWebTasksUrl: TUniQuery
Connection = ucKG Connection = ucETaskAuth
SQL.Strings = ( SQL.Strings = (
'select * from users') 'select'
' u.USER_ID,'
' u.USER_NAME,'
' u.NAME,'
' u.STATUS,'
' u.EMAIL,'
' u.ACCESS_LEVEL,'
' u.TASK_RIGHTS,'
' u.PERSPECTIVE_ID,'
' u.LAST_NAME,'
' u.FIRST_NAME,'
' w.URL_TIME,'
' w.URL_TIME_EXP'
'from web_tasks_url w'
'join users u on u.USER_ID = w.USER_ID'
'where w.USER_ID = :USER_ID'
' and w.TASK_ID = :TASK_ID'
' and w.URL_CODE = :URL_CODE'
' and TIMESTAMPDIFF(SECOND, w.URL_TIME, NOW()) between 0 and w.U' +
'RL_TIME_EXP'
'order by w.URL_TIME desc'
'limit 1')
FetchRows = 100 FetchRows = 100
Active = True
Left = 162 Left = 162
Top = 45 Top = 45
ParamData = <
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'URL_CODE'
Value = nil
end>
object uqWebTasksUrlUSER_ID: TStringField
FieldName = 'USER_ID'
Required = True
Size = 7
end
object uqWebTasksUrlUSER_NAME: TStringField
FieldName = 'USER_NAME'
Required = True
Size = 12
end
object uqWebTasksUrlNAME: TStringField
FieldName = 'NAME'
Size = 40
end
object uqWebTasksUrlSTATUS: TStringField
FieldName = 'STATUS'
Size = 7
end
object uqWebTasksUrlEMAIL: TStringField
FieldName = 'EMAIL'
Size = 50
end
object uqWebTasksUrlACCESS_LEVEL: TIntegerField
FieldName = 'ACCESS_LEVEL'
end
object uqWebTasksUrlTASK_RIGHTS: TIntegerField
FieldName = 'TASK_RIGHTS'
end
object uqWebTasksUrlPERSPECTIVE_ID: TStringField
FieldName = 'PERSPECTIVE_ID'
Size = 45
end
object uqWebTasksUrlLAST_NAME: TStringField
FieldName = 'LAST_NAME'
Size = 35
end
object uqWebTasksUrlFIRST_NAME: TStringField
FieldName = 'FIRST_NAME'
Size = 25
end
object uqWebTasksUrlURL_TIME: TDateTimeField
FieldName = 'URL_TIME'
ReadOnly = True
Required = True
end
object uqWebTasksUrlURL_TIME_EXP: TIntegerField
FieldName = 'URL_TIME_EXP'
ReadOnly = True
Required = True
end
end end
object uqMisc: TUniQuery object ucETaskAuth: TUniConnection
FetchRows = 100
Left = 249
Top = 45
end
object ucKG: TUniConnection
ProviderName = 'MySQL' ProviderName = 'MySQL'
Database = 'emt3_web_db' Database = 'eTask'
Username = 'root' Username = 'root'
Server = '192.168.102.129' Server = '192.168.102.129'
Connected = True
LoginPrompt = False LoginPrompt = False
Left = 67 Left = 67
Top = 131 Top = 131
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF' EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end end
object MySQLUniProvider1: TMySQLUniProvider object MySQLUniProvider1: TMySQLUniProvider
Left = 230 Left = 194
Top = 140 Top = 132
end end
end end
...@@ -10,10 +10,21 @@ uses ...@@ -10,10 +10,21 @@ uses
type type
TAuthDatabase = class(TDataModule) TAuthDatabase = class(TDataModule)
uq: TUniQuery; uqWebTasksUrl: TUniQuery;
uqMisc: TUniQuery; ucETaskAuth: TUniConnection;
ucKG: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider; MySQLUniProvider1: TMySQLUniProvider;
uqWebTasksUrlUSER_ID: TStringField;
uqWebTasksUrlUSER_NAME: TStringField;
uqWebTasksUrlNAME: TStringField;
uqWebTasksUrlSTATUS: TStringField;
uqWebTasksUrlEMAIL: TStringField;
uqWebTasksUrlACCESS_LEVEL: TIntegerField;
uqWebTasksUrlTASK_RIGHTS: TIntegerField;
uqWebTasksUrlPERSPECTIVE_ID: TStringField;
uqWebTasksUrlLAST_NAME: TStringField;
uqWebTasksUrlFIRST_NAME: TStringField;
uqWebTasksUrlURL_TIME: TDateTimeField;
uqWebTasksUrlURL_TIME_EXP: TIntegerField;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject); procedure DataModuleDestroy(Sender: TObject);
private private
...@@ -40,9 +51,9 @@ uses ...@@ -40,9 +51,9 @@ uses
procedure TAuthDatabase.DataModuleCreate(Sender: TObject); procedure TAuthDatabase.DataModuleCreate(Sender: TObject);
begin begin
Logger.Log( 5, 'TAuthDatabase.DataModuleCreate' ); Logger.Log( 5, 'TAuthDatabase.DataModuleCreate' );
LoadDatabaseSettings( ucKG, 'kgOrdersServer.ini' ); LoadDatabaseSettings( ucETaskAuth, 'emT3WebServer.ini' );
try try
ucKG.Connect; ucETaskAuth.Connect;
except except
on E: Exception do on E: Exception do
begin begin
...@@ -53,7 +64,7 @@ end; ...@@ -53,7 +64,7 @@ end;
procedure TAuthDatabase.DataModuleDestroy(Sender: TObject); procedure TAuthDatabase.DataModuleDestroy(Sender: TObject);
begin begin
ucKG.Connected := false; ucETaskAuth.Connected := false;
end; end;
......
...@@ -18,7 +18,7 @@ type ...@@ -18,7 +18,7 @@ type
[ServiceContract, Model(AUTH_MODEL)] [ServiceContract, Model(AUTH_MODEL)]
IAuthService = interface(IInvokable) IAuthService = interface(IInvokable)
['{9CFD59B2-A832-4F82-82BB-9A25FC93F305}'] ['{9CFD59B2-A832-4F82-82BB-9A25FC93F305}']
function Login(const user, password: string): string; function Login(const userId, taskId, urlCode: string): string;
function VerifyVersion(ClientVersion: string): TJSONObject; function VerifyVersion(ClientVersion: string): TJSONObject;
end; end;
......
...@@ -14,22 +14,25 @@ type ...@@ -14,22 +14,25 @@ type
TAuthService = class(TInterfacedObject, IAuthService) TAuthService = class(TInterfacedObject, IAuthService)
strict private strict private
authDB: TAuthDatabase; authDB: TAuthDatabase;
function GetQuery: TUniQuery;
private userId: string;
userName: string; userName: string;
userFullName: string; userFullName: string;
userId: string;
userPerspectiveID: string;
userQBID: string;
userAccessType: string;
userEmail: string;
userStatus: string; userStatus: string;
qbEnabled: boolean; userEmail: string;
userAccessLevel: string;
userTaskRights: string;
userPerspectiveId: string;
userFirstName: string;
userLastName: string;
procedure AfterConstruction; override; procedure AfterConstruction; override;
procedure BeforeDestruction; override; procedure BeforeDestruction; override;
function CheckUser(const user, password: string): Integer;
function CheckUrlLogin(const userId, taskId, urlCode: string): Integer;
procedure LoadUserFromUrlLoginQuery;
public public
function Login(const user, password: string): string; function Login(const userId, taskId, urlCode: string): string;
function VerifyVersion(ClientVersion: string): TJSONObject; function VerifyVersion(ClientVersion: string): TJSONObject;
end; end;
...@@ -38,16 +41,12 @@ implementation ...@@ -38,16 +41,12 @@ implementation
uses uses
System.SysUtils, System.SysUtils,
System.DateUtils, System.DateUtils,
System.Generics.Collections,
Bcl.JOSE.Core.Builder, Bcl.JOSE.Core.Builder,
Bcl.JOSE.Core.JWT, Bcl.JOSE.Core.JWT,
Aurelius.Global.Utils, Aurelius.Global.Utils,
XData.Sys.Exceptions, XData.Sys.Exceptions,
Common.Logging, Common.Logging,
Common.Config, Common.Config;
uLibrary;
{ TLoginService }
procedure TAuthService.AfterConstruction; procedure TAuthService.AfterConstruction;
begin begin
...@@ -58,7 +57,7 @@ begin ...@@ -58,7 +57,7 @@ begin
on E: Exception do on E: Exception do
begin begin
Logger.Log(1, 'Error when creating the Auth database: ' + E.Message); Logger.Log(1, 'Error when creating the Auth database: ' + E.Message);
raise EXDataHttpException.Create(500, 'Unable to create Auth database: A KGOrders Server Error has occured!'); raise EXDataHttpException.Create(500, 'Unable to create Auth database: A Server Error has occured!');
end; end;
end; end;
end; end;
...@@ -69,11 +68,6 @@ begin ...@@ -69,11 +68,6 @@ begin
inherited; inherited;
end; end;
function TAuthService.GetQuery: TUniQuery;
begin
Result := authDB.uq;
end;
function TAuthService.VerifyVersion(ClientVersion: string): TJSONObject; function TAuthService.VerifyVersion(ClientVersion: string): TJSONObject;
var var
iniFile: TIniFile; iniFile: TIniFile;
...@@ -86,14 +80,14 @@ begin ...@@ -86,14 +80,14 @@ begin
try try
webClientVersion := iniFile.ReadString('Settings', 'webClientVersion', ''); webClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
Result.AddPair('webClientVersion', webClientVersion); Result.AddPair('webClientVersion', webClientVersion);
qbEnabled := iniFile.ReadBool('Quickbooks', 'Enabled', false);
if webClientVersion = '' then if webClientVersion = '' then
begin begin
Result.AddPair('error', 'webClientVersion is not configured.'); Result.AddPair('error', 'webClientVersion is not configured.');
Exit; Exit;
end; end;
if clientVersion <> webClientVersion then
if ClientVersion <> webClientVersion then
begin begin
Result.AddPair('error', Result.AddPair('error',
'Your browser is running an old version of the app.' + sLineBreak + 'Your browser is running an old version of the app.' + sLineBreak +
...@@ -104,111 +98,103 @@ begin ...@@ -104,111 +98,103 @@ begin
end; end;
end; end;
function TAuthService.CheckUrlLogin(const userId, taskId, urlCode: string): Integer;
begin
Result := 0;
authDB.uqWebTasksUrl.Close;
authDB.uqWebTasksUrl.ParamByName('USER_ID').AsString := userId;
authDB.uqWebTasksUrl.ParamByName('TASK_ID').AsString := taskId;
authDB.uqWebTasksUrl.ParamByName('URL_CODE').AsString := urlCode;
authDB.uqWebTasksUrl.Open;
if authDB.uqWebTasksUrl.IsEmpty then
Exit;
if authDB.uqWebTasksUrl.FieldByName('STATUS').AsString <> 'ACTIVE' then
begin
Result := 2;
Exit;
end;
LoadUserFromUrlLoginQuery;
Result := 3;
end;
procedure TAuthService.LoadUserFromUrlLoginQuery;
var
nameValue: string;
begin
Self.userId := authDB.uqWebTasksUrl.FieldByName('USER_ID').AsString;
userName := authDB.uqWebTasksUrl.FieldByName('USER_NAME').AsString;
userStatus := authDB.uqWebTasksUrl.FieldByName('STATUS').AsString;
userEmail := authDB.uqWebTasksUrl.FieldByName('EMAIL').AsString;
userAccessLevel := authDB.uqWebTasksUrl.FieldByName('ACCESS_LEVEL').AsString;
userTaskRights := authDB.uqWebTasksUrl.FieldByName('TASK_RIGHTS').AsString;
userPerspectiveId := authDB.uqWebTasksUrl.FieldByName('PERSPECTIVE_ID').AsString;
userLastName := authDB.uqWebTasksUrl.FieldByName('LAST_NAME').AsString;
userFirstName := authDB.uqWebTasksUrl.FieldByName('FIRST_NAME').AsString;
nameValue := Trim(authDB.uqWebTasksUrl.FieldByName('NAME').AsString);
if nameValue <> '' then
userFullName := nameValue
else
userFullName := Trim(userFirstName + ' ' + userLastName);
end;
function TAuthService.Login(const user, password: string): string; function TAuthService.Login(const userId, taskId, urlCode: string): string;
var var
userState: Integer; userState: Integer;
iniFile: TIniFile; jwt: TJWT;
JWT: TJWT;
begin begin
Logger.Log(3, Format( 'AuthService.Login - User: "%s"', [User])); Logger.Log(3, Format('AuthService.Login - UserID: "%s", TaskID: "%s"', [userId, taskId]));
userState := CheckUser( user, password );
try try
userState := CheckUser(user, password); userState := CheckUrlLogin(userId, taskId, urlCode);
except except
on E: Exception do on E: Exception do
begin begin
Logger.Log(1, 'Login failed due to database error: ' + E.Message); Logger.Log(1, 'URL Login failed due to database error: ' + E.Message);
raise EXDataHttpException.Create(500, 'Login failed: Unable to connect to the database.'); raise EXDataHttpException.Create(500, 'Login failed: Unable to connect to the database.');
end; end;
end; end;
if userState = 0 then if userState = 0 then
begin begin
raise EXDataHttpUnauthorized.Create('Invalid username or password'); Logger.Log(2, 'Login Error: Invalid code or expired link');
logger.Log(2, 'Login Error: Invalid username or password'); raise EXDataHttpUnauthorized.Create('Invalid code or expired link');
end
else if userState = 1 then
begin
raise EXDataHttpUnauthorized.Create('User does not exist!');
logger.Log(2, 'Login Error: User does not exist!');
end
else if userState = 2 then
begin
raise EXDataHttpUnauthorized.Create('User not active!');
logger.Log(2, 'Login Error: User not active!');
end; end;
if userState = 2 then
iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); begin
try Logger.Log(2, 'Login Error: User not active!');
qbEnabled := iniFile.ReadBool('Quickbooks', 'Enabled', false); raise EXDataHttpUnauthorized.Create('User not active!');
finally
iniFile.Free;
end; end;
JWT := TJWT.Create; jwt := TJWT.Create;
try try
JWT.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36)); jwt.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
JWT.Claims.IssuedAt := Now; jwt.Claims.IssuedAt := Now;
JWT.Claims.Expiration := IncHour(Now, 24); jwt.Claims.Expiration := IncHour(Now, 24);
JWT.Claims.SetClaimOfType<string>('user_name', userName);
JWT.Claims.SetClaimOfType<string>('user_fullname', userFullName); jwt.Claims.SetClaimOfType<string>('user_id', Self.userId);
JWT.Claims.SetClaimOfType<string>('user_id', userId); jwt.Claims.SetClaimOfType<string>('user_name', userName);
JWT.Claims.SetClaimOfType<string>('user_perspective_id', userPerspectiveID); jwt.Claims.SetClaimOfType<string>('user_fullname', userFullName);
JWT.Claims.SetClaimOfType<string>('user_status', userStatus); jwt.Claims.SetClaimOfType<string>('user_status', userStatus);
JWT.Claims.SetClaimOfType<string>('user_email', userEmail); jwt.Claims.SetClaimOfType<string>('user_email', userEmail);
JWT.Claims.SetClaimOfType<string>('user_qb_id', userQBID); jwt.Claims.SetClaimOfType<string>('user_access_level', userAccessLevel);
JWT.Claims.SetClaimOfType<string>('user_access_type', userAccessType); jwt.Claims.SetClaimOfType<string>('task_rights', userTaskRights);
JWT.Claims.SetClaimOfType<boolean>('qb_enabled', qbEnabled); jwt.Claims.SetClaimOfType<string>('user_perspective_id', userPerspectiveId);
jwt.Claims.SetClaimOfType<string>('first_name', userFirstName);
Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, JWT); jwt.Claims.SetClaimOfType<string>('last_name', userLastName);
Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, jwt);
finally finally
JWT.Free; jwt.Free;
end;
end;
function TAuthService.CheckUser(const user, password: string): Integer;
var
userStr: string;
SQL: string;
name: string;
checkString: string;
begin
Result := 0;
Logger.Log(1, Format('AuthService.CheckUser - User: "%s"', [user]) );
SQL := 'select * from users where USER_NAME = ' + QuotedStr(user);
DoQuery(authDB.uq, SQL);
if authDB.uq.IsEmpty then
begin
Result := 1; //user does not exist, replace this with 0 for more security
end
else if ( authDB.uq.FieldByName('STATUS').AsString <> 'ACTIVE' ) then
Result := 2 // user is not active
else
begin
name := authDB.uq.FieldByName('NAME').AsString;
checkString := THashSHA2.GetHashString(name + password, THashSHA2.TSHA2Version.SHA512).ToUpper;
if authDB.uq.FieldByName('PASSWORD').AsString = checkString then
begin
userName := user;
userFullName:= authDB.uq.FieldByName('NAME').AsString;;
userId := authDB.uq.FieldByName('USER_ID').AsString;
userStatus := authDB.uq.FieldByName('STATUS').AsString;
userPerspectiveID := authDB.uq.FieldByName('PERSPECTIVE_ID').AsString;
userEmail := authDB.uq.FieldByName('EMAIL').AsString;
userQBID := authDB.uq.FieldByName('QB_ID').AsString;
userAccessType := authDB.uq.FieldByName('ACCESS_TYPE').AsString;
Logger.Log(1, Format('AuthDB.SetLoginAuditEntry: "%s"', [user]) );
Result := 3; // Succcess
end
else
Result := 0; // invalid password
end; end;
end; end;
initialization initialization
RegisterServiceType(TAuthService); RegisterServiceType(TAuthService);
end. end.
...@@ -3,7 +3,7 @@ unit Common.Config; ...@@ -3,7 +3,7 @@ unit Common.Config;
interface interface
const const
defaultServerUrl = 'http://localhost:2004/kgOrders/'; defaultServerUrl = 'http://localhost:2004/emsys/emt3';
type type
TServerConfig = class TServerConfig = class
......
...@@ -9,7 +9,7 @@ LogFileNum=175 ...@@ -9,7 +9,7 @@ LogFileNum=175
Server=192.168.102.129 Server=192.168.102.129
--Server=192.168.75.133 --Server=192.168.75.133
--Server=192.168.159.10 --Server=192.168.159.10
Database=emt3_web_db Database=eTask
Username=root Username=root
Password=emsys01 Password=emsys01
--Password=emsys!012 --Password=emsys!012
......
{ {
"url": "http://localhost:2004/kgOrders/", "url": "http://localhost:2004/emsys/emt3",
"jwtTokenSecret": "super_secret0123super_secret4567", "jwtTokenSecret": "super_secret0123super_secret4567",
"adminPassword": "whatisthisusedfor", "adminPassword": "whatisthisusedfor",
"webAppFolder": "static", "webAppFolder": "static",
......
...@@ -97,7 +97,7 @@ begin ...@@ -97,7 +97,7 @@ begin
if not DirectoryExists(logsDir) then if not DirectoryExists(logsDir) then
CreateDir(logsDir); CreateDir(logsDir);
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' ); iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'emT3webServer.ini' );
try try
fileNum := iniFile.ReadInteger( 'Settings', 'LogFileNum', 0 ); fileNum := iniFile.ReadInteger( 'Settings', 'LogFileNum', 0 );
FLogFile := logsDir + AFilename + Format( '%.4d', [fileNum] ) + '.log'; FLogFile := logsDir + AFilename + Format( '%.4d', [fileNum] ) + '.log';
...@@ -160,7 +160,7 @@ begin ...@@ -160,7 +160,7 @@ begin
Application.Initialize; Application.Initialize;
Application.MainFormOnTaskbar := True; Application.MainFormOnTaskbar := True;
Application.CreateForm(TFMain, FMain); Application.CreateForm(TFMain, FMain);
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' ); iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'emT3webServer.ini' );
try try
memoLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 3 ); memoLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 3 );
fileLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 4 ); fileLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 4 );
...@@ -168,6 +168,5 @@ begin ...@@ -168,6 +168,5 @@ begin
iniFile.Free; iniFile.Free;
end; end;
Logger.AddAppender(TMemoLogAppender.Create( memoLogLevel, FMain.memoinfo )); Logger.AddAppender(TMemoLogAppender.Create( memoLogLevel, FMain.memoinfo ));
Logger.AddAppender(TFileLogAppender.Create( fileLogLevel, 'kgOrdersServer' ));
Application.Run; Application.Run;
end. end.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment