public async Task <ActionResult <IEnumerable <ParatextProject> > > GetAsync() { Attempt <UserSecret> attempt = await _userSecrets.TryGetAsync(_userAccessor.UserId); if (!attempt.TryResult(out UserSecret userSecret)) { return(NoContent()); } try { IReadOnlyList <ParatextProject> projects = await _paratextService.GetProjectsAsync(userSecret); return(Ok(projects)); } catch (SecurityException) { return(NoContent()); } }
public async Task <ActionResult <IEnumerable <ParatextProject> > > GetAsync() { UserEntity user = await _users.GetAsync(_userAccessor.UserId); if (user.ParatextTokens == null) { return(NoContent()); } try { IReadOnlyList <ParatextProject> projects = await _paratextService.GetProjectsAsync(user); return(Ok(projects)); } catch (SecurityException) { return(NoContent()); } }
/// <summary> /// Returns SF project id of created project. /// </summary> public async Task <string> CreateProjectAsync(string curUserId, SFProjectCreateSettings settings) { Attempt <UserSecret> userSecretAttempt = await _userSecrets.TryGetAsync(curUserId); if (!userSecretAttempt.TryResult(out UserSecret userSecret)) { throw new DataNotFoundException("The user does not exist."); } IReadOnlyList <ParatextProject> ptProjects = await _paratextService.GetProjectsAsync(userSecret); ParatextProject ptProject = ptProjects.SingleOrDefault(p => p.ParatextId == settings.ParatextId); if (ptProject == null) { throw new DataNotFoundException("The paratext project does not exist."); } var project = new SFProject { ParatextId = settings.ParatextId, Name = ptProject.Name, ShortName = ptProject.ShortName, WritingSystem = new WritingSystem { Tag = ptProject.LanguageTag }, TranslateConfig = new TranslateConfig { TranslationSuggestionsEnabled = settings.TranslationSuggestionsEnabled }, CheckingConfig = new CheckingConfig { CheckingEnabled = settings.CheckingEnabled } }; Attempt <string> attempt = await TryGetProjectRoleAsync(project, curUserId); if (!attempt.TryResult(out string projectRole) || projectRole != SFProjectRole.Administrator) { throw new ForbiddenException(); } string projectId = ObjectId.GenerateNewId().ToString(); using (IConnection conn = await RealtimeService.ConnectAsync(curUserId)) { if (this.RealtimeService.QuerySnapshots <SFProject>().Any( (SFProject sfProject) => sfProject.ParatextId == project.ParatextId)) { throw new InvalidOperationException(ErrorAlreadyConnectedKey); } IDocument <SFProject> projectDoc = await conn.CreateAsync <SFProject>(projectId, project); await ProjectSecrets.InsertAsync(new SFProjectSecret { Id = projectDoc.Id }); IDocument <User> userDoc = await conn.FetchAsync <User>(curUserId); await AddUserToProjectAsync(conn, projectDoc, userDoc, SFProjectRole.Administrator, false); // Add the source after the project has been created // This will make the source project appear after the target, if it needs to be created if (settings.SourceParatextId != null && settings.SourceParatextId != settings.ParatextId) { TranslateSource source = await this.GetTranslateSourceAsync( curUserId, userSecret, settings.SourceParatextId, ptProjects); await projectDoc.SubmitJson0OpAsync(op => { UpdateSetting(op, p => p.TranslateConfig.Source, source); }); } if (projectDoc.Data.TranslateConfig.TranslationSuggestionsEnabled) { var machineProject = new MachineProject { Id = projectDoc.Id, SourceLanguageTag = projectDoc.Data.TranslateConfig.Source.WritingSystem.Tag, TargetLanguageTag = projectDoc.Data.WritingSystem.Tag }; await _engineService.AddProjectAsync(machineProject); } } await _syncService.SyncAsync(curUserId, projectId, true); return(projectId); }
/// <summary> /// Creates an internal test project. /// </summary> /// <param name="sourceId">The source project/resource identifier.</param> /// <param name="targetId">The target project identifier. /// This is the project that will reference this project/resource a source.</param> /// <returns>THe task</returns> /// <exception cref="DataNotFoundException"> /// The target project does not exist /// or /// The user does not exist. /// </exception> /// <remarks> /// This is only to be used on test runs! /// </remarks> internal async Task CreateInternalTestProjectAsync(string sourceId, string targetId) { if (!this._testProjectCollection.Any(p => p.ParatextId == sourceId) && !string.IsNullOrWhiteSpace(sourceId)) { // Create the test project SFProject testProject = new SFProject { Id = ObjectId.GenerateNewId().ToString(), ParatextId = sourceId, }; this._testProjectCollection.Add(testProject); // Load the source project from the target project's source directory (it is not moved in test mode) ScrText?scrText = SourceScrTextCollection.FindById("admin", targetId); if (scrText != null) { // Create the test text objects for all of the books foreach (int bookNum in scrText.Settings.BooksPresentSet.SelectedBookNumbers) { string usfm = scrText.GetText(bookNum); string bookText = UsfmToUsx.ConvertToXmlString(scrText, bookNum, usfm, false); var usxDoc = XDocument.Parse(bookText); Dictionary <int, ChapterDelta> deltas = this._deltaUsxMapper.ToChapterDeltas(usxDoc).ToDictionary(cd => cd.Number); var chapters = new List <Chapter>(); foreach (KeyValuePair <int, ChapterDelta> kvp in deltas) { this._testTextDataIdCollection.Add(TextData.GetTextDocId(testProject.Id, bookNum, kvp.Key)); } } } else { Program.Log($"Test Mode Error Migrating TextData For {sourceId} - Could Not Load From Filesystem!"); } // See that at least one user in the target project has permission to create the source project var targetProject = this._realtimeService.QuerySnapshots <SFProject>().FirstOrDefault(p => p.ParatextId == targetId); if (targetProject == null) { throw new DataNotFoundException("The target project does not exist"); } // Get the highest ranked users for this project, that probably have source access string[] userIds = targetProject.UserRoles .Where(ur => ur.Value == SFProjectRole.Administrator || ur.Value == SFProjectRole.Translator) .OrderBy(ur => ur.Value) .Select(ur => ur.Key) .ToArray(); bool someoneCanAccessSourceProject = false; foreach (string userId in userIds) { Attempt <UserSecret> userSecretAttempt = await _userSecrets.TryGetAsync(userId); if (!userSecretAttempt.TryResult(out UserSecret userSecret)) { throw new DataNotFoundException("The user does not exist."); } // We check projects first, in case it is a project IReadOnlyList <ParatextProject> ptProjects = await _paratextService.GetProjectsAsync(userSecret); if (ptProjects.Any(p => p.ParatextId == sourceId)) { someoneCanAccessSourceProject = true; break; } } if (!someoneCanAccessSourceProject) { Program.Log($"Test Mode Error Creating {sourceId} - Nobody In The Target Project Has Access!"); } } }
/// <summary> /// First-stage migrator. Synchronize all SF projects to the Paratext Data Access server. /// First query information that will show whether we should be able to sync all projects. In addition to /// reporting information on projects and whether there is an admin that can sync the project, this method shows /// that the admin can successfully perform queries to both the PT Registry and the PT Data Access web APIs, via /// various ParatextService method calls. /// If `doSynchronizations` is false, only do the above reporting. If true, also synchronize the SF DB with the /// Paratext Data Access server. /// </summary> public async Task SynchronizeAllProjectsAsync(bool doSynchronizations, ISet <string> sfProjectIdsToSynchronize = null, IDictionary <string, string> sfAdminsToUse = null) { List <SFProject> allSfProjects = _realtimeService.QuerySnapshots <SFProject>().ToList <SFProject>(); if (sfProjectIdsToSynchronize != null) { allSfProjects.RemoveAll((SFProject sfProject) => !sfProjectIdsToSynchronize.Contains(sfProject.Id)); string ids = string.Join(' ', allSfProjects.Select((SFProject sfProject) => sfProject.Id)); int count = allSfProjects.Count; _logger.Log($"Only working on the subset of projects (count {count}) with these SF project ids: {ids}"); } _realtimeServiceConnection = await _realtimeService.ConnectAsync(); List <Task> syncTasks = new List <Task>(); // Report on all SF projects. foreach (SFProject sfProject in allSfProjects) { _logger.Log($"{Program.Bullet1} PT project {sfProject.ShortName}, " + $"PT project id {sfProject.ParatextId}, SF project id {sfProject.Id}."); List <string> projectSfAdminUserIds = sfProject.UserRoles .Where(ur => ur.Value == SFProjectRole.Administrator).Select(ur => ur.Key).ToList <string>(); if (projectSfAdminUserIds.Count() < 1) { List <string> projectSfUserIds = sfProject.UserRoles.Select(ur => ur.Key).ToList <string>(); string users = string.Join(", ", projectSfUserIds); if (projectSfUserIds.Count() < 1) { users = "None"; } _logger.Log($" {Program.Bullet2} Warning: no admin users. Non-admin users include: {users}"); } // Report on all admins in a project foreach (string sfUserId in projectSfAdminUserIds) { UserSecret userSecret = _userSecretRepo.Query() .FirstOrDefault((UserSecret us) => us.Id == sfUserId); string ptUsername = null; string ptUserId = null; try { ptUsername = _paratextService.GetParatextUsername(userSecret); ptUserId = GetParatextUserId(userSecret); } catch (Exception e) { _logger.Log($" {Program.Bullet2} Failure getting SF user's PT username or PT user id. " + $"Skipping. SF user id was {sfUserId}. If known, PT username was {ptUsername}. " + $"Error with stack was {e}"); continue; } _logger.Log($" {Program.Bullet2} PT user '{ptUsername}', " + $"id {ptUserId}, using SF admin user id {sfUserId} on SF project."); string rt = $"{userSecret.ParatextTokens.RefreshToken.Substring(0, 5)}.."; string at = $"{userSecret.ParatextTokens.AccessToken.Substring(0, 5)}.."; bool atv = userSecret.ParatextTokens.ValidateLifetime(); _logger.Log($" {Program.Bullet3} Paratext RefreshToken: {rt}, " + $"AccessToken: {at}, AccessToken initially valid: {atv}."); // Demonstrate access to PT Registry, and report Registry's statement of role. _logger.Log($" {Program.Bullet3} PT Registry report on role on PT project: ", false); IReadOnlyDictionary <string, string> ptProjectRoles = null; try { ptProjectRoles = await _paratextService.GetProjectRolesAsync(userSecret, sfProject.ParatextId); } catch (Exception e) { Console.WriteLine($" Failure fetching user's PT project roles. Skipping. " + $"Error was {e.Message}"); continue; } if (ptProjectRoles.TryGetValue(ptUserId, out string ptRole)) { Console.WriteLine($"{ptRole}"); } else { Console.WriteLine($"Not found."); } // Demonstrate access to PT Data Access. IReadOnlyList <ParatextProject> userPtProjects = null; try { userPtProjects = await _paratextService.GetProjectsAsync(userSecret); } catch (Exception e) { _logger.Log($" {Program.Bullet3} Failure fetching user's PT projects. Skipping. " + $"Error was {e.Message}"); continue; } _logger.Log($" {Program.Bullet3} PT Data Access and PT Registry " + "based report on projects the user can access, narrowed to this project: ", false); List <string> ptProjectNamesList = userPtProjects .Where(ptProject => ptProject.ParatextId == sfProject.ParatextId) .Select(ptProject => ptProject.ShortName).ToList(); string ptProjectNames = string.Join(',', ptProjectNamesList); if (ptProjectNamesList.Count() < 1) { ptProjectNames = $"User is not on this project. " + $"PT reports they are on this many PT projects: {userPtProjects.Count()}"; } Console.WriteLine(ptProjectNames); if (doSynchronizations) { if (sfAdminsToUse != null && sfAdminsToUse.ContainsKey(sfProject.Id)) { sfAdminsToUse.TryGetValue(sfProject.Id, out string sfAdminIdToUse); bool isUserAtHand = sfUserId == sfAdminIdToUse; if (isUserAtHand) { _logger.Log($" {Program.Bullet2} For SF Project {sfProject.Id}, we were asked to use " + $"this SF user {sfUserId} to sync."); } else { _logger.Log($" {Program.Bullet2} For SF Project {sfProject.Id}, we were asked to use " + $"SF user {sfAdminIdToUse}, not {sfUserId}, to sync. So skipping this user."); continue; } } try { _logger.Log($" {Program.Bullet2} Starting an asynchronous synchronization for " + $"SF project {sfProject.Id} as SF user {sfUserId}."); Task syncTask = SynchronizeProject(sfUserId, sfProject.Id); var projectDoc = await _realtimeServiceConnection.FetchAsync <SFProject>(sfProject.Id); // Increment the queued count (such as done in SyncService), since it gets decremented // later by ParatextSyncRunner. await projectDoc.SubmitJson0OpAsync(op => op.Inc(pd => pd.Sync.QueuedCount)); _logger.Log($" {Program.Bullet3} Synchronization task for SF project {sfProject.Id} as " + $"SF user {sfUserId} has Sync Task Id {syncTask.Id}."); syncTasks.Add(syncTask); break; } catch (Exception e) { // We probably won't get here. But just in case. _logger.Log($" {Program.Bullet3} There was a problem with synchronizing. It might be " + $"tried next with another admin user. Exception is:{Environment.NewLine}{e}"); continue; } } } } if (doSynchronizations) { _logger.Log("Waiting for synchronization tasks to finish (if any). " + $"There are this many tasks: {syncTasks.Count}"); try { Task allTasks = Task.WhenAll(syncTasks); try { await allTasks.ConfigureAwait(false); } catch { if (allTasks.Exception == null) { throw; } ExceptionDispatchInfo.Capture(allTasks.Exception).Throw(); } _logger.Log("Synchronization tasks are finished."); } catch (AggregateException e) { _logger.Log("There was a problem with one or more synchronization tasks. " + $"Exception is:{Environment.NewLine}{e}"); } if (syncTasks.Any(task => !task.IsCompletedSuccessfully)) { _logger.Log("One or more sync tasks did not complete successfully."); } else { _logger.Log("All sync tasks finished with a claimed Task status of Completed Successfully."); } _logger.Log($"{Program.Bullet1} Sync task completion results:"); foreach (Task task in syncTasks) { string exceptionInfo = $"with exception {task.Exception?.InnerException}."; if (task.Exception == null) { exceptionInfo = "with no unhandled exception thrown."; } _logger.Log($" {Program.Bullet2} Sync task Id {task.Id} has status {task.Status} {exceptionInfo}"); if (task.Exception?.InnerExceptions?.Count > 1) { _logger.Log($" {Program.Bullet3} Sync task Id {task.Id} has more than one inner exception. " + "Sorry if this is redundant, but they are:"); foreach (var e in task.Exception.InnerExceptions) { _logger.Log($" {Program.Bullet3} Inner exception: {e}"); } } } } ReportLastSyncSuccesses(allSfProjects); }