Ejemplo n.º 1
0
        private async Task <IActionResult> AuthTest(string projectId, string resultOutcome)
        {
            try
            {
                using var session = _documentStore.OpenAsyncSession();
                var user = await _platformAdminUserManager.GetByUniqueIdentifierAsync(User.Identity.Name, session);

                var testMode = TestProjectId.IsValidIdentity(projectId) && !ProjectId.IsValidIdentity(projectId);
                var project  = testMode ? await _projectManager.GetTest((TestProjectId)projectId, session) : await _projectManager.Get((ProjectId)projectId, session);

                var result = await _applicationTestHttpClient.SendAuthCallback(project.Applications.First(), Guid.NewGuid().ToString(), resultOutcome, Guid.NewGuid().ToString());

                return(Ok(result));
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, "Unable to test application. {@request}", projectId);
                // return error message if there was an exception

                return(Ok(GenericResponse.Failed(ex.Message, (System.Net.HttpStatusCode)ex.StatusCode)));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unable to test application. {@request}", projectId);
                // return error message if there was an exception

                return(Ok(GenericResponse.Failed(ex.Message, System.Net.HttpStatusCode.BadRequest)));
            }
        }
Ejemplo n.º 2
0
        public async Task <Project> Update(Project project, IAsyncDocumentSession session)
        {
            _logger.LogInformation("Project update request {@id}", project.Id);

            if (TestProjectId.IsValidIdentity(project.Id))
            {
                return(await UpdateTest((TestProject)project, session));
            }

            var p = await session.LoadAsync <Project>(project.Id);

            // _logger.LogDebug("Project before {@p}", p);
            if (ProjectId.IsValidIdentity(project.Id))
            {
                await UpdateTestProjectName(project.Id, project.Name, session);

                p.Name = project.Name;
            }
            p.LogoUrl      = project.LogoUrl;
            p.Webpage      = project.Webpage;
            p.Description  = project.Description;
            p.Applications = project.Applications;
            p.Platforms    = project.Platforms;
            p.OwnerAdminId = project.OwnerAdminId;
            p.AdminIds     = project.AdminIds;
            //await session.SaveChangesAsync();
            // _logger.LogDebug("Project after {@p}", p);
            _logger.LogInformation("Project updated {@id}", p.Id);
            return(p);
        }
Ejemplo n.º 3
0
        public void PortAllBEPsTestsUsingAnUnopenedSource(
            [Values(FDOBackendProviderType.kXML, FDOBackendProviderType.kDb4oClientServer)]
            FDOBackendProviderType sourceType,
            [Values(FDOBackendProviderType.kXML, FDOBackendProviderType.kDb4oClientServer, FDOBackendProviderType.kMemoryOnly)]
            FDOBackendProviderType targetType)
        {
            var path = Path.Combine(Path.GetTempPath(), "FieldWorksTest");

            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            var sourceBackendStartupParameters = GenerateBackendStartupParameters(false, sourceType);
            var targetBackendStartupParameters = GenerateBackendStartupParameters(true, targetType);

            var sourceGuids = new List <Guid>();

            // Make sure we start from a clean slate
            DeleteDatabase(sourceBackendStartupParameters);
            DeleteDatabase(targetBackendStartupParameters);

            // Set up data source
            var projId = new TestProjectId(sourceBackendStartupParameters.ProjectId.Type,
                                           sourceBackendStartupParameters.ProjectId.Path);

            using (FdoCache sourceCache = FdoCache.CreateCacheWithNewBlankLangProj(
                       projId, "en", "fr", "en", new DummyFdoUI(), FwDirectoryFinder.FdoDirectories, new FdoSettings()))
            {
                // BEP is a singleton, so we shouldn't call Dispose on it. This will be done
                // by service locator.
                var sourceDataSetup = GetMainBEPInterface(sourceCache);
                sourceCache.ServiceLocator.GetInstance <IUndoStackManager>().Save();                // persist the new db so we can reopen it.
                sourceDataSetup.LoadDomain(BackendBulkLoadDomain.All);
                sourceGuids.AddRange(GetAllCmObjects(sourceCache).Select(obj => obj.Guid));         // Collect all source Guids
            }

            // Migrate source data to new BEP.
            IThreadedProgress progressDlg = new DummyProgressDlg();

            using (var targetCache = FdoCache.CreateCacheWithNoLangProj(
                       new TestProjectId(targetBackendStartupParameters.ProjectId.Type, null), "en", new DummyFdoUI(), FwDirectoryFinder.FdoDirectories, new FdoSettings()))
            {
                // BEP is a singleton, so we shouldn't call Dispose on it. This will be done
                // by service locator.
                var targetDataSetup = GetMainBEPInterface(targetCache);
                targetDataSetup.InitializeFromSource(new TestProjectId(targetBackendStartupParameters.ProjectId.Type,
                                                                       targetBackendStartupParameters.ProjectId.Path), sourceBackendStartupParameters, "en", progressDlg);
                targetDataSetup.LoadDomain(BackendBulkLoadDomain.All);
                CompareResults(sourceGuids, targetCache);
            }
            sourceGuids.Clear();
            // Try to clean up after ourselves
            DeleteDatabase(sourceBackendStartupParameters, false);
            DeleteDatabase(targetBackendStartupParameters, false);
        }
