From ac43b18e095296175580ee31392b50042de1572b Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 11 Jul 2024 16:07:07 +0700 Subject: [PATCH 01/26] Initial work extracting language tags Doesn't store anything in the DB yet. --- backend/LexBoxApi/Services/HgService.cs | 6 +- backend/LexBoxApi/Services/ProjectService.cs | 57 +++++++++++++++++++ .../LexCore/ServiceInterfaces/IHgService.cs | 1 + hgweb/command-runner.sh | 6 +- 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index 0b4288650..a691fe370 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -160,6 +160,10 @@ await Task.Run(() => }); } + public Task GetWsTagsFromFlexProject(ProjectCode code, CancellationToken token = default) + { + return ExecuteHgCommandServerCommand(code, "flexwritingsystems", token); + } public Task RevertRepo(ProjectCode code, string revHash) { @@ -254,11 +258,11 @@ public async Task GetChangesets(ProjectCode projectCode) return logResponse?.Changesets ?? Array.Empty(); } - public Task VerifyRepo(ProjectCode code, CancellationToken token) { return ExecuteHgCommandServerCommand(code, "verify", token); } + public async Task ExecuteHgRecover(ProjectCode code, CancellationToken token) { var response = await ExecuteHgCommandServerCommand(code, "recover", token); diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index 21ec60f14..6237c0b1b 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -141,6 +141,63 @@ public async Task FinishReset(string code, Stream? zipFile = null) await dbContext.SaveChangesAsync(); } + /// + /// Returns either an empty string, or XML (in string form) with a root LangTags element containing five child elements: AnalysisWss, CurAnalysisWss, VernWss, CurVernWss, and CurPronunWss. + /// Each child element will contain a single `` element whose text content is a list of tags separated by spaces. + /// + /// + /// + public async Task GetLangTagsAsXml(ProjectCode code) // May become private eventually + { + var result = await hgService.GetWsTagsFromFlexProject(code); + var xmlBody = await result.ReadAsStringAsync(); + if (string.IsNullOrEmpty(xmlBody)) return string.Empty; + return $"{xmlBody}"; + } + + public enum GuessConfidence { Low, Medium, High }; + public record LanguageGuess(string LangTag, GuessConfidence confidence); + public async Task GuessVernacularLanguage(ProjectCode code) + { + var langTags = await VernacularAndAnalysisLangTags(code); + if (langTags is null) return null; // Probably not a FieldWorks project + + // Just one vernacular tag? Easy. + if (langTags.VernWss.Length == 1) return new LanguageGuess(langTags.VernWss[0], GuessConfidence.High); + + // Multiple tags but they all refer to the same language? Also easy. + // TODO: Implement. Confidence = high. + + // Multiple languages, but all but one of them are also in the analysis list? Also easy. + // TODO: Implement. Confidence = high. + + // Multiple languages, but we couldn't narrow it down using the analysis list? See if any are in the project code. + // TODO: Implement. Split project code by hyphens, then normalize 2-to-3 so `en` would become `eng`. + // Then if the first vernacular-only language is present in some segment of the project code, return that. Confidence = medium. + + // If all else fails, guess the first vernacular tag with low confidence + return new LanguageGuess(langTags.VernWss[0], GuessConfidence.Low); + } + + private record VALangTags(string[] VernWss, string[] AnalysisWss); + private async Task VernacularAndAnalysisLangTags(ProjectCode code) + { + var langTagsXml = await GetLangTagsAsXml(code); + if (string.IsNullOrEmpty(langTagsXml)) return null; + var doc = new System.Xml.XmlDocument(); + doc.LoadXml(langTagsXml); + var root = doc.DocumentElement; + if (root is null) return null; + var vernWssStr = root["CurVernVss"]?["Uni"]?.InnerText; + if (vernWssStr is null) return null; + var analysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText; + if (analysisWssStr is null) return null; + var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + // TODO: Use SIL.WritingSystems.IetfLanguageTags functions to parse, validate, and normalize these + return new VALangTags(vernWss, analysisWss); + } + public async ValueTask LookupProjectOrgIds(Guid projectId) { var cacheKey = $"ProjectOrgsForId:{projectId}"; diff --git a/backend/LexCore/ServiceInterfaces/IHgService.cs b/backend/LexCore/ServiceInterfaces/IHgService.cs index 60876ea06..f3643d43d 100644 --- a/backend/LexCore/ServiceInterfaces/IHgService.cs +++ b/backend/LexCore/ServiceInterfaces/IHgService.cs @@ -11,6 +11,7 @@ public interface IHgService Task DetermineProjectType(ProjectCode projectCode); Task DeleteRepo(ProjectCode code); Task SoftDeleteRepo(ProjectCode code, string deletedRepoSuffix); + Task GetWsTagsFromFlexProject(ProjectCode code, CancellationToken token = default); BackupExecutor? BackupRepo(ProjectCode code); Task ResetRepo(ProjectCode code); Task FinishReset(ProjectCode code, Stream zipFile); diff --git a/hgweb/command-runner.sh b/hgweb/command-runner.sh index d03074d54..0adada857 100644 --- a/hgweb/command-runner.sh +++ b/hgweb/command-runner.sh @@ -1,7 +1,7 @@ #!/bin/bash # Define the list of allowed commands -allowed_commands=("verify" "tip" "wesaylexentrycount" "lexentrycount" "recover" "healthz" "invalidatedircache") +allowed_commands=("verify" "tip" "wesaylexentrycount" "lexentrycount" "flexwritingsystems" "recover" "healthz" "invalidatedircache") # Get the project code and command name from the URL IFS='/' read -ra PATH_SEGMENTS <<< "$PATH_INFO" @@ -63,6 +63,10 @@ case $command_name in [ -n "${LIFTFILE}" ] && (chg cat -r tip "${LIFTFILE}" | grep -c '/,/<\/AnalysisWss>/p' -e '//,/<\/VernWss>/p' -e '//,/<\/CurAnalysisWss>/p' -e '//,/<\/CurVernWss>/p' -e '//,/<\/CurPronunWss>/p' + ;; + tip) chg tip --template '{node}' ;; From bda54e1856a2b7ef06e39982d6ce44318de9ef5b Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 12 Jul 2024 08:37:34 +0700 Subject: [PATCH 02/26] Load all four ws lists from .langproj file We skip pronunciation since it's a subset of vernacular and we don't need it. We load all vern/analysis, and current vern/analysis. --- backend/LexBoxApi/Services/ProjectService.cs | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index 6237c0b1b..96d71a2d7 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -163,7 +163,7 @@ public record LanguageGuess(string LangTag, GuessConfidence confidence); if (langTags is null) return null; // Probably not a FieldWorks project // Just one vernacular tag? Easy. - if (langTags.VernWss.Length == 1) return new LanguageGuess(langTags.VernWss[0], GuessConfidence.High); + if (langTags.CurVernWss.Length == 1) return new LanguageGuess(langTags.CurVernWss[0], GuessConfidence.High); // Multiple tags but they all refer to the same language? Also easy. // TODO: Implement. Confidence = high. @@ -176,11 +176,11 @@ public record LanguageGuess(string LangTag, GuessConfidence confidence); // Then if the first vernacular-only language is present in some segment of the project code, return that. Confidence = medium. // If all else fails, guess the first vernacular tag with low confidence - return new LanguageGuess(langTags.VernWss[0], GuessConfidence.Low); + return new LanguageGuess(langTags.CurVernWss[0], GuessConfidence.Low); } - private record VALangTags(string[] VernWss, string[] AnalysisWss); - private async Task VernacularAndAnalysisLangTags(ProjectCode code) + public record ProjectLangTags(string[] VernWss, string[] AnalysisWss, string[] CurVernWss, string[] CurAnalysisWss); + public async Task VernacularAndAnalysisLangTags(ProjectCode code) { var langTagsXml = await GetLangTagsAsXml(code); if (string.IsNullOrEmpty(langTagsXml)) return null; @@ -188,14 +188,15 @@ private record VALangTags(string[] VernWss, string[] AnalysisWss); doc.LoadXml(langTagsXml); var root = doc.DocumentElement; if (root is null) return null; - var vernWssStr = root["CurVernVss"]?["Uni"]?.InnerText; - if (vernWssStr is null) return null; - var analysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText; - if (analysisWssStr is null) return null; + var vernWssStr = root["VernVss"]?["Uni"]?.InnerText ?? ""; + var analysisWssStr = root["AnalysisWss"]?["Uni"]?.InnerText ?? ""; + var curVernWssStr = root["CurVernVss"]?["Uni"]?.InnerText ?? ""; + var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - // TODO: Use SIL.WritingSystems.IetfLanguageTags functions to parse, validate, and normalize these - return new VALangTags(vernWss, analysisWss); + var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + return new ProjectLangTags(vernWss, analysisWss, curVernWss, curAnalysisWss); } public async ValueTask LookupProjectOrgIds(Guid projectId) From 2c9bbe803afe1ff2577cbb952a9493c46c13f85a Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 12 Jul 2024 08:38:32 +0700 Subject: [PATCH 03/26] Remove language-guessing logic for now This can go in another PR --- backend/LexBoxApi/Services/ProjectService.cs | 24 -------------------- 1 file changed, 24 deletions(-) diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index 96d71a2d7..a9822fc75 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -155,30 +155,6 @@ public async Task GetLangTagsAsXml(ProjectCode code) // May become priva return $"{xmlBody}"; } - public enum GuessConfidence { Low, Medium, High }; - public record LanguageGuess(string LangTag, GuessConfidence confidence); - public async Task GuessVernacularLanguage(ProjectCode code) - { - var langTags = await VernacularAndAnalysisLangTags(code); - if (langTags is null) return null; // Probably not a FieldWorks project - - // Just one vernacular tag? Easy. - if (langTags.CurVernWss.Length == 1) return new LanguageGuess(langTags.CurVernWss[0], GuessConfidence.High); - - // Multiple tags but they all refer to the same language? Also easy. - // TODO: Implement. Confidence = high. - - // Multiple languages, but all but one of them are also in the analysis list? Also easy. - // TODO: Implement. Confidence = high. - - // Multiple languages, but we couldn't narrow it down using the analysis list? See if any are in the project code. - // TODO: Implement. Split project code by hyphens, then normalize 2-to-3 so `en` would become `eng`. - // Then if the first vernacular-only language is present in some segment of the project code, return that. Confidence = medium. - - // If all else fails, guess the first vernacular tag with low confidence - return new LanguageGuess(langTags.CurVernWss[0], GuessConfidence.Low); - } - public record ProjectLangTags(string[] VernWss, string[] AnalysisWss, string[] CurVernWss, string[] CurAnalysisWss); public async Task VernacularAndAnalysisLangTags(ProjectCode code) { From ea91cfd036da5783f85feea36f1cac1ca3565ea6 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 12 Jul 2024 10:22:53 +0700 Subject: [PATCH 04/26] Add new DB columns for FLEx writing systems Migration will be in the next commit. --- backend/LexCore/Entities/FlexProjectMetadata.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/LexCore/Entities/FlexProjectMetadata.cs b/backend/LexCore/Entities/FlexProjectMetadata.cs index e957bd827..5d3252fc7 100644 --- a/backend/LexCore/Entities/FlexProjectMetadata.cs +++ b/backend/LexCore/Entities/FlexProjectMetadata.cs @@ -5,4 +5,17 @@ public class FlexProjectMetadata { public Guid ProjectId { get; set; } public int? LexEntryCount { get; set; } + public ProjectWritingSystems? WritingSystems { get; set; } +} + +public class ProjectWritingSystems +{ + public required FLExWsId[] VernacularWss { get; set; } + public required FLExWsId[] AnalysisWss { get; set; } +} + +public class FLExWsId +{ + public required string Tag { get; set; } + public bool IsActive { get; set; } } From 653435357fa9691567e6afa4dd4b45361877c866 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 12 Jul 2024 13:19:16 +0700 Subject: [PATCH 05/26] Attempt to configure EF for WritingSystems --- backend/LexCore/Entities/FlexProjectMetadata.cs | 4 ++-- .../Entities/FlexProjectMetadataEntityConfiguration.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/LexCore/Entities/FlexProjectMetadata.cs b/backend/LexCore/Entities/FlexProjectMetadata.cs index 5d3252fc7..15a4f0c5b 100644 --- a/backend/LexCore/Entities/FlexProjectMetadata.cs +++ b/backend/LexCore/Entities/FlexProjectMetadata.cs @@ -10,8 +10,8 @@ public class FlexProjectMetadata public class ProjectWritingSystems { - public required FLExWsId[] VernacularWss { get; set; } - public required FLExWsId[] AnalysisWss { get; set; } + public required List VernacularWss { get; set; } = []; + public required List AnalysisWss { get; set; } = []; } public class FLExWsId diff --git a/backend/LexData/Entities/FlexProjectMetadataEntityConfiguration.cs b/backend/LexData/Entities/FlexProjectMetadataEntityConfiguration.cs index 882d3ff36..29be1a356 100644 --- a/backend/LexData/Entities/FlexProjectMetadataEntityConfiguration.cs +++ b/backend/LexData/Entities/FlexProjectMetadataEntityConfiguration.cs @@ -9,5 +9,11 @@ public class FlexProjectMetadataEntityConfiguration: IEntityTypeConfiguration builder) { builder.HasKey(e => e.ProjectId); + builder.OwnsOne(e => e.WritingSystems, wsb => + { + wsb.ToJson(); + wsb.OwnsMany(e => e.AnalysisWss); + wsb.OwnsMany(e => e.VernacularWss); + }); } } From 2fab0b2b0ff28ee770ee4a5fb07045814427d012 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 12 Jul 2024 15:01:45 +0700 Subject: [PATCH 06/26] Better return type for ws-parsing method VernacularAndAnalysisLangTags now returns two lists of FLExWsId objects with the IsActive properties set, rather than four arrays of strings. --- backend/LexBoxApi/Services/ProjectService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index a9822fc75..0d788929f 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -155,7 +155,7 @@ public async Task GetLangTagsAsXml(ProjectCode code) // May become priva return $"{xmlBody}"; } - public record ProjectLangTags(string[] VernWss, string[] AnalysisWss, string[] CurVernWss, string[] CurAnalysisWss); + public record ProjectLangTags(FLExWsId[] VernWss, FLExWsId[] AnalysisWss); public async Task VernacularAndAnalysisLangTags(ProjectCode code) { var langTagsXml = await GetLangTagsAsXml(code); @@ -170,9 +170,11 @@ public record ProjectLangTags(string[] VernWss, string[] AnalysisWss, string[] C var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - return new ProjectLangTags(vernWss, analysisWss, curVernWss, curAnalysisWss); + var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); + var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); + var vernWsIds = vernWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curVernWss.Contains(tag) }).ToArray(); + var analysisWsIds = analysisWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curAnalysisWss.Contains(tag) }).ToArray(); + return new ProjectLangTags(vernWsIds, analysisWsIds); } public async ValueTask LookupProjectOrgIds(Guid projectId) From 49b954f468c6805c4e9ddbae83e4c353f50660dd Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 12 Jul 2024 15:33:36 +0700 Subject: [PATCH 07/26] add debug logging to testing services --- backend/Testing/Fixtures/TestingServicesFixture.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/Testing/Fixtures/TestingServicesFixture.cs b/backend/Testing/Fixtures/TestingServicesFixture.cs index df2a4e5d2..537cb22bf 100644 --- a/backend/Testing/Fixtures/TestingServicesFixture.cs +++ b/backend/Testing/Fixtures/TestingServicesFixture.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Internal; +using Microsoft.Extensions.Logging; namespace Testing.Fixtures; @@ -35,6 +36,7 @@ private static void ConfigureBaseServices(IServiceCollection services) }); services.AddSingleton(new ConfigurationManager()); services.AddLexData(true, dbContextLifeTime: ServiceLifetime.Singleton); + services.AddLogging(builder => builder.AddDebug()); } public ServiceProvider ConfigureServices(Action? configureServices = null) From eb90a00056ea0f754ce32b2fcc300dc883b679ac Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 12 Jul 2024 15:34:17 +0700 Subject: [PATCH 08/26] refactor xml parsing into HgService, add UpdateProjectLangTags to ProjectService --- backend/LexBoxApi/Services/HgService.cs | 36 +++++++++++++- backend/LexBoxApi/Services/ProjectService.cs | 48 +++++-------------- .../LexCore/ServiceInterfaces/IHgService.cs | 2 +- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index a691fe370..50babde38 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -160,9 +160,41 @@ await Task.Run(() => }); } - public Task GetWsTagsFromFlexProject(ProjectCode code, CancellationToken token = default) + /// + /// Returns either an empty string, or XML (in string form) with a root LangTags element containing five child elements: AnalysisWss, CurAnalysisWss, VernWss, CurVernWss, and CurPronunWss. + /// Each child element will contain a single `` element whose text content is a list of tags separated by spaces. + /// + private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken token = default) { - return ExecuteHgCommandServerCommand(code, "flexwritingsystems", token); + var result = await ExecuteHgCommandServerCommand(code, "flexwritingsystems", token); + var xmlBody = await result.ReadAsStringAsync(token); + if (string.IsNullOrEmpty(xmlBody)) return string.Empty; + return $"{xmlBody}"; + } + + public async Task GetProjectWritingSystems(ProjectCode code, CancellationToken token = default) + { + var langTagsXml = await GetLangTagsAsXml(code, token); + if (string.IsNullOrEmpty(langTagsXml)) return null; + var doc = new System.Xml.XmlDocument(); + doc.LoadXml(langTagsXml); + var root = doc.DocumentElement; + if (root is null) return null; + var vernWssStr = root["VernVss"]?["Uni"]?.InnerText ?? ""; + var analysisWssStr = root["AnalysisWss"]?["Uni"]?.InnerText ?? ""; + var curVernWssStr = root["CurVernVss"]?["Uni"]?.InnerText ?? ""; + var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; + var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); + var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); + var vernWsIds = vernWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curVernWss.Contains(tag) }).ToList(); + var analysisWsIds = analysisWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curAnalysisWss.Contains(tag) }).ToList(); + return new ProjectWritingSystems + { + VernacularWss = vernWsIds, + AnalysisWss = analysisWsIds + }; } public Task RevertRepo(ProjectCode code, string revHash) diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index 0d788929f..8e2722221 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -58,6 +58,18 @@ public async Task CreateProject(CreateProjectInput input) return projectId; } + public async Task UpdateProjectLangTags(Guid projectId) + { + var project = await dbContext.Projects.FindAsync(projectId); + if (project is null || project.Type != ProjectType.FLEx) return; + await dbContext.Entry(project).Reference(p => p.FlexProjectMetadata).LoadAsync(); + var langTags = await hgService.GetProjectWritingSystems(project.Code); + if (langTags is null) return; + project.FlexProjectMetadata ??= new FlexProjectMetadata(); + project.FlexProjectMetadata.WritingSystems = langTags; + await dbContext.SaveChangesAsync(); + } + public async Task CreateDraftProject(CreateProjectInput input) { // No need for a transaction if we're just saving a single item @@ -141,42 +153,6 @@ public async Task FinishReset(string code, Stream? zipFile = null) await dbContext.SaveChangesAsync(); } - /// - /// Returns either an empty string, or XML (in string form) with a root LangTags element containing five child elements: AnalysisWss, CurAnalysisWss, VernWss, CurVernWss, and CurPronunWss. - /// Each child element will contain a single `` element whose text content is a list of tags separated by spaces. - /// - /// - /// - public async Task GetLangTagsAsXml(ProjectCode code) // May become private eventually - { - var result = await hgService.GetWsTagsFromFlexProject(code); - var xmlBody = await result.ReadAsStringAsync(); - if (string.IsNullOrEmpty(xmlBody)) return string.Empty; - return $"{xmlBody}"; - } - - public record ProjectLangTags(FLExWsId[] VernWss, FLExWsId[] AnalysisWss); - public async Task VernacularAndAnalysisLangTags(ProjectCode code) - { - var langTagsXml = await GetLangTagsAsXml(code); - if (string.IsNullOrEmpty(langTagsXml)) return null; - var doc = new System.Xml.XmlDocument(); - doc.LoadXml(langTagsXml); - var root = doc.DocumentElement; - if (root is null) return null; - var vernWssStr = root["VernVss"]?["Uni"]?.InnerText ?? ""; - var analysisWssStr = root["AnalysisWss"]?["Uni"]?.InnerText ?? ""; - var curVernWssStr = root["CurVernVss"]?["Uni"]?.InnerText ?? ""; - var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; - var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); - var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); - var vernWsIds = vernWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curVernWss.Contains(tag) }).ToArray(); - var analysisWsIds = analysisWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curAnalysisWss.Contains(tag) }).ToArray(); - return new ProjectLangTags(vernWsIds, analysisWsIds); - } - public async ValueTask LookupProjectOrgIds(Guid projectId) { var cacheKey = $"ProjectOrgsForId:{projectId}"; diff --git a/backend/LexCore/ServiceInterfaces/IHgService.cs b/backend/LexCore/ServiceInterfaces/IHgService.cs index f3643d43d..71f282213 100644 --- a/backend/LexCore/ServiceInterfaces/IHgService.cs +++ b/backend/LexCore/ServiceInterfaces/IHgService.cs @@ -11,7 +11,7 @@ public interface IHgService Task DetermineProjectType(ProjectCode projectCode); Task DeleteRepo(ProjectCode code); Task SoftDeleteRepo(ProjectCode code, string deletedRepoSuffix); - Task GetWsTagsFromFlexProject(ProjectCode code, CancellationToken token = default); + Task GetProjectWritingSystems(ProjectCode code, CancellationToken token = default); BackupExecutor? BackupRepo(ProjectCode code); Task ResetRepo(ProjectCode code); Task FinishReset(ProjectCode code, Stream zipFile); From 8306ea055f11c67a129db91bf44314fc918f6908 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 12 Jul 2024 15:34:38 +0700 Subject: [PATCH 09/26] create migration to add writing systems to flex project metadata --- ...1_AddProjectLangTagsToMetadata.Designer.cs | 1390 +++++++++++++++++ ...0712083211_AddProjectLangTagsToMetadata.cs | 28 + .../LexBoxDbContextModelSnapshot.cs | 69 + 3 files changed, 1487 insertions(+) create mode 100644 backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.Designer.cs create mode 100644 backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.cs diff --git a/backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.Designer.cs b/backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.Designer.cs new file mode 100644 index 000000000..5a72a2979 --- /dev/null +++ b/backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.Designer.cs @@ -0,0 +1,1390 @@ +// +using System; +using System.Collections.Generic; +using LexData; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LexData.Migrations +{ + [DbContext(typeof(LexBoxDbContext))] + [Migration("20240712083211_AddProjectLangTagsToMetadata")] + partial class AddProjectLangTagsToMetadata + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False") + .HasAnnotation("ProductVersion", "8.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", "quartz"); + }); + + modelBuilder.Entity("Crdt.Core.ServerCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.ComplexProperty>("HybridDateTime", "Crdt.Core.ServerCommit.HybridDateTime#HybridDateTime", b1 => + { + b1.IsRequired(); + + b1.Property("Counter") + .HasColumnType("bigint"); + + b1.Property("DateTime") + .HasColumnType("timestamp with time zone"); + }); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("CrdtCommits", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectManagerId") + .HasColumnType("uuid"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProjectManagerId"); + + b.ToTable("DraftProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("LexEntryCount") + .HasColumnType("integer"); + + b.HasKey("ProjectId"); + + b.ToTable("FlexProjectMetadata"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrgId"); + + b.HasIndex("UserId", "OrgId") + .IsUnique(); + + b.ToTable("OrgMembers", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("OrgId", "ProjectId") + .IsUnique(); + + b.ToTable("OrgProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Orgs", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("LastCommit") + .HasColumnType("timestamp with time zone"); + + b.Property("MigratedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("ProjectOrigin") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("ResetStatus") + .HasColumnType("integer"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ParentId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId", "ProjectId") + .IsUnique(); + + b.ToTable("ProjectUsers"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CanCreateProjects") + .HasColumnType("boolean"); + + b.Property("CreatedById") + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Email") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("GoogleId") + .HasColumnType("text"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalizationCode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("en"); + + b.Property("Locked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordStrength") + .HasColumnType("integer"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Username") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("Crdt.Core.ServerCommit", b => + { + b.HasOne("LexCore.Entities.FlexProjectMetadata", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Crdt.Core.ChangeEntity", "ChangeEntities", b1 => + { + b1.Property("ServerCommitId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Change") + .HasColumnType("text"); + + b1.Property("CommitId") + .HasColumnType("uuid"); + + b1.Property("EntityId") + .HasColumnType("uuid"); + + b1.Property("Index") + .HasColumnType("integer"); + + b1.HasKey("ServerCommitId", "Id"); + + b1.ToTable("CrdtCommits"); + + b1.ToJson("ChangeEntities"); + + b1.WithOwner() + .HasForeignKey("ServerCommitId"); + }); + + b.Navigation("ChangeEntities"); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.HasOne("LexCore.Entities.User", "ProjectManager") + .WithMany() + .HasForeignKey("ProjectManagerId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ProjectManager"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithOne("FlexProjectMetadata") + .HasForeignKey("LexCore.Entities.FlexProjectMetadata", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("LexCore.Entities.ProjectWritingSystems", "WritingSystems", b1 => + { + b1.Property("FlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b1.HasKey("FlexProjectMetadataProjectId"); + + b1.ToTable("FlexProjectMetadata"); + + b1.ToJson("WritingSystems"); + + b1.WithOwner() + .HasForeignKey("FlexProjectMetadataProjectId"); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "AnalysisWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "VernacularWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.Navigation("AnalysisWss"); + + b1.Navigation("VernacularWss"); + }); + + b.Navigation("WritingSystems"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.HasOne("LexCore.Entities.Organization", "Organization") + .WithMany("Members") + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Organizations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.HasOne("LexCore.Entities.Organization", "Org") + .WithMany() + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Org"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany("Users") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.HasOne("LexCore.Entities.User", "CreatedBy") + .WithMany("UsersICreated") + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Navigation("FlexProjectMetadata"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Navigation("Organizations"); + + b.Navigation("Projects"); + + b.Navigation("UsersICreated"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.cs b/backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.cs new file mode 100644 index 000000000..04a0952a2 --- /dev/null +++ b/backend/LexData/Migrations/20240712083211_AddProjectLangTagsToMetadata.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LexData.Migrations +{ + /// + public partial class AddProjectLangTagsToMetadata : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "WritingSystems", + table: "FlexProjectMetadata", + type: "jsonb", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "WritingSystems", + table: "FlexProjectMetadata"); + } + } +} diff --git a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs index bea482824..eb8688303 100644 --- a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs +++ b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs @@ -1164,6 +1164,75 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("LexCore.Entities.FlexProjectMetadata", "ProjectId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.OwnsOne("LexCore.Entities.ProjectWritingSystems", "WritingSystems", b1 => + { + b1.Property("FlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b1.HasKey("FlexProjectMetadataProjectId"); + + b1.ToTable("FlexProjectMetadata"); + + b1.ToJson("WritingSystems"); + + b1.WithOwner() + .HasForeignKey("FlexProjectMetadataProjectId"); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "AnalysisWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "VernacularWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.Navigation("AnalysisWss"); + + b1.Navigation("VernacularWss"); + }); + + b.Navigation("WritingSystems"); }); modelBuilder.Entity("LexCore.Entities.OrgMember", b => From dd2ffa7c42e2a34aa02fdd170b2c5158ca4e7911 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 12 Jul 2024 15:34:59 +0700 Subject: [PATCH 10/26] write test for writing language tags into flex project metadata --- .../LexCore/Services/ProjectServiceTest.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/Testing/LexCore/Services/ProjectServiceTest.cs b/backend/Testing/LexCore/Services/ProjectServiceTest.cs index 49e785c49..60ba32362 100644 --- a/backend/Testing/LexCore/Services/ProjectServiceTest.cs +++ b/backend/Testing/LexCore/Services/ProjectServiceTest.cs @@ -2,6 +2,7 @@ using LexBoxApi.Services.Email; using LexCore.Entities; using LexCore.ServiceInterfaces; +using LexData; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -16,17 +17,33 @@ namespace Testing.LexCore.Services; public class ProjectServiceTest { private readonly ProjectService _projectService; + private readonly ProjectWritingSystems _writingSystems = new() + { + VernacularWss = new List() + { + new() { Tag = "en", IsActive = true }, + new() { Tag = "fr", IsActive = false } + }, + AnalysisWss = new List() + { + new() { Tag = "en", IsActive = true }, + new() { Tag = "fr", IsActive = false } + } + }; + + private LexBoxDbContext _lexBoxDbContext; public ProjectServiceTest(TestingServicesFixture testing) { var serviceProvider = testing.ConfigureServices(s => { - s.AddScoped(_ => Mock.Of()); + s.AddScoped(_ => Mock.Of(service => service.GetProjectWritingSystems(It.IsAny(), It.IsAny()) == Task.FromResult(_writingSystems))); s.AddScoped(_ => Mock.Of()); s.AddSingleton(_ => Mock.Of()); s.AddScoped(); }); _projectService = serviceProvider.GetRequiredService(); + _lexBoxDbContext = serviceProvider.GetRequiredService(); } [Fact] @@ -37,6 +54,17 @@ public async Task CanCreateProject() projectId.ShouldNotBe(default); } + [Fact] + public async Task CanUpdateProjectLangTags() + { + var projectId = await _projectService.CreateProject( + new(null, "TestProject", "Test", "test", ProjectType.FLEx, RetentionPolicy.Test, false, null, null)); + await _projectService.UpdateProjectLangTags(projectId); + var project = await _lexBoxDbContext.Projects.Include(p => p.FlexProjectMetadata).SingleAsync(p => p.Id == projectId); + project.FlexProjectMetadata.ShouldNotBeNull(); + project.FlexProjectMetadata.WritingSystems.ShouldBeEquivalentTo(_writingSystems); + } + [Fact] public async Task ShouldErrorIfCreatingAProjectWithTheSameCode() { From c8e7f4d0641cc1b792205665d666efd1449a773d Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 10:16:43 +0700 Subject: [PATCH 11/26] Add IsDefault field to ws tags in project data --- .../FlexProjectMetadataGqlConfiguration.cs | 12 + backend/LexBoxApi/Services/HgService.cs | 4 +- .../LexCore/Entities/FlexProjectMetadata.cs | 1 + ...54_AddIsDefaultFieldToFLExWsId.Designer.cs | 1396 +++++++++++++++++ ...40715044054_AddIsDefaultFieldToFLExWsId.cs | 22 + .../LexBoxDbContextModelSnapshot.cs | 6 + .../LexCore/Services/ProjectServiceTest.cs | 8 +- frontend/schema.graphql | 35 + 8 files changed, 1478 insertions(+), 6 deletions(-) create mode 100644 backend/LexBoxApi/GraphQL/CustomTypes/FlexProjectMetadataGqlConfiguration.cs create mode 100644 backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs create mode 100644 backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs diff --git a/backend/LexBoxApi/GraphQL/CustomTypes/FlexProjectMetadataGqlConfiguration.cs b/backend/LexBoxApi/GraphQL/CustomTypes/FlexProjectMetadataGqlConfiguration.cs new file mode 100644 index 000000000..3f7b7dec4 --- /dev/null +++ b/backend/LexBoxApi/GraphQL/CustomTypes/FlexProjectMetadataGqlConfiguration.cs @@ -0,0 +1,12 @@ +using HotChocolate.Data.Sorting; +using LexCore.Entities; + +namespace LexBoxApi.GraphQL.CustomTypes; + +public class FlexProjectMetadataGqlSortConfiguration : SortInputType +{ + protected override void Configure(ISortInputTypeDescriptor descriptor) + { + descriptor.Field(p => p.WritingSystems).Ignore(); + } +} diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index 50babde38..fd2ecd044 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -188,8 +188,8 @@ private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); - var vernWsIds = vernWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curVernWss.Contains(tag) }).ToList(); - var analysisWsIds = analysisWss.Select(tag => new FLExWsId { Tag = tag, IsActive = curAnalysisWss.Contains(tag) }).ToList(); + var vernWsIds = vernWss.Select((tag, idx) => new FLExWsId { Tag = tag, IsActive = curVernWss.Contains(tag), IsDefault = idx == 0 }).ToList(); + var analysisWsIds = analysisWss.Select((tag, idx) => new FLExWsId { Tag = tag, IsActive = curAnalysisWss.Contains(tag), IsDefault = idx == 0 }).ToList(); return new ProjectWritingSystems { VernacularWss = vernWsIds, diff --git a/backend/LexCore/Entities/FlexProjectMetadata.cs b/backend/LexCore/Entities/FlexProjectMetadata.cs index 15a4f0c5b..de86c635a 100644 --- a/backend/LexCore/Entities/FlexProjectMetadata.cs +++ b/backend/LexCore/Entities/FlexProjectMetadata.cs @@ -18,4 +18,5 @@ public class FLExWsId { public required string Tag { get; set; } public bool IsActive { get; set; } + public bool IsDefault { get; set; } } diff --git a/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs b/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs new file mode 100644 index 000000000..eb06a50c9 --- /dev/null +++ b/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs @@ -0,0 +1,1396 @@ +// +using System; +using System.Collections.Generic; +using LexData; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LexData.Migrations +{ + [DbContext(typeof(LexBoxDbContext))] + [Migration("20240715044054_AddIsDefaultFieldToFLExWsId")] + partial class AddIsDefaultFieldToFLExWsId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False") + .HasAnnotation("ProductVersion", "8.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", "quartz"); + }); + + modelBuilder.Entity("Crdt.Core.ServerCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.ComplexProperty>("HybridDateTime", "Crdt.Core.ServerCommit.HybridDateTime#HybridDateTime", b1 => + { + b1.IsRequired(); + + b1.Property("Counter") + .HasColumnType("bigint"); + + b1.Property("DateTime") + .HasColumnType("timestamp with time zone"); + }); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("CrdtCommits", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectManagerId") + .HasColumnType("uuid"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProjectManagerId"); + + b.ToTable("DraftProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("LexEntryCount") + .HasColumnType("integer"); + + b.HasKey("ProjectId"); + + b.ToTable("FlexProjectMetadata"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrgId"); + + b.HasIndex("UserId", "OrgId") + .IsUnique(); + + b.ToTable("OrgMembers", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("OrgId", "ProjectId") + .IsUnique(); + + b.ToTable("OrgProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Orgs", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("LastCommit") + .HasColumnType("timestamp with time zone"); + + b.Property("MigratedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("ProjectOrigin") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("ResetStatus") + .HasColumnType("integer"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ParentId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId", "ProjectId") + .IsUnique(); + + b.ToTable("ProjectUsers"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CanCreateProjects") + .HasColumnType("boolean"); + + b.Property("CreatedById") + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Email") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("GoogleId") + .HasColumnType("text"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalizationCode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("en"); + + b.Property("Locked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordStrength") + .HasColumnType("integer"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Username") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("Crdt.Core.ServerCommit", b => + { + b.HasOne("LexCore.Entities.FlexProjectMetadata", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Crdt.Core.ChangeEntity", "ChangeEntities", b1 => + { + b1.Property("ServerCommitId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Change") + .HasColumnType("text"); + + b1.Property("CommitId") + .HasColumnType("uuid"); + + b1.Property("EntityId") + .HasColumnType("uuid"); + + b1.Property("Index") + .HasColumnType("integer"); + + b1.HasKey("ServerCommitId", "Id"); + + b1.ToTable("CrdtCommits"); + + b1.ToJson("ChangeEntities"); + + b1.WithOwner() + .HasForeignKey("ServerCommitId"); + }); + + b.Navigation("ChangeEntities"); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.HasOne("LexCore.Entities.User", "ProjectManager") + .WithMany() + .HasForeignKey("ProjectManagerId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ProjectManager"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithOne("FlexProjectMetadata") + .HasForeignKey("LexCore.Entities.FlexProjectMetadata", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("LexCore.Entities.ProjectWritingSystems", "WritingSystems", b1 => + { + b1.Property("FlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b1.HasKey("FlexProjectMetadataProjectId"); + + b1.ToTable("FlexProjectMetadata"); + + b1.ToJson("WritingSystems"); + + b1.WithOwner() + .HasForeignKey("FlexProjectMetadataProjectId"); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "AnalysisWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("IsDefault") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "VernacularWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("IsDefault") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.Navigation("AnalysisWss"); + + b1.Navigation("VernacularWss"); + }); + + b.Navigation("WritingSystems"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.HasOne("LexCore.Entities.Organization", "Organization") + .WithMany("Members") + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Organizations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.HasOne("LexCore.Entities.Organization", "Org") + .WithMany() + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Org"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany("Users") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.HasOne("LexCore.Entities.User", "CreatedBy") + .WithMany("UsersICreated") + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Navigation("FlexProjectMetadata"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Navigation("Organizations"); + + b.Navigation("Projects"); + + b.Navigation("UsersICreated"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs b/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs new file mode 100644 index 000000000..b8eed9ad6 --- /dev/null +++ b/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LexData.Migrations +{ + /// + public partial class AddIsDefaultFieldToFLExWsId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs index eb8688303..a8bd1eca1 100644 --- a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs +++ b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs @@ -1191,6 +1191,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.Property("IsActive") .HasColumnType("boolean"); + b2.Property("IsDefault") + .HasColumnType("boolean"); + b2.Property("Tag") .IsRequired() .HasColumnType("text"); @@ -1215,6 +1218,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.Property("IsActive") .HasColumnType("boolean"); + b2.Property("IsDefault") + .HasColumnType("boolean"); + b2.Property("Tag") .IsRequired() .HasColumnType("text"); diff --git a/backend/Testing/LexCore/Services/ProjectServiceTest.cs b/backend/Testing/LexCore/Services/ProjectServiceTest.cs index 60ba32362..5b3943c86 100644 --- a/backend/Testing/LexCore/Services/ProjectServiceTest.cs +++ b/backend/Testing/LexCore/Services/ProjectServiceTest.cs @@ -21,13 +21,13 @@ public class ProjectServiceTest { VernacularWss = new List() { - new() { Tag = "en", IsActive = true }, - new() { Tag = "fr", IsActive = false } + new() { Tag = "en", IsActive = true, IsDefault = true }, + new() { Tag = "fr", IsActive = false, IsDefault = false } }, AnalysisWss = new List() { - new() { Tag = "en", IsActive = true }, - new() { Tag = "fr", IsActive = false } + new() { Tag = "en", IsActive = true, IsDefault = true }, + new() { Tag = "fr", IsActive = false, IsDefault = false } } }; diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 2f5792744..98023814a 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -152,9 +152,16 @@ type DraftProject { updatedDate: DateTime! } +type FLExWsId { + tag: String! + isActive: Boolean! + isDefault: Boolean! +} + type FlexProjectMetadata { projectId: UUID! lexEntryCount: Int + writingSystems: ProjectWritingSystems } type InvalidEmailError implements Error { @@ -366,6 +373,11 @@ type ProjectUsers { updatedDate: DateTime! } +type ProjectWritingSystems { + vernacularWss: [FLExWsId!]! + analysisWss: [FLExWsId!]! +} + type Query { myProjects(orderBy: [ProjectSortInput!]): [Project!]! projects(withDeleted: Boolean! = false where: ProjectFilterInput orderBy: [ProjectSortInput!]): [Project!]! @authorize(policy: "AdminRequiredPolicy") @@ -650,11 +662,20 @@ input DraftProjectSortInput { updatedDate: SortEnumType } +input FLExWsIdFilterInput { + and: [FLExWsIdFilterInput!] + or: [FLExWsIdFilterInput!] + tag: StringOperationFilterInput + isActive: BooleanOperationFilterInput + isDefault: BooleanOperationFilterInput +} + input FlexProjectMetadataFilterInput { and: [FlexProjectMetadataFilterInput!] or: [FlexProjectMetadataFilterInput!] projectId: UuidOperationFilterInput lexEntryCount: IntOperationFilterInput + writingSystems: ProjectWritingSystemsFilterInput } input FlexProjectMetadataSortInput { @@ -681,6 +702,13 @@ input LeaveProjectInput { projectId: UUID! } +input ListFilterInputTypeOfFLExWsIdFilterInput { + all: FLExWsIdFilterInput + none: FLExWsIdFilterInput + some: FLExWsIdFilterInput + any: Boolean +} + input ListFilterInputTypeOfOrgMemberFilterInput { all: OrgMemberFilterInput none: OrgMemberFilterInput @@ -836,6 +864,13 @@ input ProjectUsersFilterInput { updatedDate: DateTimeOperationFilterInput } +input ProjectWritingSystemsFilterInput { + and: [ProjectWritingSystemsFilterInput!] + or: [ProjectWritingSystemsFilterInput!] + vernacularWss: ListFilterInputTypeOfFLExWsIdFilterInput + analysisWss: ListFilterInputTypeOfFLExWsIdFilterInput +} + input RemoveProjectFromOrgInput { orgId: UUID! projectId: UUID! From ecbc444d9c8d744f07d7655651a69737df7b8fe2 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 12:41:29 +0700 Subject: [PATCH 12/26] Add command to extract LangProject GUID as well While we're adding hg runner commands, we can easily add one to extract the GUID from the LangProject element, which may allow identifying projects which started out as copies of each other even though their Mercurial repo histories have no common commits. --- backend/LexBoxApi/Services/HgService.cs | 8 ++++++++ backend/LexCore/ServiceInterfaces/IHgService.cs | 1 + hgweb/command-runner.sh | 10 +++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index fd2ecd044..6628b7b2a 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -197,6 +197,14 @@ private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken }; } + public async Task GetProjectIdOfFlexProject(ProjectCode code, CancellationToken token = default) + { + var result = await ExecuteHgCommandServerCommand(code, "flexprojectid", token); + var text = await result.ReadAsStringAsync(token); + if (Guid.TryParse(text, out var guid)) return guid; + return null; + } + public Task RevertRepo(ProjectCode code, string revHash) { throw new NotImplementedException(); diff --git a/backend/LexCore/ServiceInterfaces/IHgService.cs b/backend/LexCore/ServiceInterfaces/IHgService.cs index 71f282213..21b5b574d 100644 --- a/backend/LexCore/ServiceInterfaces/IHgService.cs +++ b/backend/LexCore/ServiceInterfaces/IHgService.cs @@ -12,6 +12,7 @@ public interface IHgService Task DeleteRepo(ProjectCode code); Task SoftDeleteRepo(ProjectCode code, string deletedRepoSuffix); Task GetProjectWritingSystems(ProjectCode code, CancellationToken token = default); + Task GetProjectIdOfFlexProject(ProjectCode code, CancellationToken token = default); BackupExecutor? BackupRepo(ProjectCode code); Task ResetRepo(ProjectCode code); Task FinishReset(ProjectCode code, Stream zipFile); diff --git a/hgweb/command-runner.sh b/hgweb/command-runner.sh index 0adada857..44a6fafb6 100644 --- a/hgweb/command-runner.sh +++ b/hgweb/command-runner.sh @@ -1,7 +1,7 @@ #!/bin/bash # Define the list of allowed commands -allowed_commands=("verify" "tip" "wesaylexentrycount" "lexentrycount" "flexwritingsystems" "recover" "healthz" "invalidatedircache") +allowed_commands=("verify" "tip" "wesaylexentrycount" "lexentrycount" "flexprojectid" "flexwritingsystems" "recover" "healthz" "invalidatedircache") # Get the project code and command name from the URL IFS='/' read -ra PATH_SEGMENTS <<< "$PATH_INFO" @@ -63,6 +63,14 @@ case $command_name in [ -n "${LIFTFILE}" ] && (chg cat -r tip "${LIFTFILE}" | grep -c '/p' | grep -oP 'guid="\K[^"]+' + # Which is slightly more verbose but ensures we're getting the guid from the LangProject element + ;; + flexwritingsystems) chg cat -r tip General/LanguageProject.langproj | sed -n -e '//,/<\/AnalysisWss>/p' -e '//,/<\/VernWss>/p' -e '//,/<\/CurAnalysisWss>/p' -e '//,/<\/CurVernWss>/p' -e '//,/<\/CurPronunWss>/p' ;; From 90d62a090219617b5b56acb27dc7eb3c518ac6d9 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 12:53:17 +0700 Subject: [PATCH 13/26] Add LangProjectId column to FlexProjectMetadata Also include a DB migration and GQL schema update --- .../LexCore/Entities/FlexProjectMetadata.cs | 4 + ...tIdColumnToFlexProjectMetadata.Designer.cs | 1399 +++++++++++++++++ ...angProjectIdColumnToFlexProjectMetadata.cs | 29 + .../LexBoxDbContextModelSnapshot.cs | 3 + frontend/schema.graphql | 3 + 5 files changed, 1438 insertions(+) create mode 100644 backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.Designer.cs create mode 100644 backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.cs diff --git a/backend/LexCore/Entities/FlexProjectMetadata.cs b/backend/LexCore/Entities/FlexProjectMetadata.cs index de86c635a..9c5ba9bb3 100644 --- a/backend/LexCore/Entities/FlexProjectMetadata.cs +++ b/backend/LexCore/Entities/FlexProjectMetadata.cs @@ -5,6 +5,10 @@ public class FlexProjectMetadata { public Guid ProjectId { get; set; } public int? LexEntryCount { get; set; } + /// + /// GUID from the LangProject element, which is not the same as the ID of the LexBox project + /// + public Guid? LangProjectId { get; set; } public ProjectWritingSystems? WritingSystems { get; set; } } diff --git a/backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.Designer.cs b/backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.Designer.cs new file mode 100644 index 000000000..994bb848a --- /dev/null +++ b/backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.Designer.cs @@ -0,0 +1,1399 @@ +// +using System; +using System.Collections.Generic; +using LexData; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LexData.Migrations +{ + [DbContext(typeof(LexBoxDbContext))] + [Migration("20240715054632_AddLangProjectIdColumnToFlexProjectMetadata")] + partial class AddLangProjectIdColumnToFlexProjectMetadata + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False") + .HasAnnotation("ProductVersion", "8.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", "quartz"); + }); + + modelBuilder.Entity("Crdt.Core.ServerCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.ComplexProperty>("HybridDateTime", "Crdt.Core.ServerCommit.HybridDateTime#HybridDateTime", b1 => + { + b1.IsRequired(); + + b1.Property("Counter") + .HasColumnType("bigint"); + + b1.Property("DateTime") + .HasColumnType("timestamp with time zone"); + }); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("CrdtCommits", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectManagerId") + .HasColumnType("uuid"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProjectManagerId"); + + b.ToTable("DraftProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("LangProjectId") + .HasColumnType("uuid"); + + b.Property("LexEntryCount") + .HasColumnType("integer"); + + b.HasKey("ProjectId"); + + b.ToTable("FlexProjectMetadata"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrgId"); + + b.HasIndex("UserId", "OrgId") + .IsUnique(); + + b.ToTable("OrgMembers", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("OrgId", "ProjectId") + .IsUnique(); + + b.ToTable("OrgProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Orgs", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("LastCommit") + .HasColumnType("timestamp with time zone"); + + b.Property("MigratedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("ProjectOrigin") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("ResetStatus") + .HasColumnType("integer"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ParentId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId", "ProjectId") + .IsUnique(); + + b.ToTable("ProjectUsers"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CanCreateProjects") + .HasColumnType("boolean"); + + b.Property("CreatedById") + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Email") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("GoogleId") + .HasColumnType("text"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalizationCode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("en"); + + b.Property("Locked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordStrength") + .HasColumnType("integer"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Username") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("Crdt.Core.ServerCommit", b => + { + b.HasOne("LexCore.Entities.FlexProjectMetadata", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Crdt.Core.ChangeEntity", "ChangeEntities", b1 => + { + b1.Property("ServerCommitId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Change") + .HasColumnType("text"); + + b1.Property("CommitId") + .HasColumnType("uuid"); + + b1.Property("EntityId") + .HasColumnType("uuid"); + + b1.Property("Index") + .HasColumnType("integer"); + + b1.HasKey("ServerCommitId", "Id"); + + b1.ToTable("CrdtCommits"); + + b1.ToJson("ChangeEntities"); + + b1.WithOwner() + .HasForeignKey("ServerCommitId"); + }); + + b.Navigation("ChangeEntities"); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.HasOne("LexCore.Entities.User", "ProjectManager") + .WithMany() + .HasForeignKey("ProjectManagerId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ProjectManager"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithOne("FlexProjectMetadata") + .HasForeignKey("LexCore.Entities.FlexProjectMetadata", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("LexCore.Entities.ProjectWritingSystems", "WritingSystems", b1 => + { + b1.Property("FlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b1.HasKey("FlexProjectMetadataProjectId"); + + b1.ToTable("FlexProjectMetadata"); + + b1.ToJson("WritingSystems"); + + b1.WithOwner() + .HasForeignKey("FlexProjectMetadataProjectId"); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "AnalysisWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("IsDefault") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "VernacularWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("IsDefault") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.Navigation("AnalysisWss"); + + b1.Navigation("VernacularWss"); + }); + + b.Navigation("WritingSystems"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.HasOne("LexCore.Entities.Organization", "Organization") + .WithMany("Members") + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Organizations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.HasOne("LexCore.Entities.Organization", "Org") + .WithMany() + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Org"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany("Users") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.HasOne("LexCore.Entities.User", "CreatedBy") + .WithMany("UsersICreated") + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Navigation("FlexProjectMetadata"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Navigation("Organizations"); + + b.Navigation("Projects"); + + b.Navigation("UsersICreated"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.cs b/backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.cs new file mode 100644 index 000000000..621a98bd5 --- /dev/null +++ b/backend/LexData/Migrations/20240715054632_AddLangProjectIdColumnToFlexProjectMetadata.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LexData.Migrations +{ + /// + public partial class AddLangProjectIdColumnToFlexProjectMetadata : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LangProjectId", + table: "FlexProjectMetadata", + type: "uuid", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LangProjectId", + table: "FlexProjectMetadata"); + } + } +} diff --git a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs index a8bd1eca1..36dacabbd 100644 --- a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs +++ b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs @@ -559,6 +559,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ProjectId") .HasColumnType("uuid"); + b.Property("LangProjectId") + .HasColumnType("uuid"); + b.Property("LexEntryCount") .HasColumnType("integer"); diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 98023814a..4c740407b 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -161,6 +161,7 @@ type FLExWsId { type FlexProjectMetadata { projectId: UUID! lexEntryCount: Int + langProjectId: UUID writingSystems: ProjectWritingSystems } @@ -675,12 +676,14 @@ input FlexProjectMetadataFilterInput { or: [FlexProjectMetadataFilterInput!] projectId: UuidOperationFilterInput lexEntryCount: IntOperationFilterInput + langProjectId: UuidOperationFilterInput writingSystems: ProjectWritingSystemsFilterInput } input FlexProjectMetadataSortInput { projectId: SortEnumType lexEntryCount: SortEnumType + langProjectId: SortEnumType } input IntOperationFilterInput { From 54a140a7b82ad91354f8c61aa7dd199e78a752ed Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 12:53:45 +0700 Subject: [PATCH 14/26] Explain grep regex in a comment Since we're using `\K`, a rarely-used feature, we should explain it. --- hgweb/command-runner.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hgweb/command-runner.sh b/hgweb/command-runner.sh index 44a6fafb6..3a4563040 100644 --- a/hgweb/command-runner.sh +++ b/hgweb/command-runner.sh @@ -64,11 +64,10 @@ case $command_name in ;; flexprojectid) - # Project ID is first GUID in file, so simplest way is to just grab the first GUID - # chg cat -r tip General/LanguageProject.langproj | grep -oP 'guid="\K[^"]+' | head -n 1 - # Alternate approach: + # grep -o extracts only what matches the pattern; -P turns on Perl-compatible regex, and \K is a Perl regex flag + # that means "consider the previous text a lookbehind assertion, and don't include it in the match." + # https://perldoc.perl.org/perlre#%5CK says the `\K` construct "may be significantly more efficient" than "standard" lookbehind assertions like `(?<=...)` chg cat -r tip General/LanguageProject.langproj | sed -n -e '//p' | grep -oP 'guid="\K[^"]+' - # Which is slightly more verbose but ensures we're getting the guid from the LangProject element ;; flexwritingsystems) From cb663b8cd3a6aad054c86750525fd77d66a722d8 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 15:03:18 +0700 Subject: [PATCH 15/26] Fix typo in language tag parsing The XML element is named VernWss, not VernVss. --- backend/LexBoxApi/Services/HgService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index 6628b7b2a..a3725a3a8 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -180,9 +180,9 @@ private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken doc.LoadXml(langTagsXml); var root = doc.DocumentElement; if (root is null) return null; - var vernWssStr = root["VernVss"]?["Uni"]?.InnerText ?? ""; + var vernWssStr = root["VernWss"]?["Uni"]?.InnerText ?? ""; var analysisWssStr = root["AnalysisWss"]?["Uni"]?.InnerText ?? ""; - var curVernWssStr = root["CurVernVss"]?["Uni"]?.InnerText ?? ""; + var curVernWssStr = root["CurVernWss"]?["Uni"]?.InnerText ?? ""; var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); From 6744abb42cee807e895000b3e5688cb546104fd7 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 15:18:16 +0700 Subject: [PATCH 16/26] Make writing system ordering match what FLEx does The order in which FLEx returns writing systems is current first, then all non-current (if any) at the end. We want what we store in the project metadata to match that ordering. --- backend/LexBoxApi/Services/HgService.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index a3725a3a8..b210bd87c 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -186,10 +186,15 @@ private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); - var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); - var vernWsIds = vernWss.Select((tag, idx) => new FLExWsId { Tag = tag, IsActive = curVernWss.Contains(tag), IsDefault = idx == 0 }).ToList(); - var analysisWsIds = analysisWss.Select((tag, idx) => new FLExWsId { Tag = tag, IsActive = curAnalysisWss.Contains(tag), IsDefault = idx == 0 }).ToList(); + var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var curVernSet = curVernWss.ToHashSet(); + var curAnalysisSet = curAnalysisWss.ToHashSet(); + // Ordering is important here to match how FLEx handles things: all *current* writing systems first, then all *non-current*. + var vernWsIds = curVernWss.Select((tag, idx) => new FLExWsId { Tag = tag, IsActive = true, IsDefault = idx == 0 }).ToList(); + var analysisWsIds = curAnalysisWss.Select((tag, idx) => new FLExWsId { Tag = tag, IsActive = true, IsDefault = idx == 0 }).ToList(); + vernWsIds.AddRange(vernWss.Where(ws => !curVernSet.Contains(ws)).Select(tag => new FLExWsId { Tag = tag, IsActive = false, IsDefault = false })); + analysisWsIds.AddRange(analysisWss.Where(ws => !curAnalysisSet.Contains(ws)).Select(tag => new FLExWsId { Tag = tag, IsActive = false, IsDefault = false })); return new ProjectWritingSystems { VernacularWss = vernWsIds, From ef228fd49d8e3f811e216fac18bbc0cfcac5caef Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 15:22:19 +0700 Subject: [PATCH 17/26] Add VERY basic UI for writing systems on proj page --- .../Controllers/ProjectController.cs | 10 ++++++ backend/LexBoxApi/Services/ProjectService.cs | 11 +++++++ .../project/[project_code]/+page.svelte | 32 +++++++++++++++++++ .../project/[project_code]/+page.ts | 18 +++++++++-- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/backend/LexBoxApi/Controllers/ProjectController.cs b/backend/LexBoxApi/Controllers/ProjectController.cs index deedcfb5b..77e217b09 100644 --- a/backend/LexBoxApi/Controllers/ProjectController.cs +++ b/backend/LexBoxApi/Controllers/ProjectController.cs @@ -232,6 +232,16 @@ public async Task> UpdateLexEntryCount(string code) return result is null ? NotFound() : result; } + [HttpPost("updateLanguageList/{code}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task UpdateLanguageList(string code) + { + var projectId = await projectService.LookupProjectId(code); + await projectService.UpdateProjectLangTags(projectId); + } + [HttpPost("queueUpdateProjectMetadataTask")] public async Task QueueUpdateProjectMetadataTask(string projectCode) { diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index 8e2722221..363db5641 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -70,6 +70,17 @@ public async Task UpdateProjectLangTags(Guid projectId) await dbContext.SaveChangesAsync(); } + public async Task UpdateProjectLangProjectId(Guid projectId) + { + var project = await dbContext.Projects.FindAsync(projectId); + if (project is null || project.Type != ProjectType.FLEx) return; + await dbContext.Entry(project).Reference(p => p.FlexProjectMetadata).LoadAsync(); + var langProjGuid = await hgService.GetProjectIdOfFlexProject(project.Code); + project.FlexProjectMetadata ??= new FlexProjectMetadata(); + project.FlexProjectMetadata.LangProjectId = langProjGuid; + await dbContext.SaveChangesAsync(); + } + public async Task CreateDraftProject(CreateProjectInput input) { // No need for a transaction if we're just saving a single item diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte index 13474298d..f7822754b 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte @@ -64,6 +64,8 @@ let lexEntryCount: number | string | null | undefined = undefined; $: lexEntryCount = project.flexProjectMetadata?.lexEntryCount; + $: vernacularLangTags = project.flexProjectMetadata?.writingSystems?.vernacularWss; + $: analysisLangTags = project.flexProjectMetadata?.writingSystems?.analysisWss; const { notifySuccess, notifyWarning } = useNotifications(); @@ -77,6 +79,10 @@ loadingEntryCount = false; } + async function updateLanguageList(): Promise { + await fetch(`/api/project/updateLanguageList/${project.code}`, {method: 'POST'}); + } + let resetProjectModal: ResetProjectModal; async function resetProject(): Promise { await resetProjectModal.open(project.code, project.resetStatus); @@ -332,6 +338,32 @@ {/if} + {#if project.type === ProjectType.FlEx} + tag.tag)}> + + + + + tag.tag)}> + + + + + {/if}
Date: Mon, 15 Jul 2024 15:24:51 +0700 Subject: [PATCH 18/26] Translate label for writing system list --- frontend/src/lib/i18n/locales/en.json | 2 ++ .../(authenticated)/project/[project_code]/+page.svelte | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index 4461a502a..652ac492c 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -387,6 +387,8 @@ the [Linguistics Institute at Payap University](https://li.payap.ac.th/) in Chia "description": "Description", "created_at": "Created", "last_commit": "Last Commit", + "vernacular_langs": "Vernacular writing systems", + "analysis_langs": "Analysis writing systems", "organization": { "title": "Organizations", "placeholder": "No organization" diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte index f7822754b..49e4c6b52 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte @@ -339,7 +339,7 @@ {/if} {#if project.type === ProjectType.FlEx} - tag.tag)}> + tag.tag)}> - tag.tag)}> + tag.tag)}> Date: Mon, 15 Jul 2024 15:45:32 +0700 Subject: [PATCH 19/26] Improve UI for writing system list --- .../Projects/WritingSystemBadge.svelte | 16 +++++++++++ .../Projects/WritingSystemList.svelte | 12 +++++++++ frontend/src/lib/layout/DetailItem.svelte | 2 +- .../project/[project_code]/+page.svelte | 27 ++++--------------- 4 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 frontend/src/lib/components/Projects/WritingSystemBadge.svelte create mode 100644 frontend/src/lib/components/Projects/WritingSystemList.svelte diff --git a/frontend/src/lib/components/Projects/WritingSystemBadge.svelte b/frontend/src/lib/components/Projects/WritingSystemBadge.svelte new file mode 100644 index 000000000..01edca463 --- /dev/null +++ b/frontend/src/lib/components/Projects/WritingSystemBadge.svelte @@ -0,0 +1,16 @@ + + + + {tag} + diff --git a/frontend/src/lib/components/Projects/WritingSystemList.svelte b/frontend/src/lib/components/Projects/WritingSystemList.svelte new file mode 100644 index 000000000..c1de66b0c --- /dev/null +++ b/frontend/src/lib/components/Projects/WritingSystemList.svelte @@ -0,0 +1,12 @@ + + + + {#each writingSystems as ws} + + {/each} + diff --git a/frontend/src/lib/layout/DetailItem.svelte b/frontend/src/lib/layout/DetailItem.svelte index 290076d5b..07d89fb3a 100644 --- a/frontend/src/lib/layout/DetailItem.svelte +++ b/frontend/src/lib/layout/DetailItem.svelte @@ -7,7 +7,7 @@
- {title}: {text} + {title}: {#if text}{text}{/if} {#if copyToClipboard} {/if} diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte index 49e4c6b52..a413f1e31 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte @@ -45,6 +45,7 @@ import DetailsPage from '$lib/layout/DetailsPage.svelte'; import OrgList from './OrgList.svelte'; import AddOrganization from './AddOrganization.svelte'; + import WritingSystemList from '$lib/components/Projects/WritingSystemList.svelte'; export let data: PageData; $: user = data.user; @@ -339,29 +340,11 @@ {/if} {#if project.type === ProjectType.FlEx} - tag.tag)}> - - - + + - tag.tag)}> - - - + + {/if}
From 0f3b465e435c17251ccba3e30a880bf384fec659 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 15:55:34 +0700 Subject: [PATCH 20/26] Add button for admins to refresh language list --- frontend/src/lib/layout/DetailItem.svelte | 2 +- .../project/[project_code]/+page.svelte | 27 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/layout/DetailItem.svelte b/frontend/src/lib/layout/DetailItem.svelte index 07d89fb3a..d823008bb 100644 --- a/frontend/src/lib/layout/DetailItem.svelte +++ b/frontend/src/lib/layout/DetailItem.svelte @@ -7,7 +7,7 @@
- {title}: {#if text}{text}{/if} + {title}: {#if text}{text}{:else}{/if} {#if copyToClipboard} {/if} diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte index a413f1e31..7391ead09 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte @@ -25,7 +25,7 @@ import Dropdown from '$lib/components/Dropdown.svelte'; import ConfirmDeleteModal from '$lib/components/modals/ConfirmDeleteModal.svelte'; import {_deleteProject} from '$lib/gql/mutations'; - import { goto } from '$app/navigation'; + import { goto, invalidate } from '$app/navigation'; import MoreSettings from '$lib/components/MoreSettings.svelte'; import { AdminContent, PageBreadcrumb } from '$lib/layout'; import Markdown from 'svelte-exmarkdown'; @@ -82,6 +82,7 @@ async function updateLanguageList(): Promise { await fetch(`/api/project/updateLanguageList/${project.code}`, {method: 'POST'}); + await invalidate(`project:${project.code}`); } let resetProjectModal: ResetProjectModal; @@ -341,10 +342,30 @@ {/if} {#if project.type === ProjectType.FlEx} - + + + + - + + + + {/if}
From d272e5e5636e7dc4ca2150279291abfb0bcfe394 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 15 Jul 2024 16:13:00 +0700 Subject: [PATCH 21/26] Improve types for WritingSystemList component --- frontend/src/lib/components/Projects/WritingSystemList.svelte | 3 ++- frontend/src/lib/layout/DetailItem.svelte | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/Projects/WritingSystemList.svelte b/frontend/src/lib/components/Projects/WritingSystemList.svelte index c1de66b0c..cda69106d 100644 --- a/frontend/src/lib/components/Projects/WritingSystemList.svelte +++ b/frontend/src/lib/components/Projects/WritingSystemList.svelte @@ -1,8 +1,9 @@ diff --git a/frontend/src/lib/layout/DetailItem.svelte b/frontend/src/lib/layout/DetailItem.svelte index d823008bb..20f647e90 100644 --- a/frontend/src/lib/layout/DetailItem.svelte +++ b/frontend/src/lib/layout/DetailItem.svelte @@ -2,7 +2,7 @@ import CopyToClipboardButton from '$lib/components/CopyToClipboardButton.svelte'; export let title: string; - export let text: string | null | undefined; + export let text: string | null | undefined = undefined; export let copyToClipboard = false; From b356f7603ff8406d37d3c77424b47eef6c9d00ce Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 16 Jul 2024 14:35:22 +0700 Subject: [PATCH 22/26] add project controller actions to update missing langprojectid and writingsystems --- .../Controllers/ProjectController.cs | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/backend/LexBoxApi/Controllers/ProjectController.cs b/backend/LexBoxApi/Controllers/ProjectController.cs index 77e217b09..dd5e8d631 100644 --- a/backend/LexBoxApi/Controllers/ProjectController.cs +++ b/backend/LexBoxApi/Controllers/ProjectController.cs @@ -26,7 +26,6 @@ public class ProjectController( ISchedulerFactory scheduler) : ControllerBase { - [HttpPost("refreshProjectLastChanged")] public async Task RefreshProjectLastChanged(string projectCode) { @@ -55,6 +54,7 @@ public async Task UpdateAllRepoCommitDates(bool onlyUnknown) { project.LastCommit = await hgService.GetLastCommitTimeFromHg(project.Code); } + await lexBoxDbContext.SaveChangesAsync(); return Ok(); @@ -73,14 +73,18 @@ public async Task> UpdateProjectType(Guid id) project.Type = await hgService.DetermineProjectType(project.Code); await lexBoxDbContext.SaveChangesAsync(); } + return project; } [HttpPost("setProjectType")] [AdminRequired] - public async Task SetProjectType(string projectCode, ProjectType projectType, bool overrideKnown = false) + public async Task SetProjectType(string projectCode, + ProjectType projectType, + bool overrideKnown = false) { - await lexBoxDbContext.Projects.Where(p => p.Code == projectCode && (p.Type == ProjectType.Unknown || overrideKnown)) + await lexBoxDbContext.Projects + .Where(p => p.Code == projectCode && (p.Type == ProjectType.Unknown || overrideKnown)) .ExecuteUpdateAsync(u => u.SetProperty(p => p.Type, projectType)); return Ok(); } @@ -107,7 +111,9 @@ public async Task> DetermineProjectType(Guid id) [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [AdminRequired] - public async Task>> UpdateProjectTypesForUnknownProjects(int limit = 50, int offset = 0) + public async Task>> UpdateProjectTypesForUnknownProjects(int limit = + 50, + int offset = 0) { var projects = lexBoxDbContext.Projects .Where(p => p.Type == ProjectType.Unknown) @@ -121,6 +127,7 @@ public async Task>> UpdateProjectTy project.Type = await hgService.DetermineProjectType(project.Code); result.Add(project.Code, project.Type); } + await lexBoxDbContext.SaveChangesAsync(); return result; } @@ -176,6 +183,7 @@ public async Task> DeleteProject(Guid id) } public record HgCommandResponse(string Response); + [HttpGet("hgVerify/{code}")] [AdminRequired] [ProducesResponseType(StatusCodes.Status200OK)] @@ -191,6 +199,7 @@ public async Task HgVerify(string code) await Response.CompleteAsync(); return; } + var result = await hgService.VerifyRepo(code, HttpContext.RequestAborted); await StreamHttpResponse(result); } @@ -210,6 +219,7 @@ public async Task HgRecover(string code) await Response.CompleteAsync(); return; } + var result = await hgService.ExecuteHgRecover(code, HttpContext.RequestAborted); await StreamHttpResponse(result); } @@ -242,6 +252,48 @@ public async Task UpdateLanguageList(string code) await projectService.UpdateProjectLangTags(projectId); } + [HttpPost("updateMissingLanguageList")] + public async Task> UpdateMissingLanguageList(int limit = 10) + { + var projects = lexBoxDbContext.Projects + .Include(p => p.FlexProjectMetadata) + .Where(p => p.Type == ProjectType.FLEx && p.LastCommit != null && p.FlexProjectMetadata!.WritingSystems == null) + .Take(limit) + .AsAsyncEnumerable(); + var codes = new List(limit); + await foreach (var project in projects) + { + codes.Add(project.Code); + project.FlexProjectMetadata ??= new FlexProjectMetadata(); + project.FlexProjectMetadata.WritingSystems = await hgService.GetProjectWritingSystems(project.Code); + } + + await lexBoxDbContext.SaveChangesAsync(); + + return Ok(codes); + } + + [HttpPost("updateMissingLangProjectId")] + public async Task> UpdateMissingLangProjectId(int limit = 10) + { + var projects = lexBoxDbContext.Projects + .Include(p => p.FlexProjectMetadata) + .Where(p => p.Type == ProjectType.FLEx && p.LastCommit != null && p.FlexProjectMetadata!.LangProjectId == null) + .Take(limit) + .AsAsyncEnumerable(); + var codes = new List(limit); + await foreach (var project in projects) + { + codes.Add(project.Code); + project.FlexProjectMetadata ??= new FlexProjectMetadata(); + project.FlexProjectMetadata.LangProjectId = await hgService.GetProjectIdOfFlexProject(project.Code); + } + + await lexBoxDbContext.SaveChangesAsync(); + + return Ok(codes); + } + [HttpPost("queueUpdateProjectMetadataTask")] public async Task QueueUpdateProjectMetadataTask(string projectCode) { From 27699a05b090c5ac28ec4648175a9683d52cc1e0 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 18 Jul 2024 10:57:53 +0700 Subject: [PATCH 23/26] Use helper method to get lang tags from XML --- backend/LexBoxApi/Services/HgService.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index b210bd87c..d1cefaafb 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -172,6 +172,13 @@ private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken return $"{xmlBody}"; } + private string[] GetWsList(System.Xml.XmlElement root, string tagName) + { + var wsStr = root[tagName]?["Uni"]?.InnerText ?? ""; + // String.Split(null) splits on any whitespace, but needs a type cast so the compiler can tell which overload (char[] vs string[]) to use + return wsStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + } + public async Task GetProjectWritingSystems(ProjectCode code, CancellationToken token = default) { var langTagsXml = await GetLangTagsAsXml(code, token); @@ -180,14 +187,10 @@ private async Task GetLangTagsAsXml(ProjectCode code, CancellationToken doc.LoadXml(langTagsXml); var root = doc.DocumentElement; if (root is null) return null; - var vernWssStr = root["VernWss"]?["Uni"]?.InnerText ?? ""; - var analysisWssStr = root["AnalysisWss"]?["Uni"]?.InnerText ?? ""; - var curVernWssStr = root["CurVernWss"]?["Uni"]?.InnerText ?? ""; - var curAnalysisWssStr = root["CurAnalysisWss"]?["Uni"]?.InnerText ?? ""; - var vernWss = vernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var analysisWss = analysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var curVernWss = curVernWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); - var curAnalysisWss = curAnalysisWssStr.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var vernWss = GetWsList(root, "VernWss"); + var analysisWss = GetWsList(root, "AnalysisWss"); + var curVernWss = GetWsList(root, "CurVernWss"); + var curAnalysisWss = GetWsList(root, "CurAnalysisWss"); var curVernSet = curVernWss.ToHashSet(); var curAnalysisSet = curAnalysisWss.ToHashSet(); // Ordering is important here to match how FLEx handles things: all *current* writing systems first, then all *non-current*. From 3cbc6fda9ce07306ee3603f1c4a41cf9a367f926 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 18 Jul 2024 10:59:00 +0700 Subject: [PATCH 24/26] Remove migration that does nothing --- ...54_AddIsDefaultFieldToFLExWsId.Designer.cs | 1396 ----------------- ...40715044054_AddIsDefaultFieldToFLExWsId.cs | 22 - 2 files changed, 1418 deletions(-) delete mode 100644 backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs delete mode 100644 backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs diff --git a/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs b/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs deleted file mode 100644 index eb06a50c9..000000000 --- a/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.Designer.cs +++ /dev/null @@ -1,1396 +0,0 @@ -// -using System; -using System.Collections.Generic; -using LexData; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace LexData.Migrations -{ - [DbContext(typeof(LexBoxDbContext))] - [Migration("20240715044054_AddIsDefaultFieldToFLExWsId")] - partial class AddIsDefaultFieldToFLExWsId - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False") - .HasAnnotation("ProductVersion", "8.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("TriggerName") - .HasColumnType("text") - .HasColumnName("trigger_name"); - - b.Property("TriggerGroup") - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.Property("BlobData") - .HasColumnType("bytea") - .HasColumnName("blob_data"); - - b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); - - b.ToTable("qrtz_blob_triggers", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("CalendarName") - .HasColumnType("text") - .HasColumnName("calendar_name"); - - b.Property("Calendar") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("calendar"); - - b.HasKey("SchedulerName", "CalendarName"); - - b.ToTable("qrtz_calendars", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("TriggerName") - .HasColumnType("text") - .HasColumnName("trigger_name"); - - b.Property("TriggerGroup") - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.Property("CronExpression") - .IsRequired() - .HasColumnType("text") - .HasColumnName("cron_expression"); - - b.Property("TimeZoneId") - .HasColumnType("text") - .HasColumnName("time_zone_id"); - - b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); - - b.ToTable("qrtz_cron_triggers", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("EntryId") - .HasColumnType("text") - .HasColumnName("entry_id"); - - b.Property("FiredTime") - .HasColumnType("bigint") - .HasColumnName("fired_time"); - - b.Property("InstanceName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("instance_name"); - - b.Property("IsNonConcurrent") - .HasColumnType("bool") - .HasColumnName("is_nonconcurrent"); - - b.Property("JobGroup") - .HasColumnType("text") - .HasColumnName("job_group"); - - b.Property("JobName") - .HasColumnType("text") - .HasColumnName("job_name"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("RequestsRecovery") - .HasColumnType("bool") - .HasColumnName("requests_recovery"); - - b.Property("ScheduledTime") - .HasColumnType("bigint") - .HasColumnName("sched_time"); - - b.Property("State") - .IsRequired() - .HasColumnType("text") - .HasColumnName("state"); - - b.Property("TriggerGroup") - .IsRequired() - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.Property("TriggerName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("trigger_name"); - - b.HasKey("SchedulerName", "EntryId"); - - b.HasIndex("InstanceName") - .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); - - b.HasIndex("JobGroup") - .HasDatabaseName("idx_qrtz_ft_job_group"); - - b.HasIndex("JobName") - .HasDatabaseName("idx_qrtz_ft_job_name"); - - b.HasIndex("RequestsRecovery") - .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); - - b.HasIndex("TriggerGroup") - .HasDatabaseName("idx_qrtz_ft_trig_group"); - - b.HasIndex("TriggerName") - .HasDatabaseName("idx_qrtz_ft_trig_name"); - - b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") - .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); - - b.ToTable("qrtz_fired_triggers", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("JobName") - .HasColumnType("text") - .HasColumnName("job_name"); - - b.Property("JobGroup") - .HasColumnType("text") - .HasColumnName("job_group"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsDurable") - .HasColumnType("bool") - .HasColumnName("is_durable"); - - b.Property("IsNonConcurrent") - .HasColumnType("bool") - .HasColumnName("is_nonconcurrent"); - - b.Property("IsUpdateData") - .HasColumnType("bool") - .HasColumnName("is_update_data"); - - b.Property("JobClassName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("job_class_name"); - - b.Property("JobData") - .HasColumnType("bytea") - .HasColumnName("job_data"); - - b.Property("RequestsRecovery") - .HasColumnType("bool") - .HasColumnName("requests_recovery"); - - b.HasKey("SchedulerName", "JobName", "JobGroup"); - - b.HasIndex("RequestsRecovery") - .HasDatabaseName("idx_qrtz_j_req_recovery"); - - b.ToTable("qrtz_job_details", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("LockName") - .HasColumnType("text") - .HasColumnName("lock_name"); - - b.HasKey("SchedulerName", "LockName"); - - b.ToTable("qrtz_locks", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("TriggerGroup") - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.HasKey("SchedulerName", "TriggerGroup"); - - b.ToTable("qrtz_paused_trigger_grps", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("InstanceName") - .HasColumnType("text") - .HasColumnName("instance_name"); - - b.Property("CheckInInterval") - .HasColumnType("bigint") - .HasColumnName("checkin_interval"); - - b.Property("LastCheckInTime") - .HasColumnType("bigint") - .HasColumnName("last_checkin_time"); - - b.HasKey("SchedulerName", "InstanceName"); - - b.ToTable("qrtz_scheduler_state", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("TriggerName") - .HasColumnType("text") - .HasColumnName("trigger_name"); - - b.Property("TriggerGroup") - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.Property("BooleanProperty1") - .HasColumnType("bool") - .HasColumnName("bool_prop_1"); - - b.Property("BooleanProperty2") - .HasColumnType("bool") - .HasColumnName("bool_prop_2"); - - b.Property("DecimalProperty1") - .HasColumnType("numeric") - .HasColumnName("dec_prop_1"); - - b.Property("DecimalProperty2") - .HasColumnType("numeric") - .HasColumnName("dec_prop_2"); - - b.Property("IntegerProperty1") - .HasColumnType("integer") - .HasColumnName("int_prop_1"); - - b.Property("IntegerProperty2") - .HasColumnType("integer") - .HasColumnName("int_prop_2"); - - b.Property("LongProperty1") - .HasColumnType("bigint") - .HasColumnName("long_prop_1"); - - b.Property("LongProperty2") - .HasColumnType("bigint") - .HasColumnName("long_prop_2"); - - b.Property("StringProperty1") - .HasColumnType("text") - .HasColumnName("str_prop_1"); - - b.Property("StringProperty2") - .HasColumnType("text") - .HasColumnName("str_prop_2"); - - b.Property("StringProperty3") - .HasColumnType("text") - .HasColumnName("str_prop_3"); - - b.Property("TimeZoneId") - .HasColumnType("text") - .HasColumnName("time_zone_id"); - - b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); - - b.ToTable("qrtz_simprop_triggers", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("TriggerName") - .HasColumnType("text") - .HasColumnName("trigger_name"); - - b.Property("TriggerGroup") - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.Property("RepeatCount") - .HasColumnType("bigint") - .HasColumnName("repeat_count"); - - b.Property("RepeatInterval") - .HasColumnType("bigint") - .HasColumnName("repeat_interval"); - - b.Property("TimesTriggered") - .HasColumnType("bigint") - .HasColumnName("times_triggered"); - - b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); - - b.ToTable("qrtz_simple_triggers", "quartz"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => - { - b.Property("SchedulerName") - .HasColumnType("text") - .HasColumnName("sched_name"); - - b.Property("TriggerName") - .HasColumnType("text") - .HasColumnName("trigger_name"); - - b.Property("TriggerGroup") - .HasColumnType("text") - .HasColumnName("trigger_group"); - - b.Property("CalendarName") - .HasColumnType("text") - .HasColumnName("calendar_name"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EndTime") - .HasColumnType("bigint") - .HasColumnName("end_time"); - - b.Property("JobData") - .HasColumnType("bytea") - .HasColumnName("job_data"); - - b.Property("JobGroup") - .IsRequired() - .HasColumnType("text") - .HasColumnName("job_group"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("job_name"); - - b.Property("MisfireInstruction") - .HasColumnType("smallint") - .HasColumnName("misfire_instr"); - - b.Property("NextFireTime") - .HasColumnType("bigint") - .HasColumnName("next_fire_time"); - - b.Property("PreviousFireTime") - .HasColumnType("bigint") - .HasColumnName("prev_fire_time"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("StartTime") - .HasColumnType("bigint") - .HasColumnName("start_time"); - - b.Property("TriggerState") - .IsRequired() - .HasColumnType("text") - .HasColumnName("trigger_state"); - - b.Property("TriggerType") - .IsRequired() - .HasColumnType("text") - .HasColumnName("trigger_type"); - - b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); - - b.HasIndex("NextFireTime") - .HasDatabaseName("idx_qrtz_t_next_fire_time"); - - b.HasIndex("TriggerState") - .HasDatabaseName("idx_qrtz_t_state"); - - b.HasIndex("NextFireTime", "TriggerState") - .HasDatabaseName("idx_qrtz_t_nft_st"); - - b.HasIndex("SchedulerName", "JobName", "JobGroup"); - - b.ToTable("qrtz_triggers", "quartz"); - }); - - modelBuilder.Entity("Crdt.Core.ServerCommit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ClientId") - .HasColumnType("uuid"); - - b.Property("Metadata") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.ComplexProperty>("HybridDateTime", "Crdt.Core.ServerCommit.HybridDateTime#HybridDateTime", b1 => - { - b1.IsRequired(); - - b1.Property("Counter") - .HasColumnType("bigint"); - - b1.Property("DateTime") - .HasColumnType("timestamp with time zone"); - }); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("CrdtCommits", (string)null); - }); - - modelBuilder.Entity("LexCore.Entities.DraftProject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Code") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("IsConfidential") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrgId") - .HasColumnType("uuid"); - - b.Property("ProjectManagerId") - .HasColumnType("uuid"); - - b.Property("RetentionPolicy") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("integer"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("ProjectManagerId"); - - b.ToTable("DraftProjects"); - }); - - modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => - { - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.Property("LexEntryCount") - .HasColumnType("integer"); - - b.HasKey("ProjectId"); - - b.ToTable("FlexProjectMetadata"); - }); - - modelBuilder.Entity("LexCore.Entities.OrgMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("OrgId") - .HasColumnType("uuid"); - - b.Property("Role") - .HasColumnType("integer"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrgId"); - - b.HasIndex("UserId", "OrgId") - .IsUnique(); - - b.ToTable("OrgMembers", (string)null); - }); - - modelBuilder.Entity("LexCore.Entities.OrgProjects", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("OrgId") - .HasColumnType("uuid"); - - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.HasIndex("OrgId", "ProjectId") - .IsUnique(); - - b.ToTable("OrgProjects"); - }); - - modelBuilder.Entity("LexCore.Entities.Organization", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Orgs", (string)null); - }); - - modelBuilder.Entity("LexCore.Entities.Project", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Code") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("IsConfidential") - .HasColumnType("boolean"); - - b.Property("LastCommit") - .HasColumnType("timestamp with time zone"); - - b.Property("MigratedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentId") - .HasColumnType("uuid"); - - b.Property("ProjectOrigin") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(1); - - b.Property("ResetStatus") - .HasColumnType("integer"); - - b.Property("RetentionPolicy") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("integer"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("ParentId"); - - b.ToTable("Projects"); - }); - - modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.Property("Role") - .HasColumnType("integer"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.HasIndex("UserId", "ProjectId") - .IsUnique(); - - b.ToTable("ProjectUsers"); - }); - - modelBuilder.Entity("LexCore.Entities.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CanCreateProjects") - .HasColumnType("boolean"); - - b.Property("CreatedById") - .HasColumnType("uuid"); - - b.Property("CreatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasColumnType("text") - .UseCollation("case_insensitive"); - - b.Property("EmailVerified") - .HasColumnType("boolean"); - - b.Property("GoogleId") - .HasColumnType("text"); - - b.Property("IsAdmin") - .HasColumnType("boolean"); - - b.Property("LastActive") - .HasColumnType("timestamp with time zone"); - - b.Property("LocalizationCode") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("en"); - - b.Property("Locked") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("PasswordHash") - .IsRequired() - .HasColumnType("text"); - - b.Property("PasswordStrength") - .HasColumnType("integer"); - - b.Property("Salt") - .IsRequired() - .HasColumnType("text"); - - b.Property("UpdatedDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("now()"); - - b.Property("Username") - .HasColumnType("text") - .UseCollation("case_insensitive"); - - b.HasKey("Id"); - - b.HasIndex("CreatedById"); - - b.HasIndex("Email") - .IsUnique(); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("text"); - - b.Property("ApplicationType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ClientId") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("ClientSecret") - .HasColumnType("text"); - - b.Property("ClientType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ConcurrencyToken") - .IsConcurrencyToken() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ConsentType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("DisplayNames") - .HasColumnType("text"); - - b.Property("JsonWebKeySet") - .HasColumnType("text"); - - b.Property("Permissions") - .HasColumnType("text"); - - b.Property("PostLogoutRedirectUris") - .HasColumnType("text"); - - b.Property("Properties") - .HasColumnType("text"); - - b.Property("RedirectUris") - .HasColumnType("text"); - - b.Property("Requirements") - .HasColumnType("text"); - - b.Property("Settings") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("OpenIddictApplications", (string)null); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("text"); - - b.Property("ApplicationId") - .HasColumnType("text"); - - b.Property("ConcurrencyToken") - .IsConcurrencyToken() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Properties") - .HasColumnType("text"); - - b.Property("Scopes") - .HasColumnType("text"); - - b.Property("Status") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Subject") - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId", "Status", "Subject", "Type"); - - b.ToTable("OpenIddictAuthorizations", (string)null); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("text"); - - b.Property("ConcurrencyToken") - .IsConcurrencyToken() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Descriptions") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("DisplayNames") - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Properties") - .HasColumnType("text"); - - b.Property("Resources") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("OpenIddictScopes", (string)null); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("text"); - - b.Property("ApplicationId") - .HasColumnType("text"); - - b.Property("AuthorizationId") - .HasColumnType("text"); - - b.Property("ConcurrencyToken") - .IsConcurrencyToken() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Payload") - .HasColumnType("text"); - - b.Property("Properties") - .HasColumnType("text"); - - b.Property("RedemptionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ReferenceId") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Subject") - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.HasIndex("AuthorizationId"); - - b.HasIndex("ReferenceId") - .IsUnique(); - - b.HasIndex("ApplicationId", "Status", "Subject", "Type"); - - b.ToTable("OpenIddictTokens", (string)null); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => - { - b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") - .WithMany("BlobTriggers") - .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Trigger"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => - { - b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") - .WithMany("CronTriggers") - .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Trigger"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => - { - b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") - .WithMany("SimplePropertyTriggers") - .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Trigger"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => - { - b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") - .WithMany("SimpleTriggers") - .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Trigger"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => - { - b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") - .WithMany("Triggers") - .HasForeignKey("SchedulerName", "JobName", "JobGroup") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("JobDetail"); - }); - - modelBuilder.Entity("Crdt.Core.ServerCommit", b => - { - b.HasOne("LexCore.Entities.FlexProjectMetadata", null) - .WithMany() - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsMany("Crdt.Core.ChangeEntity", "ChangeEntities", b1 => - { - b1.Property("ServerCommitId") - .HasColumnType("uuid"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - b1.Property("Change") - .HasColumnType("text"); - - b1.Property("CommitId") - .HasColumnType("uuid"); - - b1.Property("EntityId") - .HasColumnType("uuid"); - - b1.Property("Index") - .HasColumnType("integer"); - - b1.HasKey("ServerCommitId", "Id"); - - b1.ToTable("CrdtCommits"); - - b1.ToJson("ChangeEntities"); - - b1.WithOwner() - .HasForeignKey("ServerCommitId"); - }); - - b.Navigation("ChangeEntities"); - }); - - modelBuilder.Entity("LexCore.Entities.DraftProject", b => - { - b.HasOne("LexCore.Entities.User", "ProjectManager") - .WithMany() - .HasForeignKey("ProjectManagerId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("ProjectManager"); - }); - - modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => - { - b.HasOne("LexCore.Entities.Project", null) - .WithOne("FlexProjectMetadata") - .HasForeignKey("LexCore.Entities.FlexProjectMetadata", "ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsOne("LexCore.Entities.ProjectWritingSystems", "WritingSystems", b1 => - { - b1.Property("FlexProjectMetadataProjectId") - .HasColumnType("uuid"); - - b1.HasKey("FlexProjectMetadataProjectId"); - - b1.ToTable("FlexProjectMetadata"); - - b1.ToJson("WritingSystems"); - - b1.WithOwner() - .HasForeignKey("FlexProjectMetadataProjectId"); - - b1.OwnsMany("LexCore.Entities.FLExWsId", "AnalysisWss", b2 => - { - b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") - .HasColumnType("uuid"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - b2.Property("IsActive") - .HasColumnType("boolean"); - - b2.Property("IsDefault") - .HasColumnType("boolean"); - - b2.Property("Tag") - .IsRequired() - .HasColumnType("text"); - - b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); - - b2.ToTable("FlexProjectMetadata"); - - b2.WithOwner() - .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); - }); - - b1.OwnsMany("LexCore.Entities.FLExWsId", "VernacularWss", b2 => - { - b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") - .HasColumnType("uuid"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - b2.Property("IsActive") - .HasColumnType("boolean"); - - b2.Property("IsDefault") - .HasColumnType("boolean"); - - b2.Property("Tag") - .IsRequired() - .HasColumnType("text"); - - b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); - - b2.ToTable("FlexProjectMetadata"); - - b2.WithOwner() - .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); - }); - - b1.Navigation("AnalysisWss"); - - b1.Navigation("VernacularWss"); - }); - - b.Navigation("WritingSystems"); - }); - - modelBuilder.Entity("LexCore.Entities.OrgMember", b => - { - b.HasOne("LexCore.Entities.Organization", "Organization") - .WithMany("Members") - .HasForeignKey("OrgId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LexCore.Entities.User", "User") - .WithMany("Organizations") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LexCore.Entities.OrgProjects", b => - { - b.HasOne("LexCore.Entities.Organization", "Org") - .WithMany() - .HasForeignKey("OrgId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LexCore.Entities.Project", "Project") - .WithMany() - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Org"); - - b.Navigation("Project"); - }); - - modelBuilder.Entity("LexCore.Entities.Project", b => - { - b.HasOne("LexCore.Entities.Project", null) - .WithMany() - .HasForeignKey("ParentId"); - }); - - modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => - { - b.HasOne("LexCore.Entities.Project", "Project") - .WithMany("Users") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LexCore.Entities.User", "User") - .WithMany("Projects") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Project"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LexCore.Entities.User", b => - { - b.HasOne("LexCore.Entities.User", "CreatedBy") - .WithMany("UsersICreated") - .HasForeignKey("CreatedById") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("CreatedBy"); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => - { - b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") - .WithMany("Authorizations") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => - { - b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") - .WithMany("Tokens") - .HasForeignKey("ApplicationId"); - - b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") - .WithMany("Tokens") - .HasForeignKey("AuthorizationId"); - - b.Navigation("Application"); - - b.Navigation("Authorization"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => - { - b.Navigation("Triggers"); - }); - - modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => - { - b.Navigation("BlobTriggers"); - - b.Navigation("CronTriggers"); - - b.Navigation("SimplePropertyTriggers"); - - b.Navigation("SimpleTriggers"); - }); - - modelBuilder.Entity("LexCore.Entities.Organization", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("LexCore.Entities.Project", b => - { - b.Navigation("FlexProjectMetadata"); - - b.Navigation("Users"); - }); - - modelBuilder.Entity("LexCore.Entities.User", b => - { - b.Navigation("Organizations"); - - b.Navigation("Projects"); - - b.Navigation("UsersICreated"); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => - { - b.Navigation("Authorizations"); - - b.Navigation("Tokens"); - }); - - modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => - { - b.Navigation("Tokens"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs b/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs deleted file mode 100644 index b8eed9ad6..000000000 --- a/backend/LexData/Migrations/20240715044054_AddIsDefaultFieldToFLExWsId.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace LexData.Migrations -{ - /// - public partial class AddIsDefaultFieldToFLExWsId : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} From 47117b4d817d748555cbfb78fb9d7de7898d0cd1 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 18 Jul 2024 11:00:52 +0700 Subject: [PATCH 25/26] Show loading indicator at correct time --- .../(authenticated)/project/[project_code]/+page.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte index 7391ead09..c7610baa4 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte @@ -80,8 +80,11 @@ loadingEntryCount = false; } + let loadingLanguageList = false; async function updateLanguageList(): Promise { + loadingLanguageList = true; await fetch(`/api/project/updateLanguageList/${project.code}`, {method: 'POST'}); + loadingLanguageList = false; await invalidate(`project:${project.code}`); } @@ -345,7 +348,7 @@ Date: Tue, 23 Jul 2024 08:56:33 +0700 Subject: [PATCH 26/26] Fix failing unit tests Eventually these tests should clean up after themselves, or use randomly-assigned project codes so that they don't conflict. But for now this is good enough for a quick fix. --- backend/Testing/LexCore/Services/ProjectServiceTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Testing/LexCore/Services/ProjectServiceTest.cs b/backend/Testing/LexCore/Services/ProjectServiceTest.cs index 5b3943c86..48dcc9739 100644 --- a/backend/Testing/LexCore/Services/ProjectServiceTest.cs +++ b/backend/Testing/LexCore/Services/ProjectServiceTest.cs @@ -50,7 +50,7 @@ public ProjectServiceTest(TestingServicesFixture testing) public async Task CanCreateProject() { var projectId = await _projectService.CreateProject( - new(null, "TestProject", "Test", "test", ProjectType.FLEx, RetentionPolicy.Test, false, null, null)); + new(null, "TestProject", "Test", "test1", ProjectType.FLEx, RetentionPolicy.Test, false, null, null)); projectId.ShouldNotBe(default); } @@ -58,7 +58,7 @@ public async Task CanCreateProject() public async Task CanUpdateProjectLangTags() { var projectId = await _projectService.CreateProject( - new(null, "TestProject", "Test", "test", ProjectType.FLEx, RetentionPolicy.Test, false, null, null)); + new(null, "TestProject", "Test", "test2", ProjectType.FLEx, RetentionPolicy.Test, false, null, null)); await _projectService.UpdateProjectLangTags(projectId); var project = await _lexBoxDbContext.Projects.Include(p => p.FlexProjectMetadata).SingleAsync(p => p.Id == projectId); project.FlexProjectMetadata.ShouldNotBeNull();