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/
*.local
*.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
XData.Web.Client;
const
TOKEN_NAME = 'KG_ORDERS_WEB_TOKEN';
TOKEN_NAME = 'EMT3_WEB_TOKEN';
type
TOnLoginSuccess = reference to procedure;
TOnLoginError = reference to procedure(AMsg: string);
TOnProfileSuccess = reference to procedure;
TOnProfileError = reference to procedure(AMsg: string);
TAuthService = class
private
......@@ -23,8 +21,9 @@ type
public
constructor Create; reintroduce;
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;
function GetToken: string;
function Authenticated: Boolean;
......@@ -42,7 +41,7 @@ type
class function DecodePayload(AToken: string): string;
end;
function AuthService: TAuthService;
function AuthService: TAuthService;
implementation
......@@ -55,9 +54,7 @@ var
function AuthService: TAuthService;
begin
if not Assigned(_AuthService) then
begin
_AuthService := TAuthService.Create;
end;
Result := _AuthService;
end;
......@@ -91,8 +88,7 @@ begin
Result := window.localStorage.getItem(TOKEN_NAME);
end;
procedure TAuthService.Login(AUser, APassword: string; ASuccess: TOnLoginSuccess;
AError: TOnLoginError);
procedure TAuthService.Login(const userId, taskId, urlCode: string; ASuccess: TOnLoginSuccess; AError: TOnLoginError);
procedure OnLoad(Response: TXDataClientResponse);
var
......@@ -109,14 +105,14 @@ procedure TAuthService.Login(AUser, APassword: string; ASuccess: TOnLoginSuccess
end;
begin
if (AUser = '') or (APassword = '') then
if (userId = '') or (taskId = '') or (urlCode = '') then
begin
AError('Please enter a username and a password');
AError('Missing URL parameters. Please reopen from emt3.');
Exit;
end;
FClient.RawInvoke(
'IAuthService.Login', [AUser, APassword],
'IAuthService.Login', [userId, taskId, urlCode],
@OnLoad, @OnError
);
end;
......@@ -124,6 +120,10 @@ end;
procedure TAuthService.Logout;
begin
DeleteToken;
window.localStorage.removeItem('EMT3_USER_ID');
window.localStorage.removeItem('EMT3_TASK_ID');
window.localStorage.removeItem('EMT3_CODE');
end;
procedure TAuthService.SetToken(AToken: string);
......@@ -140,17 +140,9 @@ begin
ExpirationDate := TJwtHelper.TokenExpirationDate(GetToken);
Result := EncodeDate(
ExpirationDate.FullYear,
ExpirationDate.Month + 1,
ExpirationDate.Date
) +
EncodeTime(
ExpirationDate.Hours,
ExpirationDate.Minutes,
ExpirationDate.Seconds,
0
);
Result :=
EncodeDate(ExpirationDate.FullYear, ExpirationDate.Month + 1, ExpirationDate.Date) +
EncodeTime(ExpirationDate.Hours, ExpirationDate.Minutes, ExpirationDate.Seconds, 0);
end;
function TAuthService.TokenExpired: Boolean;
......@@ -176,8 +168,7 @@ begin
Result := '';
asm
const parts = AToken.split('.');
if (parts.length === 3) { // <- strict compare
// JWTs use url-safe base64; convert before atob
if (parts.length === 3) {
Result = atob(parts[1].replace(/-/g,'+').replace(/_/g,'/'));
}
end;
......
......@@ -17,11 +17,13 @@ type
procedure AuthConnectionError(Error: TXDataWebConnectionError);
private
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
FUserIdParam: string;
FTaskIdParam: string;
FCodeParam: string;
public
const clientVersion = '0.0.1';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure InitApp(SuccessProc: TSuccessProc; UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback);
end;
......@@ -52,11 +54,10 @@ begin
Args.Request.Headers.SetValue('Authorization', 'Bearer ' + AuthService.GetToken);
end;
procedure TDMConnection.ApiConnectionResponse(
Args: TXDataWebConnectionResponse);
procedure TDMConnection.ApiConnectionResponse(Args: TXDataWebConnectionResponse);
begin
if Args.Response.StatusCode = 401 then
FUnauthorizedAccessProc(Format('%d: %s',[Args.Response.StatusCode, Args.Response.ContentAsText]));
if (Args.Response.StatusCode = 401) and Assigned(FUnauthorizedAccessProc) then
FUnauthorizedAccessProc(Format('%d: %s', [Args.Response.StatusCode, Args.Response.ContentAsText]));
end;
procedure TDMConnection.AuthConnectionError(Error: TXDataWebConnectionError);
......@@ -83,13 +84,12 @@ begin
LoadConfig(@ConfigLoaded);
end;
procedure TDMConnection.SetClientConfig(Callback: TVersionCheckCallback);
begin
XDataWebClient1.Connection := AuthConnection;
XDataWebClient1.RawInvoke('IAuthService.VerifyVersion', [clientVersion],
XDataWebClient1.RawInvoke(
'IAuthService.VerifyVersion', [clientVersion],
procedure(Response: TXDataClientResponse)
var
jsonResult: TJSObject;
......@@ -106,8 +106,12 @@ begin
Callback(False, error)
else
Callback(True, '');
end);
end,
procedure(Error: TXDataClientError)
begin
Callback(False, Error.ErrorMessage);
end
);
end;
end.
......@@ -46596,7 +46596,6 @@ object FViewLogin: TFViewLogin
Role = 'null'
TabOrder = 2
WidthPercent = 100.000000000000000000
OnClick = btnLoginClick
end
object pnlMessage: TWebPanel
Left = 240
......@@ -20,7 +20,6 @@ type
XDataWebClient: TXDataWebClient;
WebImageControl1: TWebImageControl;
lblClientVersion: TWebLabel;
procedure btnLoginClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject);
procedure WebFormShow(Sender: TObject);
private
......@@ -43,26 +42,6 @@ uses
{$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);
begin
TFViewLogin.Display(LoginProc, '');
......
......@@ -64,7 +64,7 @@
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">
Back to Orders
Restart
</button>
</div>
</div>
......
......@@ -45,7 +45,6 @@ implementation
uses
Auth.Service,
View.Login,
View.Tasks,
View.TasksHTML,
View.TasksDataGrid,
......@@ -62,11 +61,11 @@ begin
end;
procedure TFViewMain.WebFormCreate(Sender: TObject);
var
userName: string;
test: boolean;
begin
console.log('TFViewMain.WebFormCreate fired');
FChildForm := nil;
console.log('About to ShowForm(TFTasksHTML), host=' + WebPanel1.ElementID);
ShowForm(TFTasksHTML);
lblAppTitle.Caption := 'emT3web';
lblVersion.Caption := 'v' + DMConnection.clientVersion;
......@@ -89,7 +88,7 @@ end;
procedure TFViewMain.ConfirmLogout;
begin
ShowConfirmationModal(
'Are you sure you want to log out?',
'End this session?.',
'Yes',
'No',
procedure(confirmed: Boolean)
......
......@@ -3,7 +3,7 @@ object FTasksHTML: TFTasksHTML
Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
OnCreate = WebFormCreate
OnShow = WebFormShow
object btnReload: TWebButton
Left = 78
Top = 88
......
......@@ -30,9 +30,10 @@ type
xdwdsTaskstaskItemId: TStringField;
procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject);
procedure WebFormShow(Sender: TObject);
private
[async] procedure LoadTasks(const AProjectId: string);
FTaskId: string;
[async] procedure LoadTasks(const ATaskId: string);
procedure RenderTable;
procedure BindTableEditors;
......@@ -44,7 +45,7 @@ type
procedure GotoRowIndex(AIndex: Integer);
function HtmlEncode(const s: string): string;
procedure SetProjectLabel(const AProjectId: string);
procedure SetTaskLabel(const ATaskId: string);
[async] procedure SaveRow(AIndex: Integer);
procedure EditorBlur(Event: TJSEvent);
......@@ -61,9 +62,20 @@ uses
{$R *.dfm}
procedure TFTasksHTML.WebFormCreate(Sender: TObject);
procedure TFTasksHTML.WebFormShow(Sender: TObject);
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;
......@@ -191,23 +203,19 @@ end;
procedure TFTasksHTML.btnAddRowClick(Sender: TObject);
begin
if not xdwdsTasks.Active then
Exit;
xdwdsTasks.Append;
xdwdsTaskstaskId.AsString := 'NEW_TASK_ID';
xdwdsTasksreportedBy.AsString := '';
xdwdsTasksassignedTo.AsString := '';
xdwdsTasksstatus.AsString := '';
xdwdsTasks.Post;
RenderTable;
Utils.ShowErrorModal('Add row is not enabled yet.');
end;
procedure TFTasksHTML.btnReloadClick(Sender: TObject);
begin
LoadTasks('WPR0001');
if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing Task Id. Update url params or resend from emT3.');
Exit;
end;
LoadTasks(FTaskId);
end;
procedure TFTasksHTML.EnableAutoGrowTextAreas;
......@@ -225,29 +233,37 @@ begin
end;
end;
procedure TFTasksHTML.SetProjectLabel(const AProjectId: string);
procedure TFTasksHTML.SetTaskLabel(const ATaskId: string);
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('lbl_project_name'));
if Assigned(el) then
el.innerText := 'Tasks - ' + AProjectId;
el.innerText := 'Tasks - ' + ATaskId;
end;
[async] procedure TFTasksHTML.LoadTasks(const AProjectId: string);
[async] procedure TFTasksHTML.LoadTasks(const ATaskId: string);
var
response: TXDataClientResponse;
resultObj, taskObj: TJSObject;
tasksArray, itemsArray, flatItems: TJSArray;
taskIndex, itemIndex: Integer;
begin
SetProjectLabel(AProjectId);
SetTaskLabel(ATaskId);
Utils.ShowSpinner('spinner');
try
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.GetProjectTasks', [AProjectId]
'IApiService.GetTaskItems', [ATaskId]
));
except
on E: EXDataClientRequestException do
begin
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
if not Assigned(response.Result) then
Exit;
......@@ -275,6 +291,7 @@ begin
end;
end;
procedure TFTasksHTML.RenderTable;
var
host: TJSHTMLElement;
......@@ -566,5 +583,7 @@ end;
end.
{
"AuthUrl" : "http://localhost:2004/kgOrders/auth/",
"ApiUrl" : "http://localhost:2004/kgOrders/api/"
"AuthUrl" : "http://localhost:2004/emsys/emt3/auth/",
"ApiUrl" : "http://localhost:2004/emsys/emt3/api/"
}
......@@ -2,6 +2,9 @@ program emT3webClient;
uses
Vcl.Forms,
System.SysUtils,
JS,
Web,
XData.Web.Connection,
WEBLib.Dialogs,
Auth.Service in 'Auth.Service.pas',
......@@ -19,7 +22,57 @@ uses
{$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;
......@@ -27,6 +80,7 @@ procedure DisplayMainView;
begin
if Assigned(FViewLogin) then
FViewLogin.Free;
TFViewMain.Display(@DisplayLoginView);
end;
......@@ -37,25 +91,33 @@ begin
ConnectProc;
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);
begin
DisplayLoginView(AMessage);
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;
var
UserIdParam: string;
TaskIdParam: string;
CodeParam: string;
begin
UserIdParam := Application.Parameters.Values['user_id'];
TaskIdParam := Application.Parameters.Values['task_id'];
CodeParam := Application.Parameters.Values['code'];
SaveUrlParamsToStorage(UserIdParam, TaskIdParam, CodeParam);
DMConnection.InitApp(
procedure
begin
......@@ -64,33 +126,30 @@ begin
begin
if Success then
begin
if (not AuthService.Authenticated) or AuthService.TokenExpired then
DisplayLoginView
else
if (UserIdParam <> '') and (TaskIdParam <> '') and (CodeParam <> '') then
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
DisplayLoginView;
end
else
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'>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;
DisplayAccessDeniedModal(ErrorMessage);
end;
end);
end,
......@@ -98,9 +157,6 @@ begin
);
end;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
......
......@@ -141,27 +141,22 @@
<DCCReference Include="Utils.pas"/>
<DCCReference Include="View.Tasks.pas">
<Form>FTasks</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksHTML.pas">
<Form>FTasksHTML</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksDataGrid.pas">
<Form>FTasksDataGrid</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksTabulator.pas">
<Form>FTasksTabulator</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksDBGrid.pas">
<Form>FTasksDBGrid</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<None Include="index.html"/>
......
......@@ -6,7 +6,7 @@
<noscript>Your browser does not support JavaScript!</noscript>
<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://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
......
......@@ -2,9 +2,9 @@ object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate
Height = 358
Width = 519
object ucEmT3: TUniConnection
object ucETaskApi: TUniConnection
ProviderName = 'MySQL'
Database = 'emt3_web_db'
Database = 'eTask'
Username = 'root'
Server = '192.168.102.129'
Connected = True
......@@ -18,7 +18,7 @@ object ApiDatabase: TApiDatabase
Top = 66
end
object uqUsers: TUniQuery
Connection = ucEmT3
Connection = ucETaskApi
SQL.Strings = (
'SELECT USER_ID, NAME, STATUS from users ORDER BY NAME')
OnCalcFields = uqUsersCalcFields
......@@ -42,84 +42,10 @@ object ApiDatabase: TApiDatabase
Calculated = True
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
Connection = ucEmT3
Connection = ucETaskApi
SQL.Strings = (
'UPDATE task_items'
'UPDATE web_tasks'
'SET'
' APPLICATION = :APPLICATION,'
' APP_VERSION = :APP_VERSION,'
......@@ -133,8 +59,8 @@ object ApiDatabase: TApiDatabase
' ISSUE = :ISSUE,'
' NOTES = :NOTES'
'WHERE TASK_ITEM_ID = :TASK_ITEM_ID')
Left = 308
Top = 208
Left = 306
Top = 198
ParamData = <
item
DataType = ftUnknown
......@@ -197,4 +123,149 @@ object ApiDatabase: TApiDatabase
Value = nil
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
......@@ -9,29 +9,30 @@ uses
type
TApiDatabase = class(TDataModule)
ucEmT3: TUniConnection;
ucETaskApi: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider;
uqUsers: TUniQuery;
uqUsersUSER_ID: TIntegerField;
uqUsersNAME: TStringField;
uqUsersSTATUS: 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;
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 uqUsersCalcFields(DataSet: TDataSet);
private
......@@ -54,9 +55,9 @@ uses
procedure TApiDatabase.DataModuleCreate(Sender: TObject);
begin
Logger.Log( 5, 'TApiDatabase.DataModuleCreate' );
LoadDatabaseSettings( ucEmT3, 'emT3webServer.ini' );
LoadDatabaseSettings( ucETaskApi, 'emT3webServer.ini' );
try
ucEmT3.Connect;
ucETaskApi.Connect;
except
on E: Exception do
begin
......
......@@ -10,13 +10,16 @@ object ApiServerModule: TApiServerModule
ModelName = 'Api'
EntitySetPermissions = <>
SwaggerOptions.Enabled = True
SwaggerOptions.AuthMode = Jwt
SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True
Left = 80
Top = 142
object XDataServerJWT: TSparkleJwtMiddleware
ForbidAnonymousAccess = True
OnGetSecret = XDataServerJWTGetSecret
OnForbidRequest = XDataServerJWTForbidRequest
end
object XDataServerCompress: TSparkleCompressMiddleware
end
......
......@@ -25,6 +25,8 @@ type
XDataServerCompress: TSparkleCompressMiddleware;
XDataServerCORS: TSparkleCorsMiddleware;
XDataServerLogging: TSparkleLoggingMiddleware;
procedure XDataServerJWTForbidRequest(Sender: TObject; Context:
THttpServerContext; var Forbid: Boolean);
procedure XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
procedure XDataServerJWTGetSecret(Sender: TObject; var Secret: string);
......@@ -86,6 +88,27 @@ begin
Logger.Log(1, Format('Api server module listening at "%s"', [XDataServer.BaseUrl]));
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;
var Middleware: IHttpServerMiddleware);
begin
......
......@@ -77,7 +77,7 @@ type
[ServiceContract, Model(API_MODEL)]
IApiService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
[HttpGet] function GetProjectTasks(projectId: string): TTasksList;
function GetTaskItems(taskId: string): TTasksList;
[HttpPost] function SaveTaskRow(const Item: TTaskRowSave): Boolean;
end;
......
......@@ -3,9 +3,14 @@ unit Api.ServiceImpl;
interface
uses
XData.Server.Module, System.Generics.Collections,
XData.Service.Common, System.Variants, System.DateUtils,
Api.Service, Api.Database, Common.Logging, System.SysUtils;
XData.Server.Module,
XData.Service.Common,
System.Variants,
System.DateUtils,
Api.Service,
Api.Database,
Common.Logging,
System.SysUtils;
type
[ServiceImplementation]
......@@ -13,104 +18,133 @@ type
strict private
ApiDB: TApiDatabase;
private
procedure EnsureBlankWebTaskRow(const taskId: string);
function SaveTaskRow(const Item: TTaskRowSave): Boolean;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
function GetProjectTasks(projectId: string): TTasksList;
function GetTaskItems(taskId: string): TTasksList;
end;
implementation
procedure TApiService.AfterConstruction;
begin
inherited;
ApiDB := TApiDatabase.Create(nil);
Logger.Log(4, 'ApiService.AfterConstruction - ApiDB created');
end;
procedure TApiService.BeforeDestruction;
begin
Logger.Log(4, 'ApiService.BeforeDestruction - freeing ApiDB');
ApiDB.Free;
inherited;
end;
function TApiService.GetProjectTasks(projectId: string): TTasksList;
function TApiService.GetTaskItems(taskId: string): TTasksList;
var
taskMap: TDictionary<string, TTask>;
task: TTask;
item: TTaskItem;
taskId: string;
begin
Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId]));
Result := TTasksList.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
taskMap := TDictionary<string, TTask>.Create;
try
ApiDB.uqProjectTasks.Close;
ApiDB.uqProjectTasks.ParamByName('PROJECT_ID').AsString := projectId;
ApiDB.uqProjectTasks.Open;
ApiDB.uqWebTasks.Close;
ApiDB.uqWebTasks.ParamByName('TASK_ID').AsString := taskId;
ApiDB.uqWebTasks.Open;
while not ApiDB.uqProjectTasks.Eof do
if ApiDB.uqWebTasks.IsEmpty then
begin
taskId := ApiDB.uqProjectTasksTASK_ID.AsString;
Logger.Log(4, Format('ApiService.GetTaskItems - no rows for TASK_ID="%s", ensuring blank row', [taskId]));
EnsureBlankWebTaskRow(taskId);
ApiDB.uqWebTasks.Close;
ApiDB.uqWebTasks.ParamByName('TASK_ID').AsString := taskId;
ApiDB.uqWebTasks.Open;
end;
if not taskMap.TryGetValue(taskId, task) then
if ApiDB.uqWebTasks.IsEmpty then
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.uqProjectTasksPROJECT_ID.AsString;
task.projectId := ApiDB.uqWebTasksPROJECT_ID.AsString;
taskMap.Add(taskId, task);
Result.data.Add(task);
end;
while not ApiDB.uqWebTasks.Eof do
begin
item := TTaskItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);
item.taskItemId := ApiDB.uqProjectTasksTASK_ITEM_ID.AsString;
item.taskId := ApiDB.uqProjectTasksTASK_ID.AsString;
item.projectId := ApiDB.uqProjectTasksPROJECT_ID.AsString;
item.taskItemId := ApiDB.uqWebTasksTASK_ITEM_ID.AsString;
item.taskId := ApiDB.uqWebTasksTASK_ID.AsString;
item.projectId := ApiDB.uqWebTasksPROJECT_ID.AsString;
item.application := ApiDB.uqProjectTasksAPPLICATION.AsString;
item.version := ApiDB.uqProjectTasksAPP_VERSION.AsString;
item.application := ApiDB.uqWebTasksAPPLICATION.AsString;
item.version := ApiDB.uqWebTasksAPP_VERSION.AsString;
if ApiDB.uqProjectTasksTASK_DATE.IsNull then
if ApiDB.uqWebTasksTASK_DATE.IsNull then
item.taskDate := 0
else
item.taskDate := ApiDB.uqProjectTasksTASK_DATE.AsDateTime;
item.taskDate := ApiDB.uqWebTasksTASK_DATE.AsDateTime;
item.reportedBy := ApiDB.uqProjectTasksREPORTED_BY.AsString;
item.assignedTo := ApiDB.uqProjectTasksASSIGNED_TO.AsString;
item.reportedBy := ApiDB.uqWebTasksREPORTED_BY.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
else
item.statusDate := ApiDB.uqProjectTasksSTATUS_DATE.AsDateTime;
item.statusDate := ApiDB.uqWebTasksSTATUS_DATE.AsDateTime;
item.fixedVersion := ApiDB.uqProjectTasksFIXED_VERSION.AsString;
item.formSection := ApiDB.uqProjectTasksFORM_SECTION.AsString;
item.issue := ApiDB.uqProjectTasksISSUE.AsString;
item.notes := ApiDB.uqProjectTasksNOTES.AsString;
item.fixedVersion := ApiDB.uqWebTasksFIXED_VERSION.AsString;
item.formSection := ApiDB.uqWebTasksFORM_SECTION.AsString;
item.issue := ApiDB.uqWebTasksISSUE.AsString;
item.notes := ApiDB.uqWebTasksNOTES.AsString;
task.items.Add(item);
ApiDB.uqProjectTasks.Next;
ApiDB.uqWebTasks.Next;
end;
Result.count := Result.data.Count;
finally
taskMap.Free;
Logger.Log(4, Format('ApiService.GetTaskItems - returned %d task(s)', [Result.count]));
except
on E: Exception do
begin
Logger.Log(2, 'ApiService.GetTaskItems - ERROR: ' + E.Message);
raise;
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;
......@@ -124,6 +158,9 @@ function TApiService.SaveTaskRow(const Item: TTaskRowSave): Boolean;
var
d: TDateTime;
begin
Logger.Log(4, Format('ApiService.SaveTaskRow - TASK_ITEM_ID="%s"', [Item.taskItemId]));
try
ApiDB.uqSaveTaskRow.Close;
ApiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsString := Item.taskItemId;
......@@ -144,7 +181,7 @@ begin
if ParseDateOrZero(Item.statusDate, d) then
ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := d
else
ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').Clear;
ApiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := Date;
ApiDB.uqSaveTaskRow.ParamByName('FIXED_VERSION').AsString := Item.fixedVersion;
ApiDB.uqSaveTaskRow.ParamByName('FORM_SECTION').AsString := Item.formSection;
......@@ -154,11 +191,19 @@ begin
ApiDB.uqSaveTaskRow.ExecSQL;
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
RegisterServiceType(TypeInfo(IApiService));
RegisterServiceType(TApiService);
end.
......@@ -3,31 +3,116 @@ object AuthDatabase: TAuthDatabase
OnDestroy = DataModuleDestroy
Height = 249
Width = 433
object uq: TUniQuery
Connection = ucKG
object uqWebTasksUrl: TUniQuery
Connection = ucETaskAuth
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
Active = True
Left = 162
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
object uqMisc: TUniQuery
FetchRows = 100
Left = 249
Top = 45
end
object ucKG: TUniConnection
object ucETaskAuth: TUniConnection
ProviderName = 'MySQL'
Database = 'emt3_web_db'
Database = 'eTask'
Username = 'root'
Server = '192.168.102.129'
Connected = True
LoginPrompt = False
Left = 67
Top = 131
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 230
Top = 140
Left = 194
Top = 132
end
end
......@@ -10,10 +10,21 @@ uses
type
TAuthDatabase = class(TDataModule)
uq: TUniQuery;
uqMisc: TUniQuery;
ucKG: TUniConnection;
uqWebTasksUrl: TUniQuery;
ucETaskAuth: TUniConnection;
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 DataModuleDestroy(Sender: TObject);
private
......@@ -40,9 +51,9 @@ uses
procedure TAuthDatabase.DataModuleCreate(Sender: TObject);
begin
Logger.Log( 5, 'TAuthDatabase.DataModuleCreate' );
LoadDatabaseSettings( ucKG, 'kgOrdersServer.ini' );
LoadDatabaseSettings( ucETaskAuth, 'emT3WebServer.ini' );
try
ucKG.Connect;
ucETaskAuth.Connect;
except
on E: Exception do
begin
......@@ -53,7 +64,7 @@ end;
procedure TAuthDatabase.DataModuleDestroy(Sender: TObject);
begin
ucKG.Connected := false;
ucETaskAuth.Connected := false;
end;
......
......@@ -18,7 +18,7 @@ type
[ServiceContract, Model(AUTH_MODEL)]
IAuthService = interface(IInvokable)
['{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;
end;
......
......@@ -14,22 +14,25 @@ type
TAuthService = class(TInterfacedObject, IAuthService)
strict private
authDB: TAuthDatabase;
function GetQuery: TUniQuery;
private
userId: string;
userName: string;
userFullName: string;
userId: string;
userPerspectiveID: string;
userQBID: string;
userAccessType: string;
userEmail: string;
userStatus: string;
qbEnabled: boolean;
userEmail: string;
userAccessLevel: string;
userTaskRights: string;
userPerspectiveId: string;
userFirstName: string;
userLastName: string;
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
function CheckUser(const user, password: string): Integer;
function CheckUrlLogin(const userId, taskId, urlCode: string): Integer;
procedure LoadUserFromUrlLoginQuery;
public
function Login(const user, password: string): string;
function Login(const userId, taskId, urlCode: string): string;
function VerifyVersion(ClientVersion: string): TJSONObject;
end;
......@@ -38,16 +41,12 @@ implementation
uses
System.SysUtils,
System.DateUtils,
System.Generics.Collections,
Bcl.JOSE.Core.Builder,
Bcl.JOSE.Core.JWT,
Aurelius.Global.Utils,
XData.Sys.Exceptions,
Common.Logging,
Common.Config,
uLibrary;
{ TLoginService }
Common.Config;
procedure TAuthService.AfterConstruction;
begin
......@@ -58,7 +57,7 @@ begin
on E: Exception do
begin
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;
......@@ -69,11 +68,6 @@ begin
inherited;
end;
function TAuthService.GetQuery: TUniQuery;
begin
Result := authDB.uq;
end;
function TAuthService.VerifyVersion(ClientVersion: string): TJSONObject;
var
iniFile: TIniFile;
......@@ -86,14 +80,14 @@ begin
try
webClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
Result.AddPair('webClientVersion', webClientVersion);
qbEnabled := iniFile.ReadBool('Quickbooks', 'Enabled', false);
if webClientVersion = '' then
begin
Result.AddPair('error', 'webClientVersion is not configured.');
Exit;
end;
if clientVersion <> webClientVersion then
if ClientVersion <> webClientVersion then
begin
Result.AddPair('error',
'Your browser is running an old version of the app.' + sLineBreak +
......@@ -104,111 +98,103 @@ begin
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;
function TAuthService.Login(const user, password: string): string;
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 userId, taskId, urlCode: string): string;
var
userState: Integer;
iniFile: TIniFile;
JWT: TJWT;
jwt: TJWT;
begin
Logger.Log(3, Format( 'AuthService.Login - User: "%s"', [User]));
userState := CheckUser( user, password );
Logger.Log(3, Format('AuthService.Login - UserID: "%s", TaskID: "%s"', [userId, taskId]));
try
userState := CheckUser(user, password);
userState := CheckUrlLogin(userId, taskId, urlCode);
except
on E: Exception do
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.');
end;
end;
if userState = 0 then
begin
raise EXDataHttpUnauthorized.Create('Invalid username or password');
logger.Log(2, 'Login Error: Invalid username or password');
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!');
Logger.Log(2, 'Login Error: Invalid code or expired link');
raise EXDataHttpUnauthorized.Create('Invalid code or expired link');
end;
iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
qbEnabled := iniFile.ReadBool('Quickbooks', 'Enabled', false);
finally
iniFile.Free;
if userState = 2 then
begin
Logger.Log(2, 'Login Error: User not active!');
raise EXDataHttpUnauthorized.Create('User not active!');
end;
JWT := TJWT.Create;
jwt := TJWT.Create;
try
JWT.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
JWT.Claims.IssuedAt := Now;
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', userId);
JWT.Claims.SetClaimOfType<string>('user_perspective_id', userPerspectiveID);
JWT.Claims.SetClaimOfType<string>('user_status', userStatus);
JWT.Claims.SetClaimOfType<string>('user_email', userEmail);
JWT.Claims.SetClaimOfType<string>('user_qb_id', userQBID);
JWT.Claims.SetClaimOfType<string>('user_access_type', userAccessType);
JWT.Claims.SetClaimOfType<boolean>('qb_enabled', qbEnabled);
Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, JWT);
jwt.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
jwt.Claims.IssuedAt := Now;
jwt.Claims.Expiration := IncHour(Now, 24);
jwt.Claims.SetClaimOfType<string>('user_id', Self.userId);
jwt.Claims.SetClaimOfType<string>('user_name', userName);
jwt.Claims.SetClaimOfType<string>('user_fullname', userFullName);
jwt.Claims.SetClaimOfType<string>('user_status', userStatus);
jwt.Claims.SetClaimOfType<string>('user_email', userEmail);
jwt.Claims.SetClaimOfType<string>('user_access_level', userAccessLevel);
jwt.Claims.SetClaimOfType<string>('task_rights', userTaskRights);
jwt.Claims.SetClaimOfType<string>('user_perspective_id', userPerspectiveId);
jwt.Claims.SetClaimOfType<string>('first_name', userFirstName);
jwt.Claims.SetClaimOfType<string>('last_name', userLastName);
Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, jwt);
finally
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
jwt.Free;
end;
end;
initialization
RegisterServiceType(TAuthService);
end.
......@@ -3,7 +3,7 @@ unit Common.Config;
interface
const
defaultServerUrl = 'http://localhost:2004/kgOrders/';
defaultServerUrl = 'http://localhost:2004/emsys/emt3';
type
TServerConfig = class
......
......@@ -9,7 +9,7 @@ LogFileNum=175
Server=192.168.102.129
--Server=192.168.75.133
--Server=192.168.159.10
Database=emt3_web_db
Database=eTask
Username=root
Password=emsys01
--Password=emsys!012
......
{
"url": "http://localhost:2004/kgOrders/",
"url": "http://localhost:2004/emsys/emt3",
"jwtTokenSecret": "super_secret0123super_secret4567",
"adminPassword": "whatisthisusedfor",
"webAppFolder": "static",
......
......@@ -97,7 +97,7 @@ begin
if not DirectoryExists(logsDir) then
CreateDir(logsDir);
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' );
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'emT3webServer.ini' );
try
fileNum := iniFile.ReadInteger( 'Settings', 'LogFileNum', 0 );
FLogFile := logsDir + AFilename + Format( '%.4d', [fileNum] ) + '.log';
......@@ -160,7 +160,7 @@ begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TFMain, FMain);
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' );
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'emT3webServer.ini' );
try
memoLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 3 );
fileLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 4 );
......@@ -168,6 +168,5 @@ begin
iniFile.Free;
end;
Logger.AddAppender(TMemoLogAppender.Create( memoLogLevel, FMain.memoinfo ));
Logger.AddAppender(TFileLogAppender.Create( fileLogLevel, 'kgOrdersServer' ));
Application.Run;
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