Ejemplo n.º 4
0
        public async Task <IActionResult> Create([FromBody] ApplicationCreationRequest request)
        {
            try
            {
                // Who's logged in?
                using var session = _documentStore.OpenAsyncSession();
                var user = await _platformAdminUserManager.GetByUniqueIdentifierAsync(User.Identity.Name, session);

                // Which project are we working on?

                var testMode = TestProjectId.IsValidIdentity(request.ProjectId) && !ProjectId.IsValidIdentity(request.ProjectId);

                var project = testMode ? await _projectManager.GetTest((TestProjectId)request.ProjectId, session) : await _projectManager.Get((ProjectId)request.ProjectId, session);

                // Does the user have access to the project?
                if (!project.AdminIds.Contains(user.Id) && project.OwnerAdminId != user.Id)
                {
                    throw new ApiException("Seems you are not an admin on this project.", (int)System.Net.HttpStatusCode.Unauthorized);
                }

                var registeredApplication = await _applicationHttpClient.CreateApplication(new CreateApplicationModel
                {
                    Name            = project.Name,
                    AuthCallbackUrl = request.AuthCallbackUrl ?? "",
                    //EmailVerificationNotificationEndpointUrl = request.EmailVerificationUrl ?? "",
                    DataUpdateCallbackUrl = request.DataUpdateCallbackUrl ?? ""
                });

                if (string.IsNullOrEmpty(registeredApplication.ApplicationId))
                {
                    _logger.LogError("Create application failed. {@request} {@registeredApplication}", request, registeredApplication);
                    throw new ApiException("Creating the application failed.");
                }
                var application = request.CreateApplication(registeredApplication);

                // One application per project, so just replace
                project.Applications = new List <Core.Entities.Application> {
                    application
                };
                // Save
                project = await _projectManager.Update(project, session);

                await session.SaveChangesAsync();

                return(Ok(project));
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, "Unable to create application. {@request}", request);
                // return error message if there was an exception

                // return BadRequest(new { message = ex.Message });
                throw;
            }
        }
Ejemplo n.º 5
0
        public async Task <IActionResult> Dummy([FromRoute] string projectType, [FromRoute] string id)
        {
            var projectId = $"{projectType}/{id}";

            _logger.LogInformation("Dummy data for {projectId}", projectId);
            using var session = _documentStore.OpenAsyncSession();
            //var user = await _platformAdminUserManager.GetByUniqueIdentifierAsync(User.Identity.Name, session);
            var testMode = TestProjectId.IsValidIdentity(projectId) && !ProjectId.IsValidIdentity(projectId);
            var project  = testMode ? await _projectManager.GetTest((TestProjectId)projectId, session) : await _projectManager.Get((ProjectId)projectId, session);

            var payload = DummyPayload(project.Name, project.Applications.FirstOrDefault()?.SecretKey);

            return(Ok(payload));
        }
        public void PortAllBEPsTestsUsingAnAlreadyOpenedSource(
            [Values(BackendProviderType.kXMLWithMemoryOnlyWsMgr, BackendProviderType.kMemoryOnly)]
            BackendProviderType sourceType,
            [Values(BackendProviderType.kXMLWithMemoryOnlyWsMgr, BackendProviderType.kMemoryOnly)]
            BackendProviderType targetType)
        {
            var sourceBackendStartupParameters = GenerateBackendStartupParameters(false, sourceType);
            var targetBackendStartupParameters = GenerateBackendStartupParameters(true, targetType);

            // Set up data source, but only do it once.
            var sourceGuids     = new List <Guid>();
            var sourceProjectId = new TestProjectId(sourceBackendStartupParameters.ProjectId.Type,
                                                    sourceBackendStartupParameters.ProjectId.Path);

            using (var sourceCache = LcmCache.CreateCacheWithNewBlankLangProj(sourceProjectId, "en", "fr", "en", new DummyLcmUI(),
                                                                              m_lcmDirectories, new LcmSettings()))
            {
                // BEP is a singleton, so we shouldn't call Dispose on it. This will be done
                // by service locator.
                var sourceDataSetup = GetMainBEPInterface(sourceCache);
                // The source is created ex nihilo.
                sourceDataSetup.LoadDomain(sourceBackendStartupParameters.BulkLoadDomain);
                sourceGuids.AddRange(GetAllCmObjects(sourceCache).Select(obj => obj.Guid));                 // Collect all source Guids

                DeleteDatabase(targetBackendStartupParameters);

                // Migrate source data to new BEP.
                var targetProjectId = new TestProjectId(targetBackendStartupParameters.ProjectId.Type,
                                                        targetBackendStartupParameters.ProjectId.Path);
                using (var targetCache = LcmCache.CreateCacheCopy(targetProjectId, "en", new DummyLcmUI(),
                                                                  m_lcmDirectories, new LcmSettings(), sourceCache))
                {
                    // BEP is a singleton, so we shouldn't call Dispose on it. This will be done
                    // by service locator.
                    var targetDataSetup = GetMainBEPInterface(targetCache);
                    targetDataSetup.LoadDomain(BackendBulkLoadDomain.All);

                    CompareResults(sourceGuids, targetCache);
                }
            }
        }
Ejemplo n.º 7
0
        public async Task <IActionResult> Data([FromRoute] string projectType, [FromRoute] string id)
        {
            var projectId = $"{projectType}/{id}";

            _logger.LogInformation("Dummy data url test for {projectId}", projectId);
            using var session = _documentStore.OpenAsyncSession();
            //var user = await _platformAdminUserManager.GetByUniqueIdentifierAsync(User.Identity.Name, session);
            var testMode = TestProjectId.IsValidIdentity(projectId) && !ProjectId.IsValidIdentity(projectId);
            var project  = testMode ? await _projectManager.GetTest((TestProjectId)projectId, session) : await _projectManager.Get((ProjectId)projectId, session);

            var payload = DummyPayload(project.Name, project.Applications.FirstOrDefault()?.SecretKey);

            try
            {
                var result = await _applicationTestHttpClient.SendDataTest(project.Applications.First(), payload);

                return(Ok(result));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unable to test application. {@request}", projectId);
                return(Ok(GenericResponse.Failed(ex.Message, System.Net.HttpStatusCode.BadRequest)));
            }
        }
Ejemplo n.º 8
0
 public async Task <TestProject> GetTest(TestProjectId projectId, IAsyncDocumentSession session)
 => await session.LoadAsync <TestProject>(projectId.Value);
        public void PortAllBEPsTestsUsingAnUnopenedSource()
        {
            var path = Path.Combine(Path.GetTempPath(), "FieldWorksTest");

            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            foreach (var sourceBackendStartupParameters in m_sourceInfo)
            {
                if (sourceBackendStartupParameters == null)
                {
                    continue;
                }

                // The Memory only source BEP can't tested here, since it can't be deleted, created, and restarted
                // which is required of all source BEPs in this test.
                // The source memory BEP is tested in 'PortAllBEPsTestsUsingAnAlreadyOpenedSource',
                // since source BEPs are only created once and the open connection is reused for all targets.
                if (sourceBackendStartupParameters.ProjectId.Type == FDOBackendProviderType.kMemoryOnly)
                {
                    continue;
                }

                DeleteDatabase(sourceBackendStartupParameters);
                var createSource = true;
                var sourceGuids  = new List <Guid>();
                foreach (var targetBackendStartupParameters in m_targetInfo)
                {
                    if (targetBackendStartupParameters == null)
                    {
                        continue;
                    }

                    DeleteDatabase(targetBackendStartupParameters);

                    if (createSource)                     // When 'createSource' is true, the source is created ex-nihilo.
                    {
                        DeleteDatabase(sourceBackendStartupParameters);
                    }

                    // Set up data source, but only do it once.
                    TestProjectId projId = new TestProjectId(sourceBackendStartupParameters.ProjectId.Type,
                                                             sourceBackendStartupParameters.ProjectId.Path);
                    IThreadedProgress progressDlg = new DummyProgressDlg();
                    using (FdoCache sourceCache = createSource ? FdoCache.CreateCacheWithNewBlankLangProj(
                               projId, "en", "fr", "en", progressDlg.ThreadHelper) :
                                                  FdoCache.CreateCacheFromExistingData(projId, "en", progressDlg))
                    {
                        // BEP is a singleton, so we shouldn't call Dispose on it. This will be done
                        // by service locator.
                        var sourceDataSetup = GetMainBEPInterface(sourceCache);
                        if (createSource)
                        {
                            sourceCache.ServiceLocator.GetInstance <IUndoStackManager>().Save(); // persist the new db so we can reopen it.
                            createSource = false;                                                // Next time, only load it.
                        }
                        sourceDataSetup.LoadDomain(BackendBulkLoadDomain.All);
                        foreach (var obj in GetAllCmObjects(sourceCache))
                        {
                            sourceGuids.Add(obj.Guid);                             // Collect up all source Guids.
                        }
                    }

                    // Migrate source data to new BEP.
                    using (var targetCache = FdoCache.CreateCacheWithNoLangProj(
                               new TestProjectId(targetBackendStartupParameters.ProjectId.Type, null), "en", progressDlg.ThreadHelper))
                    {
                        // BEP is a singleton, so we shouldn't call Dispose on it. This will be done
                        // by service locator.
                        var targetDataSetup = GetMainBEPInterface(targetCache);
                        targetDataSetup.InitializeFromSource(new TestProjectId(targetBackendStartupParameters.ProjectId.Type,
                                                                               targetBackendStartupParameters.ProjectId.Path), sourceBackendStartupParameters, "en", progressDlg);
                        targetDataSetup.LoadDomain(BackendBulkLoadDomain.All);
                        CompareResults(sourceGuids, targetCache);
                    }
                    sourceGuids.Clear();
                }
            }
        }
 /// <summary/>
 public void Startup()
 {
     ProjectId = new TestProjectId(FDOBackendProviderType.kXML, Project);
     StartupInternal(ModelVersion);
 }
        public async Task <IActionResult> Save(IFormFile file, [FromRoute] string projectNamespace, [FromRoute] string projectId,
                                               CancellationToken cancellationToken)
        {
            if (!string.Equals(file.ContentType, "image/jpg", StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(file.ContentType, "image/jpeg", StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(file.ContentType, "image/pjpeg", StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(file.ContentType, "image/gif", StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(file.ContentType, "image/x-png", StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(file.ContentType, "image/png", StringComparison.OrdinalIgnoreCase))
            {
                throw new Exception("Incorrect file type");
            }

            // Who's logged in?
            using var session = _documentStore.OpenAsyncSession();
            var user = await _platformAdminUserManager.GetByUniqueIdentifierAsync(User.Identity.Name, session);

            projectId = $"{projectNamespace}/{projectId}";

            var testMode = TestProjectId.IsValidIdentity(projectId) && !ProjectId.IsValidIdentity(projectId);

            // Which project are we working on?
            var project = testMode ?
                          await _projectManager.GetTest((TestProjectId)projectId, session) :
                          await _projectManager.Get((ProjectId)projectId, session);

            string fileExtension;

            switch (file.ContentType)
            {
            case "image/jpg":
            case "image/jpeg":
            case "image/pjpeg":
                fileExtension = ".jpg";
                break;

            case "image/x-png":
            case "image/png":
                fileExtension = ".png";
                break;

            case "image/gif":
                fileExtension = ".gif";
                break;

            default:
                throw new Exception("Incorrect file type");
            }

            var fileName = $"{Guid.NewGuid()}{fileExtension}";

            // Does the user have access to the project?
            if (!project.AdminIds.Contains(user.Id) && project.OwnerAdminId != user.Id)
            {
                throw new ApiException("Seems you are not an admin on this project.", (int)System.Net.HttpStatusCode.Unauthorized);
            }
            try
            {
                return(Ok(await _fileManager.UploadFileAsync(file, fileName, $"devprojects/assets")));
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Unable to upload file.");
                return(StatusCode(StatusCodes.Status500InternalServerError));
            }
        }
Ejemplo n.º 12
0
        public async Task <IActionResult> Save([FromBody] UpdateApplicationUrlsRequest request)
        {
            var errors = Util.UriErrors(new Dictionary <string, string> {
                { "auth-callback-url", request.AuthCallbackUrl },
                { "data-update-callback-url", request.DataUpdateCallbackUrl },
                //{ "email-verification-url", request.EmailVerificationUrl },
            }, _logger);

            // if (errors != null && errors.Any())
            // {
            //     return BadRequest(new { message = "All urls have to be valid.", errors = errors });
            // }
            try
            {
                // Who's logged in?
                using var session = _documentStore.OpenAsyncSession();
                var user = await _platformAdminUserManager.GetByUniqueIdentifierAsync(User.Identity.Name, session);

                // Which project are we working on?


                var testMode = TestProjectId.IsValidIdentity(request.ProjectId) && !ProjectId.IsValidIdentity(request.ProjectId);

                var project = testMode ? await _projectManager.GetTest((TestProjectId)request.ProjectId, session) : await _projectManager.Get((ProjectId)request.ProjectId, session);

                // Does the user have access to the project?
                if (!project.AdminIds.Contains(user.Id) && project.OwnerAdminId != user.Id)
                {
                    throw new ApiException("Seems you are not an admin on this project.", (int)System.Net.HttpStatusCode.Unauthorized);
                }
                var  application = project.Applications?.FirstOrDefault();
                bool recreated   = false;

                if (application != null)
                {
                    // Check that the application exists through the API
                    try
                    {
                        var apiApplication = await _applicationHttpClient.Get(application.Id);
                    }
                    catch (ApiException ex)
                    {
                        // Unable to get the application
                        if (ex.InnerException is System.Net.Http.HttpRequestException && ex.StatusCode == (int)System.Net.HttpStatusCode.NotFound)
                        {
                            // Not found in API - recreate the application
                            _logger.LogInformation("Application not found in API. Attempting to re-create.");
                            var apiApplication = await _applicationHttpClient.CreateApplication(new CreateApplicationModel
                            {
                                Name            = project.Name,
                                AuthCallbackUrl = request.AuthCallbackUrl ?? "",
                                //EmailVerificationNotificationEndpointUrl = request.EmailVerificationUrl ?? "",
                                DataUpdateCallbackUrl = request.DataUpdateCallbackUrl ?? ""
                            });

                            if (apiApplication != null)
                            {
                                _logger.LogInformation("New application created. {id}", apiApplication.ApplicationId);
                                recreated   = true;
                                application = new Core.Entities.Application
                                {
                                    Id              = apiApplication.ApplicationId,
                                    SecretKey       = apiApplication.SecretKey,
                                    AuthCallbackUrl = request.AuthCallbackUrl,
                                    //EmailVerificationUrl = request.EmailVerificationUrl,
                                    DataUpdateCallbackUrl = request.DataUpdateCallbackUrl
                                };
                            }
                        }
                        if (ex.InnerException is System.Net.Http.HttpRequestException && ex.StatusCode == (int)System.Net.HttpStatusCode.RequestTimeout)
                        {
                            throw;
                        }
                        // throw;
                    }
                    catch (System.Exception)
                    {
                        throw;
                    }
                    if (!recreated)
                    {
                        //await _applicationHttpClient.PatchEmailVerificationUrl(application.Id, request.EmailVerificationUrl);
                        await _applicationHttpClient.PatchApiEndpointAppSetNotificationUrl(application.Id, request.DataUpdateCallbackUrl);

                        await _applicationHttpClient.PatchAuthCallbackUrl(application.Id, request.AuthCallbackUrl);

                        application = new Core.Entities.Application
                        {
                            Id              = application.Id,
                            SecretKey       = application.SecretKey,
                            AuthCallbackUrl = request.AuthCallbackUrl,
                            //EmailVerificationUrl = request.EmailVerificationUrl,
                            DataUpdateCallbackUrl = request.DataUpdateCallbackUrl
                        };
                    }
                }
                else
                {
                    _logger.LogError("No application found.");
                    return(BadRequest(new { message = "No application found." }));
                }

                // One application per project, so just replace
                project.Applications = new List <Core.Entities.Application> {
                    application
                };
                // Save
                project = await _projectManager.Update(project, session);

                await session.SaveChangesAsync();

                return(Ok(project));
            }
            catch (ApiException ex)
            {
                // return error message if there was an exception
                return(BadRequest(new { message = ex.Message }));
            }
        }
        public async Task<Project> Update(UpdateProjectRequest request, IAsyncDocumentSession session, PlatformAdminUserId userId)
        {
            _logger.LogInformation("Project update {@projectId}", request.Id);

            var requestName = request.Name.Trim();

            if (string.IsNullOrEmpty(requestName) || string.IsNullOrWhiteSpace(requestName))
            {
                _logger.LogError("Project update - empty name requested");
                throw new ApiException("Seems you are not an admin on this project.", (int)System.Net.HttpStatusCode.BadRequest);
            }

            var testMode = TestProjectId.IsValidIdentity(request.Id) && !ProjectId.IsValidIdentity(request.Id);

            var project = testMode ? await _projectManager.GetTest((TestProjectId)request.Id, session) : await _projectManager.Get((ProjectId)request.Id, session);

            if (!project.AdminIds.Contains(userId.Value) && project.OwnerAdminId != userId.Value)
            {
                _logger.LogError("User {userId} is not part of {ownerId} or {@adminIds}", userId.Value, project.OwnerAdminId, project.AdminIds);
                throw new ApiException("Seems you are not an admin on this project.", (int)System.Net.HttpStatusCode.Unauthorized);
            }

            var updates = new List<string>();
            var application = project.Applications.FirstOrDefault();
            var platform = project.Platforms.FirstOrDefault();

            if (project.Name != requestName)
            {
                if (testMode)
                {
                    _logger.LogInformation("TestProject - skipping name update {@projectId}", request.Id);
                }
                else
                {
                    // TODO: Catch exception thrown on failed update
                    if (application != null)
                        await _applicationHttpClient.SetName(application.Id, requestName);
                    if (platform != null)
                        await _platformAdminHttpClient.SetName(platform.Id.ToString(), requestName);

                    project.Name = requestName;
                    updates.Add(nameof(Project.Name));
                    // _logger.LogInformation("Project update: {@property}", nameof(Project.Name));
                }
            }
            if (project.LogoUrl != request.LogoUrl)
            {
                // TODO: Catch exception thrown on failed update
                if (application != null && !testMode)
                    await _applicationHttpClient.SetLogoUrl(application.Id, request.LogoUrl);
                if (platform != null && !testMode)
                    await _platformAdminHttpClient.SetLogoUrl(platform.Id.ToString(), request.LogoUrl);
                project.LogoUrl = request.LogoUrl;
                updates.Add(nameof(Project.LogoUrl));
                // _logger.LogInformation("Project update: {@property}", nameof(Project.LogoUrl));
            }
            if (project.Description != request.Description)
            {
                // TODO: Catch exception thrown on failed update
                if (application != null && !testMode)
                    await _applicationHttpClient.SetDescription(application.Id, request.Description);
                if (platform != null && !testMode)
                    await _platformAdminHttpClient.SetDescription(platform.Id.ToString(), request.Description);
                project.Description = request.Description;
                updates.Add(nameof(Project.Description));
                // _logger.LogInformation("Project update: {@property}", nameof(Project.Description));
            }
            if (project.Webpage != request.Webpage)
            {
                // TODO: Catch exception thrown on failed update
                if (application != null && !testMode)
                    await _applicationHttpClient.SetWebsiteUrl(application.Id, request.Webpage);
                if (platform != null && !testMode)
                    await _platformAdminHttpClient.SetWebsiteUrl(platform.Id.ToString(), request.Webpage);
                project.Webpage = request.Webpage;
                updates.Add(nameof(Project.Webpage));
                // _logger.LogInformation("Project update: {@property}", nameof(Project.Webpage));
            }

            if (updates.Any())
            {
                _logger.LogInformation("Project update: Preparing to save {@updates}", updates);
                project = await _projectManager.Update(project, session);
                await session.SaveChangesAsync();
            }
            return project;
        }