29 Commits
Author SHA1 Message Date
rrejc 8643b6feaa Merge pull request 'New features 2026' (#1) from new_features_2026 into main
Reviewed-on: #1
2026-09-03 14:28:06 +00:00
rrejc 551b8469e4 Add shared MSBuild configuration to Dockerfiles 2026-09-03 16:25:37 +02:00
rrejc 1d85f4ccf8 Upgrade docker images to .net 10 2026-09-03 16:15:38 +02:00
rrejc cc226d6ce6 Update readme 2026-09-03 16:11:13 +02:00
rrejc 4996fa7baa Change logo link to slovenscina.eu 2026-06-22 11:27:38 +02:00
rrejc 5215ca95fb Change icon used in aggregations. 2026-06-22 11:19:39 +02:00
rrejc 6d554f190d Fix help display in concordance and list results. 2026-06-20 07:03:21 +02:00
rrejc 3776aa3f4e Enable wildcard search in terms list (missing file). 2026-06-20 06:33:42 +02:00
rrejc 18fd3950c9 Enable wildcard search in terms list 2026-06-20 06:33:16 +02:00
rrejc cff72f167a Updated .gitignore file 2026-06-08 15:30:52 +02:00
rrejc d3e773fe48 Remove DotSettings. 2026-06-08 15:26:51 +02:00
rrejc 8a80499d73 Remove old StyleCop and use built-in analyzer. 2026-06-08 15:25:44 +02:00
rrejc 5b89d794ca Upgrade to top-level program.cs 2026-06-08 14:24:00 +02:00
rrejc 227c46827f Upgrade packages to .net 10 2026-06-08 13:40:48 +02:00
rrejc 5bd50d1f55 Add central package management. 2026-06-08 13:18:31 +02:00
rrejc a0691c2ccd Add Directory.Build.props file 2026-06-08 13:14:02 +02:00
rrejc 14c70888e4 Add nuget config and add local-packages directory to it 2026-06-08 13:07:56 +02:00
rrejc 64b2121fa1 Bumping up version for Docker containers. 2023-04-14 10:31:44 +02:00
rrejc 1ee3dedcf3 New corpus database migration which changes data types for Form and Lemma from varying(400) to text to allow longer forms and lemmas. 2023-04-14 10:27:32 +02:00
rrejc 89f6c5864e Include batch files for building Docker images. 2023-01-23 11:17:40 +01:00
rrejc 8851fa938c Fix layout when source (file name) is long, bump version to 1.0.3 2023-01-20 08:45:09 +01:00
rrejc 691196ebf1 Don't wrap concordance results in multiple lines. 2023-01-18 13:11:08 +01:00
rrejc b000f2225c Change extension of export file from csv to txt. 2023-01-16 13:01:53 +01:00
rrejc 2f8d5fa403 Adds source to the each item in the export concordances file. 2023-01-16 12:55:38 +01:00
rrejc 075990cf8a Display text information (in concordance details) only if it is available 2023-01-16 12:14:01 +01:00
rrejc 574348a9f2 Fix (pogostostpojavljanja is now array) for importing term lists, new version. 2023-01-11 12:50:31 +01:00
rrejc ce7b0e6d1f Merge remote-tracking branch 'origin/main' 2023-01-11 12:43:13 +01:00
rrejc d7170126f0 Performance improvements for indexing text. 2023-01-11 12:38:10 +01:00
rrejc 1341d4ebcb Update README.md 2022-12-16 07:36:56 +01:00
57 changed files with 765 additions and 830 deletions
+52
View File
@@ -1 +1,53 @@
# RSDO (Razvoj Slovenščine v Digitalnem Okolju) - Concordancer # RSDO (Razvoj Slovenščine v Digitalnem Okolju) - Concordancer
This repository contains the source code of the concordancer which is a part of the [Terminology Portal](https://github.com/clarinsi/rsdo_term_portal).
There are two main parts:
- Web application with backend (API interface) written in ASP.NET Core 10 and frontend written in React
- System Manager which is written in .NET 10 and is used to initially set up the system
## Prerequisites
Web application requires following external dependencies
- OpenSearch v2.3.0 (use docker image: `opensearchproject/opensearch:2.3.0`)
- PostgreSQL (use docker image: `postgres`)
## Building source code
### Frontend (React application)
To build a frontend application successfully, first, [Node.js](https://nodejs.org/en/) must be installed (at least version 16).
Use `npm install` to install all required dependencies, and `npm run build` to build the application.
For more info see the [Readme](src/apps/Rsdo.Concordancer.Api/WebApp/README.md) inside the frontend [root](src/apps/Rsdo.Concordancer.Api/WebApp) folder.
### Backend (ASP.NET Core 10) and System Manager (.NET 10)
Open [Rsdo.Concordancer.sln](src/Rsdo.Concordancer.sln) inside of your IDE and build the solution, or use `dotnet build` command to build the solution. All required dependencies will be installed via NuGet packages.
## Creating docker images
There are two Dockerfile's in the source repository for each part. Both images should be compiled within the context of the project root folder.
### Web application
[Dockerfile](src/apps/Rsdo.Concordancer.Api/Dockerfile) for Web Application accepts additional optional argument (WEBAPP_PUBLIC_URL) which is mapped to React [PUBLIC_URL](https://create-react-app.dev/docs/using-the-public-folder/) variable. If the variable is omited, then application should be deployed on a domain root.
`docker build -t rsdo-concordancer-api -f src/apps/Rsdo.Concordancer.Api/Dockerfile .`
`docker build --build-arg WEBAPP_PUBLIC_URL=/corpus -t rsdo-concordancer-api -f src/apps/Rsdo.Concordancer.Api/Dockerfile .`
### System Manager
Use following [Dockerfile](src/apps/Rsdo.Concordancer.SystemManager/Dockerfile) to build System Manager.
`docker build -t rsdo-concordancer-systemmanager -f src/apps/Rsdo.Concordancer.SystemManager/Dockerfile .`
## Environment variables
### Web application
Web application must be configured with the following Environment variables and values:
- `RSDO:Database:MasterConnectionString` holds connection string to the main database; e.g.: `Host=rsdo_db;Username=rsdo;Password=strongpassword;Database=Concordancer`
- `RSDO:Elastic:ConnectionString` holds connection string to OpenSearch server; e.g.: `http://rsdo_opensearch:9200`
- `RSDO:Tokenizer:ClasslaUrl` holds and url to external service which is used for tokenizing search strings; e.g.: `https://orodja.cjvt.si/oznacevalnik/ajax_api/v1/slv/process`
### System Manager
System manager must be configured with the following Environment variables and values:
- `RSDO:Database:MasterConnectionString` holds connection string to the main database; e.g.: `Host=rsdo_db;Username=rsdo;Password=strongpassword;Database=Concordancer`
- `RSDO:Elastic:ConnectionString` holds connection string to OpenSearch server; e.g.: `http://rsdo_opensearch:9200`
## Using System Manager
To initialy setup the system, you have to use System Manager and run following commands:
1. `dotnet Rsdo.Concordancer.SystemManager.dll createMasterDb --connectionString="Host=rsdo_db;Username=rsdo;Password=strongpassword;Database=postgres"` which will create master database with the name Concordancer. You can select different name with the `--name` option
2. `dotnet Rsdo.Concordancer.SystemManager.dll importSloleks --sourceFile=/data/Sloleks2.0.LMF/sloleks_clarin_2.0.xml` which will import Sloleks (which is used for better searching). XML file is not part of this repository, but it can be downloaded from the following [URL](https://www.clarin.si/repository/xmlui/bitstream/handle/11356/1230/Sloleks2.0.LMF.zip?sequence=3&isAllowed=y)
+3
View File
@@ -0,0 +1,3 @@
docker build --no-cache -t rsdo-concordancer-api -f src/apps/Rsdo.Concordancer.Api/Dockerfile .
docker build --no-cache --build-arg WEBAPP_PUBLIC_URL=/korpus -t rsdo-concordancer-api-term-portal -f src/apps/Rsdo.Concordancer.Api/Dockerfile .
docker build --no-cache -t rsdo-concordancer-systemmanager -f src/apps/Rsdo.Concordancer.SystemManager/Dockerfile .
+24
View File
@@ -0,0 +1,24 @@
docker build --no-cache -t rsdo-concordancer-api -f src/apps/Rsdo.Concordancer.Api/Dockerfile .
docker build --no-cache --build-arg WEBAPP_PUBLIC_URL=/korpus -t rsdo-concordancer-api-term-portal -f src/apps/Rsdo.Concordancer.Api/Dockerfile .
docker build --no-cache -t rsdo-concordancer-systemmanager -f src/apps/Rsdo.Concordancer.SystemManager/Dockerfile .
docker login ghcr.io
docker tag rsdo-concordancer-api ghcr.io/clarinsi/rsdo-concordancer-api:latest
docker tag rsdo-concordancer-api ghcr.io/clarinsi/rsdo-concordancer-api:v1.0.4
docker tag rsdo-concordancer-api ghcr.io/clarinsi/rsdo-concordancer-api:v1.0
docker tag rsdo-concordancer-api ghcr.io/clarinsi/rsdo-concordancer-api:v1
docker tag rsdo-concordancer-api-term-portal ghcr.io/clarinsi/rsdo-concordancer-api-term-portal:latest
docker tag rsdo-concordancer-api-term-portal ghcr.io/clarinsi/rsdo-concordancer-api-term-portal:v1.0.4
docker tag rsdo-concordancer-api-term-portal ghcr.io/clarinsi/rsdo-concordancer-api-term-portal:v1.0
docker tag rsdo-concordancer-api-term-portal ghcr.io/clarinsi/rsdo-concordancer-api-term-portal:v1
docker tag rsdo-concordancer-systemmanager ghcr.io/clarinsi/rsdo-concordancer-systemmanager:latest
docker tag rsdo-concordancer-systemmanager ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1.0.4
docker tag rsdo-concordancer-systemmanager ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1.0
docker tag rsdo-concordancer-systemmanager ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1
docker push --all-tags ghcr.io/clarinsi/rsdo-concordancer-api
docker push --all-tags ghcr.io/clarinsi/rsdo-concordancer-api-term-portal
docker push --all-tags ghcr.io/clarinsi/rsdo-concordancer-systemmanager
Binary file not shown.
+62 -18
View File
@@ -1,7 +1,7 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
## ##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files # User-specific files
*.rsuser *.rsuser
@@ -9,6 +9,7 @@
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
*.env
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
@@ -21,17 +22,37 @@ mono_crash.*
[Dd]ebugPublic/ [Dd]ebugPublic/
[Rr]elease/ [Rr]elease/
[Rr]eleases/ [Rr]eleases/
x64/
x86/ [Dd]ebug/x64/
[Dd]ebugPublic/x64/
[Rr]elease/x64/
[Rr]eleases/x64/
bin/x64/
obj/x64/
[Dd]ebug/x86/
[Dd]ebugPublic/x86/
[Rr]elease/x86/
[Rr]eleases/x86/
bin/x86/
obj/x86/
[Ww][Ii][Nn]32/ [Ww][Ii][Nn]32/
[Aa][Rr][Mm]/ [Aa][Rr][Mm]/
[Aa][Rr][Mm]64/ [Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/ bld/
[Bb]in/
[Oo]bj/ [Oo]bj/
[Oo]ut/
[Ll]og/ [Ll]og/
[Ll]ogs/ [Ll]ogs/
# Build results on 'Bin' directories
**/[Bb]in/*
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
# Visual Studio 2015/2017 cache/options directory # Visual Studio 2015/2017 cache/options directory
.vs/ .vs/
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
@@ -43,12 +64,16 @@ Generated\ Files/
# MSTest test Results # MSTest test Results
[Tt]est[Rr]esult*/ [Tt]est[Rr]esult*/
[Bb]uild[Ll]og.* [Bb]uild[Ll]og.*
*.trx
# NUnit # NUnit
*.VisualState.xml *.VisualState.xml
TestResult.xml TestResult.xml
nunit-*.xml nunit-*.xml
# Approval Tests result files
*.received.*
# Build Results of an ATL Project # Build Results of an ATL Project
[Dd]ebugPS/ [Dd]ebugPS/
[Rr]eleasePS/ [Rr]eleasePS/
@@ -61,6 +86,7 @@ BenchmarkDotNet.Artifacts/
project.lock.json project.lock.json
project.fragment.lock.json project.fragment.lock.json
artifacts/ artifacts/
.artifacts/
# ASP.NET Scaffolding # ASP.NET Scaffolding
ScaffoldingReadMe.txt ScaffoldingReadMe.txt
@@ -75,6 +101,7 @@ StyleCopReport.xml
*.ilk *.ilk
*.meta *.meta
*.obj *.obj
*.idb
*.iobj *.iobj
*.pch *.pch
*.pdb *.pdb
@@ -82,6 +109,8 @@ StyleCopReport.xml
*.pgc *.pgc
*.pgd *.pgd
*.rsp *.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr *.sbr
*.tlb *.tlb
*.tli *.tli
@@ -153,6 +182,7 @@ coverage*.info
# NCrunch # NCrunch
_NCrunch_* _NCrunch_*
.NCrunch_*
.*crunch*.local.xml .*crunch*.local.xml
nCrunchTemp_* nCrunchTemp_*
@@ -206,9 +236,6 @@ PublishScripts/
*.nuget.props *.nuget.props
*.nuget.targets *.nuget.targets
# Nuget personal access tokens and Credentials
nuget.config
# Microsoft Azure Build Output # Microsoft Azure Build Output
csx/ csx/
*.build.csdef *.build.csdef
@@ -297,6 +324,14 @@ node_modules/
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw *.vbw
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output # Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts **/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts
@@ -306,22 +341,22 @@ node_modules/
_Pvt_Extensions _Pvt_Extensions
# Paket dependency manager # Paket dependency manager
.paket/paket.exe **/.paket/paket.exe
paket-files/ paket-files/
# FAKE - F# Make # FAKE - F# Make
.fake/ **/.fake/
# CodeRush personal settings # CodeRush personal settings
.cr/personal **/.cr/personal
# Python Tools for Visual Studio (PTVS) # Python Tools for Visual Studio (PTVS)
__pycache__/ **/__pycache__/
*.pyc *.pyc
# Cake - Uncomment if you are using it # Cake - Uncomment if you are using it
# tools/** #tools/**
# !tools/packages.config #!tools/packages.config
# Tabs Studio # Tabs Studio
*.tss *.tss
@@ -343,15 +378,22 @@ ASALocalRun/
# MSBuild Binary and Structured Log # MSBuild Binary and Structured Log
*.binlog *.binlog
MSBuild_Logs/
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file # NVidia Nsight GPU debugger configuration file
*.nvuser *.nvuser
# MFractors (Xamarin productivity tool) working folder # MFractors (Xamarin productivity tool) working folder
.mfractor/ **/.mfractor/
# Local History for Visual Studio # Local History for Visual Studio
.localhistory/ **/.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database # BeatPulse healthcheck temp database
healthchecksdb healthchecksdb
@@ -360,7 +402,7 @@ healthchecksdb
MigrationBackup/ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder # Ionide (cross platform F# VS Code tools) working folder
.ionide/ **/.ionide/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
@@ -371,11 +413,14 @@ FodyWeavers.xsd
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
*.code-workspace !.vscode/*.code-snippets
# Local History for Visual Studio Code # Local History for Visual Studio Code
.history/ .history/
# Built Visual Studio Code Extensions
*.vsix
# Windows Installer files from build outputs # Windows Installer files from build outputs
*.cab *.cab
*.msi *.msi
@@ -385,4 +430,3 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
.idea/ .idea/
*.sln.iml
+11
View File
@@ -0,0 +1,11 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>10-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<Version>1.0.4</Version>
</PropertyGroup>
</Project>
+42
View File
@@ -0,0 +1,42 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Autofac" Version="9.1.0" />
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
<PackageVersion Include="EFCore.NamingConventions" Version="10.0.1" />
<PackageVersion Include="FluentMigrator" Version="8.0.1" />
<PackageVersion Include="FluentMigrator.Runner" Version="8.0.1" />
<PackageVersion Include="FluentMigrator.Runner.Postgres" Version="8.0.1" />
<PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="Hangfire" Version="1.8.23" />
<PackageVersion Include="Hangfire.Core" Version="1.8.23" />
<PackageVersion Include="Hangfire.PostgreSql" Version="1.21.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Npgsql" Version="10.0.3" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
<PackageVersion Include="OpenSearch.Client" Version="1.8.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.Email" Version="4.2.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.2.1" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="10.2.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
</Project>
+7
View File
@@ -24,6 +24,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rsdo.Concordancer.Data", "c
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rsdo.Concordancer.SystemManager", "apps\Rsdo.Concordancer.SystemManager\Rsdo.Concordancer.SystemManager.csproj", "{41B26AD1-C0A5-4796-95D0-CB84FB574A83}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rsdo.Concordancer.SystemManager", "apps\Rsdo.Concordancer.SystemManager\Rsdo.Concordancer.SystemManager.csproj", "{41B26AD1-C0A5-4796-95D0-CB84FB574A83}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0BC9D8CB-3F46-40F4-B769-E2605F03E0F6}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
.gitignore = .gitignore
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
-357
View File
@@ -1,357 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Browsers/Browsers/@EntryValue">C65+,E16+,FF58+,IE11+</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">VISIBLE_FILES</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeThisQualifier/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CSharpWarnings_003A_003ACS1591/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Reformat_0020Code_0020IDE/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Reformat Code IDE"&gt;&lt;CSReorderTypeMembers&gt;True&lt;/CSReorderTypeMembers&gt;&lt;CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="False" ArrangeCodeBodyStyle="False" /&gt;&lt;CSArrangeQualifiers&gt;True&lt;/CSArrangeQualifiers&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSReformatCode&gt;True&lt;/CSReformatCode&gt;&lt;CSFixBuiltinTypeReferences&gt;True&lt;/CSFixBuiltinTypeReferences&gt;&lt;CSShortenReferences&gt;True&lt;/CSShortenReferences&gt;&lt;XAMLCollapseEmptyTags&gt;False&lt;/XAMLCollapseEmptyTags&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=StyleCop/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="StyleCop"&gt;&lt;CSUpdateFileHeader&gt;False&lt;/CSUpdateFileHeader&gt;&lt;CSArrangeQualifiers&gt;True&lt;/CSArrangeQualifiers&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSReformatCode&gt;True&lt;/CSReformatCode&gt;&lt;CSReorderTypeMembers&gt;True&lt;/CSReorderTypeMembers&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">None</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&#xD;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;&#xD;
&lt;TypePattern DisplayName="Non-reorderable types"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Interface" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /&gt;&#xD;
&lt;HasAttribute Name="System.Runtime.InteropServices.ComImport" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;Kind Is="Struct" /&gt;&#xD;
&lt;HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /&gt;&#xD;
&lt;HasAttribute Name="JetBrains.Annotations.NoReorder" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;HasMember&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.FactAttribute" Inherited="True" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/HasMember&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Constructor" /&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;ImplementsInterface Name="System.IDisposable" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Kind Order="Constructor" /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.FactAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="Default Pattern (StyleCop)" RemoveRegions="All"&gt;&#xD;
&lt;Entry DisplayName="Constants"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Constant" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Static fields"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Field" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Readonly /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Fields"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Field" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Readonly /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Constructors and Destructors" Priority="200"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Constructor" /&gt;&#xD;
&lt;Kind Is="Destructor" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Kind Order="Constructor Destructor" /&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Delegates"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Delegate" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public events"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Event" /&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface events"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Event" /&gt;&#xD;
&lt;ImplementsInterface /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;ImplementsInterface Immediate="True" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Other events"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Event" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Enums"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Enum" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interfaces"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Interface" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public properties"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Property" /&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface properties"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Property" /&gt;&#xD;
&lt;ImplementsInterface /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;ImplementsInterface Immediate="True" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Other properties"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Property" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public indexers" Priority="1000"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Indexer" /&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface indexers" Priority="1000"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Indexer" /&gt;&#xD;
&lt;ImplementsInterface /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;ImplementsInterface Immediate="True" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Other indexers" Priority="1000"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Indexer" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public methods and operators"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;Kind Is="Operator" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;ImplementsInterface /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;ImplementsInterface Immediate="True" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Other methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Operators"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Operator" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Nested structs" Priority="600"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Struct" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Nested classes" Priority="700"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;Access Order="Public Internal ProtectedInternal Protected Private" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;/Patterns&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpUsing/KeepImports/=System/@EntryIndexedValue">System</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpUsing/KeepImports/=System_002ELinq/@EntryIndexedValue">System.Linq</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MB/@EntryIndexedValue">MB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dtos/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -1,5 +1,5 @@
using System; using System;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi;
namespace Rsdo.Concordancer.Api.Controllers; namespace Rsdo.Concordancer.Api.Controllers;
+6 -9
View File
@@ -1,8 +1,12 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
# COPY SHARED MSBUILD CONFIGURATION
COPY src/Directory.Build.props /src/Directory.Build.props
COPY src/Directory.Packages.props /src/Directory.Packages.props
# INSTALL NODE.JS # INSTALL NODE.JS
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \
@@ -10,13 +14,6 @@ RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \
nodejs \ nodejs \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# COPY LOCAL NUGET PACKAGES
WORKDIR /nuget
COPY local-packages/. .
# ADD LOCAL NUGET SOURCE
RUN dotnet nuget add source /nuget
# RESTORE PACKAGES (with project files only) # RESTORE PACKAGES (with project files only)
WORKDIR /src/apps WORKDIR /src/apps
COPY src/apps/Rsdo.Concordancer.Api/Rsdo.Concordancer.Api.csproj Rsdo.Concordancer.Api/Rsdo.Concordancer.Api.csproj COPY src/apps/Rsdo.Concordancer.Api/Rsdo.Concordancer.Api.csproj Rsdo.Concordancer.Api/Rsdo.Concordancer.Api.csproj
@@ -1,9 +1,9 @@
using System; using System;
using System.Net; using System.Net;
using MailKit.Security;
using Serilog; using Serilog;
using Serilog.Configuration; using Serilog.Configuration;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.Email;
namespace Rsdo.Concordancer.Api.Framework; namespace Rsdo.Concordancer.Api.Framework;
@@ -23,25 +23,25 @@ public static class SerilogExtensions
int batchPostingLimit = 100, int batchPostingLimit = 100,
string mailSubject = "Log Email") string mailSubject = "Log Email")
{ {
var connectionInfo = new EmailConnectionInfo() NetworkCredential credentials = null;
{
FromEmail = fromEmail,
ToEmail = toEmail,
EmailSubject = mailSubject,
MailServer = mailServer,
Port = port,
EnableSsl = enableSsl,
};
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{ {
connectionInfo.NetworkCredentials = new NetworkCredential() credentials = new NetworkCredential()
{ {
UserName = userName, UserName = userName,
Password = password, Password = password,
}; };
} }
return sinkConfiguration.Email(connectionInfo: connectionInfo, restrictedToMinimumLevel: restrictedToMinimumLevel); return sinkConfiguration.Email(
from: fromEmail,
to: toEmail,
host: mailServer,
port: port,
connectionSecurity: enableSsl ? SecureSocketOptions.Auto : SecureSocketOptions.None,
credentials: credentials,
subject: mailSubject,
body: outputTemplate,
restrictedToMinimumLevel: restrictedToMinimumLevel);
} }
} }
+137 -43
View File
@@ -1,52 +1,146 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection;
using System.Text.Json.Serialization;
using Autofac;
using Autofac.Extensions.DependencyInjection; using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting; using Hangfire;
using Microsoft.Extensions.Configuration; using Hangfire.PostgreSql;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi;
using Rsdo.Concordancer.Api.Controllers;
using Rsdo.Concordancer.Api.Framework;
using Rsdo.Concordancer.Core.Constants;
using Rsdo.Concordancer.Data.CompositionRoot;
using Rsdo.Concordancer.Infrastructure.CompositionRoot;
using Rsdo.Concordancer.ServiceModel.Shared;
using Rsdo.Concordancer.Services.CompositionRoot;
using Rsdo.Concordancer.Services.Framework.Cache;
using Serilog; using Serilog;
namespace Rsdo.Concordancer.Api; var builder = WebApplication.CreateBuilder(
new WebApplicationOptions()
public class Program
{
public static int Main(string[] args)
{ {
// Read configuration file Args = args,
var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) WebRootPath = "./WebApp/build",
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
.Build();
// Create logger
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
// Run application
try
{
Log.Information("Starting web host");
CreateHostBuilder(args).Build().Run();
return 0;
}
catch (Exception e)
{
Log.Fatal(e, "Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(
webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseWebRoot("./WebApp/build");
}); });
builder.Host.UseSerilog(
(context, _, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(context.Configuration);
});
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(
containerBuilder =>
{
containerBuilder.RegisterModule(new ServicesModule());
containerBuilder.RegisterModule(new InfrastructureModule());
containerBuilder.RegisterModule(new DataModule());
containerBuilder.RegisterBuildCallback(
(c) =>
{
var warmUps = c.Resolve<IEnumerable<ICacheWarmUp>>();
foreach (var warmUp in warmUps)
{
warmUp.WarmUp();
}
});
});
builder.Services.AddCors(
opt =>
{
opt.AddPolicy(
"CorsPolicy",
policy =>
{
policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
builder.Services.AddControllers()
.AddJsonOptions(
opts =>
{
// Bind strings to enums
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
builder.Services.AddSwaggerGen(
c =>
{
c.SwaggerDoc(
ServiceApiInfo.ApiGroupConcordancer,
info: new OpenApiInfo()
{
Title = $"{ServiceApiInfo.ServiceName} Concordancer API",
Version = $"v{ServiceApiInfo.ServiceVersion.ToString(3)}",
});
c.SwaggerDoc(
ServiceApiInfo.ApiGroupDashboard,
info: new OpenApiInfo()
{
Title = $"{ServiceApiInfo.ServiceName} Dashboard API",
Version = $"v{ServiceApiInfo.ServiceVersion.ToString(3)}",
});
// enable attribute annotations
c.EnableAnnotations();
// include code documentation to the swagger doc
foreach (var assembly in new[] { Assembly.GetExecutingAssembly(), typeof(ExecutionResult).Assembly })
{
var xmlFile = $"{assembly.GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
}
});
builder.Services.AddHangfire(
x =>
{
// It would be better to use ConnectionStringProvider, but in this case
// we would have to build the container which would (at this point) double singletons.
// So we are duplicated code from ConnectionStringProvider
var connectionString = builder.Configuration[ConfigurationKey.Database.MasterConnectionString];
x.UsePostgreSqlStorage(
options =>
{
options.UseNpgsqlConnection(connectionString);
});
x.UseMediator();
});
builder.Services.AddHangfireServer();
builder.Services.AddHttpClient();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} }
app.UseSwagger();
app.UseSwaggerUI(
c =>
{
c.SwaggerEndpoint($"{ServiceApiInfo.ApiGroupConcordancer}/swagger.json", $"{ServiceApiInfo.ServiceName} Concordancer API");
c.SwaggerEndpoint($"{ServiceApiInfo.ApiGroupDashboard}/swagger.json", $"{ServiceApiInfo.ServiceName} Dashboard API");
});
app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthorization();
app.MapControllers();
app.MapHangfireDashboard("/hangfire");
app.MapFallbackToFile("index.html");
app.Run();
@@ -1,27 +1,21 @@
{ {
"$schema": "http://json.schemastore.org/launchsettings.json", "$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:9184",
"sslPort": 44312
}
},
"profiles": { "profiles": {
"IIS Express": { "http": {
"commandName": "IISExpress", "commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "swagger/index.html",
"applicationUrl": "http://localhost:5000",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
}, },
"Rsdo.Concordancer.Api": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": "true", "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "swagger/index.html",
"applicationUrl": "https://localhost:5001;http://localhost:5000", "applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
@@ -1,27 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" /> <PackageReference Include="Autofac" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" />
<PackageReference Include="Hangfire" Version="1.7.31" /> <PackageReference Include="Hangfire" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.9.9" /> <PackageReference Include="Hangfire.PostgreSql" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.10" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" /> <PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" /> <PackageReference Include="Serilog.Expressions" />
<PackageReference Include="Serilog.Expressions" Version="3.4.1" /> <PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" /> <PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Email" />
<PackageReference Include="Serilog.Sinks.Email" Version="2.4.0" /> <PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
-150
View File
@@ -1,150 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using Autofac;
using Hangfire;
using Hangfire.PostgreSql;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Rsdo.Concordancer.Api.Controllers;
using Rsdo.Concordancer.Api.Framework;
using Rsdo.Concordancer.Core.Constants;
using Rsdo.Concordancer.Data.CompositionRoot;
using Rsdo.Concordancer.Infrastructure.CompositionRoot;
using Rsdo.Concordancer.ServiceModel.Shared;
using Rsdo.Concordancer.Services.CompositionRoot;
using Rsdo.Concordancer.Services.Framework.Cache;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Rsdo.Concordancer.Api;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(
opt =>
{
opt.AddPolicy(
"CorsPolicy",
policy =>
{
policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
services.AddControllers()
.AddJsonOptions(
opts =>
{
// Bind strings to enums
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
services.AddSwaggerGen(
c =>
{
c.SwaggerDoc(
ServiceApiInfo.ApiGroupConcordancer,
info: new OpenApiInfo()
{
Title = $"{ServiceApiInfo.ServiceName} Concordancer API",
Version = $"v{ServiceApiInfo.ServiceVersion.ToString(3)}",
});
c.SwaggerDoc(
ServiceApiInfo.ApiGroupDashboard,
info: new OpenApiInfo()
{
Title = $"{ServiceApiInfo.ServiceName} Dashboard API",
Version = $"v{ServiceApiInfo.ServiceVersion.ToString(3)}",
});
// enable attribute annotations
c.EnableAnnotations();
// include code documentation to the swagger doc
ConfigureSwaggerApiDoc(c, Assembly.GetExecutingAssembly(), typeof(ExecutionResult).Assembly);
});
services.AddHangfire(
x =>
{
// It would be better to use ConnectionStringProvider, but in this case
// we would have to build the container which would (at this point) double singletons.
// So we are duplicated code from ConnectionStringProvider
var connectionString = Configuration[ConfigurationKey.Database.MasterConnectionString];
x.UsePostgreSqlStorage(connectionString);
x.UseMediator();
});
services.AddHangfireServer();
services.AddHttpClient();
}
public void ConfigureSwaggerApiDoc(SwaggerGenOptions options, params Assembly[] assemblies)
{
foreach (var assembly in assemblies)
{
var xmlFile = $"{assembly.GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
}
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new ServicesModule());
builder.RegisterModule(new InfrastructureModule());
builder.RegisterModule(new DataModule());
builder.RegisterBuildCallback(
(c) =>
{
var warmUps = c.Resolve<IEnumerable<ICacheWarmUp>>();
foreach (var warmUp in warmUps)
{
warmUp.WarmUp();
}
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(
c =>
{
c.SwaggerEndpoint($"{ServiceApiInfo.ApiGroupConcordancer}/swagger.json", $"{ServiceApiInfo.ServiceName} Concordancer API");
c.SwaggerEndpoint($"{ServiceApiInfo.ApiGroupDashboard}/swagger.json", $"{ServiceApiInfo.ServiceName} Dashboard API");
});
app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthorization();
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
endpoints.MapHangfireDashboard("/hangfire");
endpoints.MapFallbackToFile("index.html");
});
}
}
@@ -0,0 +1,3 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.8297 5.28995L2.5897 1.04995C2.49674 0.95622 2.38613 0.881826 2.26428 0.831057C2.14242 0.780288 2.01171 0.75415 1.8797 0.75415C1.74769 0.75415 1.61698 0.780288 1.49512 0.831057C1.37326 0.881826 1.26266 0.95622 1.1697 1.04995C0.983448 1.23731 0.878906 1.49076 0.878906 1.75495C0.878906 2.01913 0.983448 2.27259 1.1697 2.45995L4.7097 5.99995L1.1697 9.53995C0.983448 9.72731 0.878906 9.98076 0.878906 10.2449C0.878906 10.5091 0.983448 10.7626 1.1697 10.9499C1.26314 11.0426 1.37395 11.116 1.49579 11.1657C1.61763 11.2155 1.74809 11.2407 1.8797 11.2399C2.01131 11.2407 2.14177 11.2155 2.26361 11.1657C2.38544 11.116 2.49626 11.0426 2.5897 10.9499L6.8297 6.70995C6.92343 6.61699 6.99782 6.50638 7.04859 6.38453C7.09936 6.26267 7.1255 6.13196 7.1255 5.99995C7.1255 5.86794 7.09936 5.73723 7.04859 5.61537C6.99782 5.49351 6.92343 5.38291 6.8297 5.28995Z" fill="#848C91"/>
</svg>

After

Width:  |  Height:  |  Size: 977 B

@@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 3.75C1.5 2.50736 2.50736 1.5 3.75 1.5H14.25C15.4926 1.5 16.5 2.50736 16.5 3.75V14.25C16.5 15.4926 15.4926 16.5 14.25 16.5H3.75C2.50736 16.5 1.5 15.4926 1.5 14.25V3.75Z" fill="#006CB7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.6857 6.21973C12.9786 6.51262 12.9786 6.98749 12.6857 7.28039L8.03033 12.1554C7.73744 12.4483 7.26256 12.4483 6.96967 12.1554L4.71967 9.90539C4.42678 9.61249 4.42678 9.13762 4.71967 8.84473C5.01256 8.55183 5.48744 8.55183 5.78033 8.84473L7.5 10.5644L11.625 6.21973C11.9179 5.92683 12.3928 5.92683 12.6857 6.21973Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 725 B

@@ -1,14 +1,26 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import useSearch from '../../hooks/use-search'; import useSearch from '../../hooks/use-search';
import styles from './ConcordanceAggregationItem.module.scss'; import styles from './ConcordanceAggregationItem.module.scss';
import { useTranslation } from 'react-i18next';
import selectIcon from '../../assets/chevron-right.svg';
import selectedIcon from '../../assets/square-checkbox-solid.svg';
const ConcordanceAggregationItem = (props) => { const ConcordanceAggregationItem = (props) => {
const { t } = useTranslation();
const search = useSearch(); const search = useSearch();
const isSelected = search.isFiltered('TextIds', props.filterKey) === true; const isSelected = search.isFiltered('TextIds', props.filterKey) === true;
const link = isSelected ? search.getClearFilterLink('TextIds', props.filterKey) : search.getFilterLink('TextIds', props.filterKey); const link = isSelected ? search.getClearFilterLink('TextIds', props.filterKey) : search.getFilterLink('TextIds', props.filterKey);
let icon;
if (isSelected) {
icon = <img src={selectedIcon} alt={t('shared.unselect')} />;
} else {
icon = <img src={selectIcon} alt={t('shared.select')} />;
}
return ( return (
<li className={styles.aggregationItem}><Link to={link}>{isSelected && <span>X - </span>}{props.title}<span className='float-end'>{props.count}</span></Link></li> <li className={styles.aggregationItem}><Link to={link}>{icon}<span className={styles.title}>{props.title}</span><span className={styles.count}>{props.count}</span></Link></li>
); );
}; };
@@ -7,6 +7,15 @@
a { a {
color: $text-color; color: $text-color;
display: flex;
text-decoration: none; text-decoration: none;
.title {
margin-left: 1.2rem;
flex-grow: 1;
margin-right: 0.8rem;
overflow: hidden;
text-overflow: clip;
}
} }
} }
@@ -6,7 +6,7 @@ const ConcordanceAlternateSearchItem = (props) => {
const search = useSearch(); const search = useSearch();
const link = search.getAlternateSearchLink(props.search); const link = search.getAlternateSearchLink(props.search);
return ( return (
<li className={styles.alternateSearchItem}><Link to={link}>{props.title}<span className='float-end'>{props.count}</span></Link></li> <li className={styles.alternateSearchItem}><Link to={link}><span className={styles.title}>{props.title}</span><span className={styles.count}>{props.count}</span></Link></li>
); );
}; };
@@ -7,6 +7,14 @@
a { a {
color: $text-color; color: $text-color;
display: flex;
text-decoration: none; text-decoration: none;
.title {
flex-grow: 1;
margin-right: 0.8rem;
overflow: hidden;
text-overflow: clip;
}
} }
} }
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import ConcordanceDetailsTextInfo from './ConcordanceDetailsTextInfo';
import sytles from './ConcordanceDetailsText.module.scss'; import { useTranslation } from 'react-i18next';
const ConcordanceDetailsText = (props) => { const ConcordanceDetailsText = (props) => {
// Hooks // Hooks
@@ -8,22 +8,10 @@ const ConcordanceDetailsText = (props) => {
return ( return (
<> <>
<div className={sytles.textInfo}> <ConcordanceDetailsTextInfo title={t('concordance.detailsTextSource')} value={props.source} />
<h5>{t('concordance.detailsTextSource')}</h5> <ConcordanceDetailsTextInfo title={t('concordance.detailsTextYear')} value={props.year} />
<p>{props.source}</p> <ConcordanceDetailsTextInfo title={t('concordance.detailsTextTitle')} value={props.title} />
</div> <ConcordanceDetailsTextInfo title={t('concordance.detailsTextAuthor')} value={props.author} />
<div className={sytles.textInfo}>
<h5>{t('concordance.detailsTextYear')}</h5>
<p>{props.year}</p>
</div>
<div className={sytles.textInfo}>
<h5>{t('concordance.detailsTextTitle')}</h5>
<p>{props.title}</p>
</div>
<div className={sytles.textInfo}>
<h5>{t('concordance.detailsTextAuthor')}</h5>
<p>{props.author}</p>
</div>
</> </>
); );
}; };
@@ -0,0 +1,15 @@
import styles from './ConcordanceDetailsTextInfo.module.scss';
const ConcordanceDetailsTextInfo = (props) => {
var title = props.title;
var value = props.value;
if (value) {
return (<div className={styles.textInfo}>
<h5>{title}</h5>
<p>{value}</p>
</div>);
}
};
export default ConcordanceDetailsTextInfo;
@@ -6,8 +6,8 @@ const ConcordanceListItem = (props) => {
return ( return (
<> <>
<div className={`row ${styles.item}`} onClick={() => { props.toggleDetails(props.index) }}> <div className={`row ${styles.item}`} onClick={() => { props.toggleDetails(props.index) }}>
<div className='col text-end'><ConcordanceTokens tokens={props.item.leftContext} />&nbsp;</div> <div className={`col ${styles.left}`}><span><ConcordanceTokens tokens={props.item.leftContext} />&nbsp;</span></div>
<div className='col'><ConcordanceTokens tokens={[props.item.centerContext]} /> <ConcordanceTokens tokens={props.item.rightContext} /></div> <div className={`col ${styles.right}`}><ConcordanceTokens tokens={[props.item.centerContext]} /> <ConcordanceTokens tokens={props.item.rightContext} /></div>
</div> </div>
{props.expanded && <ConcordanceDetails paragraphId={props.item.paragraphId} tokenOrder={props.item.centerContext.tokenOrder} onClose={() => { props.toggleDetails(null) }} />} {props.expanded && <ConcordanceDetails paragraphId={props.item.paragraphId} tokenOrder={props.item.centerContext.tokenOrder} onClose={() => { props.toggleDetails(null) }} />}
</> </>
@@ -5,4 +5,24 @@
cursor: pointer; cursor: pointer;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
:global(.col) {
&.left,
&.right {
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}
&.left {
span {
display: inline-block;
float: right;
min-width: 100%;
overflow: visible;
text-align: right;
}
}
}
} }
@@ -18,7 +18,7 @@ const IndexTopBar = () => {
<div className={styles.menu}> <div className={styles.menu}>
<div className='container-fluid'> <div className='container-fluid'>
<div className='row align-items-center'> <div className='row align-items-center'>
<div className='col'><img src={logo} alt='Logo' /></div> <div className='col'><a href="https://www.slovenscina.eu"><img src={logo} alt='Logo' /></a></div>
<div className={`col ${styles.links}`}><button type='button' onClick={languageClickHandler}><img src={langIcon} alt={changeLanguageTitle} />{changeLanguageCode}</button></div> <div className={`col ${styles.links}`}><button type='button' onClick={languageClickHandler}><img src={langIcon} alt={changeLanguageTitle} />{changeLanguageCode}</button></div>
</div> </div>
</div> </div>
@@ -12,7 +12,7 @@ const useDownload = (url) => {
var url = window.URL.createObjectURL(blob); var url = window.URL.createObjectURL(blob);
var a = document.createElement('a'); var a = document.createElement('a');
a.href = url; a.href = url;
a.download = "export.csv"; a.download = "export.txt";
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
a.click(); a.click();
a.remove(); //afterwards we remove the element again a.remove(); //afterwards we remove the element again
@@ -22,7 +22,9 @@ const resources = {
'searchError': 'Server returned error when searching.', 'searchError': 'Server returned error when searching.',
'searchHelp': 'Help', 'searchHelp': 'Help',
'searchHistory': 'Search history', 'searchHistory': 'Search history',
'searchRecords': 'Showing {{start}}-{{end}} from {{total}} records.' 'searchRecords': 'Showing {{start}}-{{end}} from {{total}} records.',
'select': 'Select',
'unselect': 'Remove selected'
}, },
concordance: { concordance: {
'title': 'Search concordances', 'title': 'Search concordances',
@@ -63,7 +65,9 @@ const resources = {
'searchError': 'Strežnik je vrnil napako pri iskanju.', 'searchError': 'Strežnik je vrnil napako pri iskanju.',
'searchHelp': 'Pomoč', 'searchHelp': 'Pomoč',
'searchHistory': 'Zgodovina iskanj', 'searchHistory': 'Zgodovina iskanj',
'searchRecords': 'Prikazujem {{start}}-{{end}} od {{total}} zapisov.' 'searchRecords': 'Prikazujem {{start}}-{{end}} od {{total}} zapisov.',
'select': 'Izberi',
'unselect': 'Odstrani izbrano'
}, },
concordance: { concordance: {
'title': 'Iskanje po konkordancah', 'title': 'Iskanje po konkordancah',
@@ -49,7 +49,7 @@ const ResultsLayout = (props) => {
<div className='container-fluid'> <div className='container-fluid'>
<div className='row align-items-center'> <div className='row align-items-center'>
<div className='col-sm-4 col-lg-2 col-xl-2'> <div className='col-sm-4 col-lg-2 col-xl-2'>
<Link to={search.rootUrl}><img src={logo} alt='Logo' /></Link> <a href="https://www.slovenscina.eu"><img src={logo} alt='Logo' /></a>
<ul className={styles.areaButtons}> <ul className={styles.areaButtons}>
<li className={`${styles.areaButton} ${isConcordance ? styles.active : null}`}><Link to={`/${search.corpusId}/concordance`}><img src={isConcordance ? concordanceActiveIcon : concordanceInactiveIcon} alt='Concordance' /></Link></li> <li className={`${styles.areaButton} ${isConcordance ? styles.active : null}`}><Link to={`/${search.corpusId}/concordance`}><img src={isConcordance ? concordanceActiveIcon : concordanceInactiveIcon} alt='Concordance' /></Link></li>
<li className={`${styles.areaButton} ${!isConcordance ? styles.active : null}`}><Link to={`/${search.corpusId}/list`}><img src={!isConcordance ? listActiveIcon : listInactiveIcon} alt='List' /></Link></li> <li className={`${styles.areaButton} ${!isConcordance ? styles.active : null}`}><Link to={`/${search.corpusId}/list`}><img src={!isConcordance ? listActiveIcon : listInactiveIcon} alt='List' /></Link></li>
@@ -73,7 +73,7 @@ const ConcordanceResults = () => {
content = <p>{t('shared.noResults')}</p> content = <p>{t('shared.noResults')}</p>
} }
return ( return (
<ResultsLayout source='concordance'> <ResultsLayout source='concordance' help={<ConcordanceHelp />}>
<div className='row'> <div className='row'>
<div className='col-lg-10 offset-lg-2'> <div className='col-lg-10 offset-lg-2'>
{content} {content}
@@ -11,6 +11,7 @@ import ResultsHeader from '../../components/results/ResultsHeader';
import ResultsPager from '../../components/results/ResultsPager'; import ResultsPager from '../../components/results/ResultsPager';
import ResultsExport from '../../components/results/ResultsExport'; import ResultsExport from '../../components/results/ResultsExport';
import ListItems from '../../components/list/ListItems'; import ListItems from '../../components/list/ListItems';
import ListHelp from '../../components/list/ListHelp';
import Spinner from '../../components/shared/Spinner'; import Spinner from '../../components/shared/Spinner';
const ListResults = () => { const ListResults = () => {
@@ -48,7 +49,7 @@ const ListResults = () => {
if (!isLoading && isSuccess && data.items.length > 0) { if (!isLoading && isSuccess && data.items.length > 0) {
return ( return (
<ResultsLayout source='list'> <ResultsLayout source='list' help={<ListHelp />}>
<div className='row'> <div className='row'>
<div className='col'> <div className='col'>
<ResultsHeader query={body.query} onExport={() => setIsVisible(prevIsVisible => !prevIsVisible)} /> <ResultsHeader query={body.query} onExport={() => setIsVisible(prevIsVisible => !prevIsVisible)} />
@@ -69,7 +70,7 @@ const ListResults = () => {
content = <p>{t('shared.noResults')}</p> content = <p>{t('shared.noResults')}</p>
} }
return ( return (
<ResultsLayout source='list'> <ResultsLayout source='list' help={<ListHelp />}>
<div className='row'> <div className='row'>
<div className='col-lg-10 offset-lg-2'> <div className='col-lg-10 offset-lg-2'>
{content} {content}
@@ -1,14 +1,11 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS base FROM mcr.microsoft.com/dotnet/sdk:10.0 AS base
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
# COPY LOCAL NUGET PACKAGES # COPY SHARED MSBUILD CONFIGURATION
WORKDIR /nuget COPY src/Directory.Build.props /src/Directory.Build.props
COPY local-packages/. . COPY src/Directory.Packages.props /src/Directory.Packages.props
# ADD LOCAL NUGET SOURCE
RUN dotnet nuget add source /nuget
# RESTORE PACKAGES (with project files only) # RESTORE PACKAGES (with project files only)
WORKDIR /src/apps WORKDIR /src/apps
@@ -2,16 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" /> <PackageReference Include="Autofac" />
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -0,0 +1,9 @@
namespace Rsdo.Concordancer.Core.Extensions;
public static class StringExtensions
{
public static bool IsWildcardSearch(this string value)
{
return !string.IsNullOrEmpty(value) && (value.Contains('*') || value.Contains('?'));
}
}
@@ -1,16 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Rsdo.Concordancer.ServiceModel\Rsdo.Concordancer.ServiceModel.csproj" /> <ProjectReference Include="..\Rsdo.Concordancer.ServiceModel\Rsdo.Concordancer.ServiceModel.csproj" />
</ItemGroup> </ItemGroup>
@@ -14,6 +14,8 @@ public class VersionInfoTableMetadata : IVersionTableMetaData
public string UniqueIndexName => "uc_version"; public string UniqueIndexName => "uc_version";
public bool CreateWithPrimaryKey => false;
public object ApplicationContext { get; set; } public object ApplicationContext { get; set; }
public bool OwnsSchema => true; public bool OwnsSchema => true;
@@ -0,0 +1,22 @@
using System;
using FluentMigrator;
using Rsdo.Concordancer.Core.Entities;
using Rsdo.Concordancer.Data.Framework;
namespace Rsdo.Concordancer.Data.Migrations.v1_0.Corpus;
[CorpusDb]
[Migration(20230413111500)]
public class Mig20230413111500_ChangeFormToText : ForwardOnlyMigration
{
public override void Up()
{
// Token
Alter.Column(nameof(Token.Form)).OnTable(nameof(Token)).AsString(int.MaxValue).Nullable();
Alter.Column(nameof(Token.Lemma)).OnTable(nameof(Token)).AsString(int.MaxValue).Nullable();
// Term
Alter.Column(nameof(Term.Form)).OnTable(nameof(Term)).AsString(int.MaxValue).NotNullable();
Alter.Column(nameof(Term.Lemma)).OnTable(nameof(Term)).AsString(int.MaxValue).NotNullable();
}
}
@@ -1,19 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" /> <PackageReference Include="Autofac" />
<PackageReference Include="FluentMigrator" Version="3.3.2" /> <PackageReference Include="FluentMigrator" />
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" /> <PackageReference Include="FluentMigrator.Runner" />
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="3.3.2" /> <PackageReference Include="FluentMigrator.Runner.Postgres" />
<PackageReference Include="Npgsql" Version="6.0.7" /> <PackageReference Include="Npgsql" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenSearch.Client; using OpenSearch.Client;
using Rsdo.Concordancer.Core.Extensions;
namespace Rsdo.Concordancer.Infrastructure.Extensions; namespace Rsdo.Concordancer.Infrastructure.Extensions;
@@ -30,4 +32,36 @@ public static class ElasticQueryExtensions
}, },
}; };
} }
public static QueryContainer ToQuery(this string value, string field)
{
if (value.IsWildcardSearch())
{
return new WildcardQuery()
{
Field = field,
Value = value,
};
}
return new TermQuery()
{
Field = field,
Value = value,
};
}
public static QueryContainer ToQuery(this List<string> values, string field)
{
if (values.Any(v => v.IsWildcardSearch()))
{
return values.Select(value => value.ToQuery(field)).ToList().ToBooleanOrQuery();
}
return new TermsQuery()
{
Field = field,
Terms = values,
};
}
} }
@@ -1,18 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" /> <PackageReference Include="Autofac" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="OpenSearch.Client" Version="1.1.0" /> <PackageReference Include="OpenSearch.Client" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenSearch.Client; using OpenSearch.Client;
using Rsdo.Concordancer.Infrastructure.Search.Extensions; using Rsdo.Concordancer.Infrastructure.Search.Extensions;
@@ -27,6 +28,9 @@ public abstract class BaseAddRecordsHandler<TEntity, TElasticEntity> : IAddRecor
// Get index name // Get index name
var indexName = indexProvider.IndexName; var indexName = indexProvider.IndexName;
// Batch entities by 1000
foreach (var batch in entities.Chunk(1000))
{
// Create request // Create request
var request = new BulkRequest(indexName) var request = new BulkRequest(indexName)
{ {
@@ -35,7 +39,7 @@ public abstract class BaseAddRecordsHandler<TEntity, TElasticEntity> : IAddRecor
}; };
// Convert entities to elastic entities // Convert entities to elastic entities
foreach (var entity in entities) foreach (var entity in batch)
{ {
var elasticEntity = ConvertEntity(entity); var elasticEntity = ConvertEntity(entity);
request.Operations.Add(new BulkIndexOperation<TElasticEntity>(elasticEntity)); request.Operations.Add(new BulkIndexOperation<TElasticEntity>(elasticEntity));
@@ -45,6 +49,7 @@ public abstract class BaseAddRecordsHandler<TEntity, TElasticEntity> : IAddRecor
var response = await client.BulkAsync(request); var response = await client.BulkAsync(request);
response.ThrowIfInvalid(); response.ThrowIfInvalid();
} }
}
protected abstract TElasticEntity ConvertEntity(TEntity entity); protected abstract TElasticEntity ConvertEntity(TEntity entity);
} }
@@ -35,22 +35,12 @@ public class TermListQueryBuilder : IQueryBuilder<TermListQuery>
if (!string.IsNullOrEmpty(wordQuery.Form)) if (!string.IsNullOrEmpty(wordQuery.Form))
{ {
queries.Add( queries.Add(wordQuery.Form.ToLower().ToQuery($"{tokenField}.formLower"));
new TermQuery()
{
Field = $"{tokenField}.formLower",
Value = wordQuery.Form.ToLower(),
});
} }
if (!wordQuery.Lemmas.IsNullOrEmpty()) if (!wordQuery.Lemmas.IsNullOrEmpty())
{ {
queries.Add( queries.Add(wordQuery.Lemmas.ToQuery($"{tokenField}.lemma"));
new TermsQuery()
{
Field = $"{tokenField}.lemma",
Terms = wordQuery.Lemmas,
});
} }
return queries.Count == 0 ? new MatchNoneQuery() : queries.ToBooleanAndQuery(); return queries.Count == 0 ? new MatchNoneQuery() : queries.ToBooleanAndQuery();
@@ -1,15 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,10 +1,14 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Autofac; using Autofac;
using Autofac.Core;
using FluentValidation; using FluentValidation;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Rsdo.Concordancer.Core.Interfaces; using Rsdo.Concordancer.Core.Interfaces;
using Rsdo.Concordancer.Core.Search.Queries.Concordances;
using Rsdo.Concordancer.Core.Search.Queries.TermLists;
using Rsdo.Concordancer.ServiceModel.Requests.Concordances; using Rsdo.Concordancer.ServiceModel.Requests.Concordances;
using Rsdo.Concordancer.ServiceModel.Requests.TermLists;
using Rsdo.Concordancer.ServiceModel.Types; using Rsdo.Concordancer.ServiceModel.Types;
using Rsdo.Concordancer.Services.Framework; using Rsdo.Concordancer.Services.Framework;
using Rsdo.Concordancer.Services.Framework.BulkLoaders; using Rsdo.Concordancer.Services.Framework.BulkLoaders;
@@ -15,6 +19,8 @@ using Rsdo.Concordancer.Services.Framework.Decorators;
using Rsdo.Concordancer.Services.Search.Aggregations; using Rsdo.Concordancer.Services.Search.Aggregations;
using Rsdo.Concordancer.Services.Search.AlternateSearches; using Rsdo.Concordancer.Services.Search.AlternateSearches;
using Rsdo.Concordancer.Services.Search.QueryFactories; using Rsdo.Concordancer.Services.Search.QueryFactories;
using Rsdo.Concordancer.Services.Search.QueryFactories.Concordances;
using Rsdo.Concordancer.Services.Search.QueryFactories.TermLists;
using Rsdo.Concordancer.Services.Services.InputQueryParser; using Rsdo.Concordancer.Services.Services.InputQueryParser;
using Rsdo.Concordancer.Services.Services.LemmatizationService; using Rsdo.Concordancer.Services.Services.LemmatizationService;
using Rsdo.Concordancer.Services.Services.ParagraphService; using Rsdo.Concordancer.Services.Services.ParagraphService;
@@ -83,7 +89,8 @@ public class ServicesModule : Module
builder.RegisterType<DefaultTokenizerService>().Keyed<ITokenizerService>(TokenizerType.Default).SingleInstance(); builder.RegisterType<DefaultTokenizerService>().Keyed<ITokenizerService>(TokenizerType.Default).SingleInstance();
builder.RegisterType<InputQueryParser>().As<IInputQueryParser>().SingleInstance(); builder.RegisterType<InputQueryParser>().As<IInputQueryParser>().SingleInstance();
builder.RegisterType<ParagraphService>().As<IParagraphService>().InstancePerLifetimeScope(); builder.RegisterType<ParagraphService>().As<IParagraphService>().InstancePerLifetimeScope();
builder.RegisterType<LemmatizationService>().As<ILemmatizationService>().InstancePerLifetimeScope(); builder.RegisterType<SimpleLemmatizationService>().Keyed<ILemmatizationService>(LemmatizationType.Simple).InstancePerLifetimeScope();
builder.RegisterType<WildcardLemmatizationService>().Keyed<ILemmatizationService>(LemmatizationType.Wildcard).InstancePerLifetimeScope();
builder.RegisterType<PartOfSpeechService>().As<IPartOfSpeechService>().InstancePerLifetimeScope(); builder.RegisterType<PartOfSpeechService>().As<IPartOfSpeechService>().InstancePerLifetimeScope();
} }
@@ -103,7 +110,33 @@ public class ServicesModule : Module
private void RegisterSearch(ContainerBuilder builder) private void RegisterSearch(ContainerBuilder builder)
{ {
// Query factories // Query factories
builder.RegisterAssemblyTypes(ServicesAssembly).AsClosedTypesOf(typeof(IQueryFactory<,>)).AsImplementedInterfaces().InstancePerLifetimeScope(); var wildcardLemmatizationServiceParameter = new ResolvedParameter(
(p, ctx) => p.ParameterType == typeof(ILemmatizationService),
(p, ctx) => ctx.ResolveKeyed<ILemmatizationService>(LemmatizationType.Wildcard));
var simpleLemmatizationServiceParameter = new ResolvedParameter(
(p, ctx) => p.ParameterType == typeof(ILemmatizationService),
(p, ctx) => ctx.ResolveKeyed<ILemmatizationService>(LemmatizationType.Simple));
builder.RegisterType<ExportConcordancesQueryFactory>()
.As<IQueryFactory<ExportConcordances, ConcordancesQuery>>()
.WithParameter(simpleLemmatizationServiceParameter)
.InstancePerLifetimeScope();
builder.RegisterType<SearchConcordancesQueryFactory>()
.As<IQueryFactory<SearchConcordances, ConcordancesQuery>>()
.WithParameter(simpleLemmatizationServiceParameter)
.InstancePerLifetimeScope();
builder.RegisterType<ConcordanceDetailsQueryFactory>()
.As<IQueryFactory<ConcordanceDetails, ConcordancesQuery>>()
.WithParameter(simpleLemmatizationServiceParameter)
.InstancePerLifetimeScope();
builder.RegisterType<ExportTermListQueryFactory>()
.As<IQueryFactory<ExportTermList, TermListQuery>>()
.WithParameter(wildcardLemmatizationServiceParameter)
.InstancePerLifetimeScope();
builder.RegisterType<SearchTermListQueryFactory>()
.As<IQueryFactory<SearchTermList, TermListQuery>>()
.WithParameter(wildcardLemmatizationServiceParameter)
.InstancePerLifetimeScope();
// Aggregations // Aggregations
builder.RegisterType<AggregationProviderFactory>().As<IAggregationProviderFactory>().InstancePerLifetimeScope(); builder.RegisterType<AggregationProviderFactory>().As<IAggregationProviderFactory>().InstancePerLifetimeScope();
@@ -112,6 +145,7 @@ public class ServicesModule : Module
// Alternate searches // Alternate searches
builder.RegisterType<LemmasAlternateSearchProvider>() builder.RegisterType<LemmasAlternateSearchProvider>()
.As<IAlternateSearchProvider<SearchConcordances, SearchConcordancesResponse>>() .As<IAlternateSearchProvider<SearchConcordances, SearchConcordancesResponse>>()
.WithParameter(simpleLemmatizationServiceParameter)
.InstancePerLifetimeScope(); .InstancePerLifetimeScope();
} }
} }
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Npgsql; using Npgsql;
using Rsdo.Concordancer.Core.Interfaces; using Rsdo.Concordancer.Core.Interfaces;
using Rsdo.Concordancer.ServiceModel.Types;
using Rsdo.Concordancer.Services.Framework.DbContext; using Rsdo.Concordancer.Services.Framework.DbContext;
namespace Rsdo.Concordancer.Services.Framework.DatabaseManager; namespace Rsdo.Concordancer.Services.Framework.DatabaseManager;
@@ -57,7 +58,7 @@ public class PostgreSqlDatabaseManager : IDatabaseManager
public async Task UpdateCorpusDatabases() public async Task UpdateCorpusDatabases()
{ {
// Update all corpus databases // Update all corpus databases
var corpusIds = await dbContext.Corpus.Select(c => c.Id).ToListAsync(); var corpusIds = await dbContext.Corpus.Where(s => s.Status == CorpusStatus.Active).Select(c => c.Id).ToListAsync();
foreach (var corpusId in corpusIds) foreach (var corpusId in corpusIds)
{ {
var connectionString = connectionStringProvider.GetCorpusConnectionString(corpusId); var connectionString = connectionStringProvider.GetCorpusConnectionString(corpusId);
@@ -1,8 +1,11 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Rsdo.Concordancer.Core.Constants; using Rsdo.Concordancer.Core.Constants;
using Rsdo.Concordancer.Core.Interfaces; using Rsdo.Concordancer.Core.Interfaces;
using Rsdo.Concordancer.Core.Model; using Rsdo.Concordancer.Core.Model;
@@ -11,6 +14,7 @@ using Rsdo.Concordancer.Core.Search.Queries.Concordances;
using Rsdo.Concordancer.ServiceModel.Requests.Concordances; using Rsdo.Concordancer.ServiceModel.Requests.Concordances;
using Rsdo.Concordancer.ServiceModel.Shared; using Rsdo.Concordancer.ServiceModel.Shared;
using Rsdo.Concordancer.ServiceModel.Types; using Rsdo.Concordancer.ServiceModel.Types;
using Rsdo.Concordancer.Services.Framework.DbContext;
using Rsdo.Concordancer.Services.Search.QueryFactories; using Rsdo.Concordancer.Services.Search.QueryFactories;
using Rsdo.Concordancer.Services.Services.ParagraphService; using Rsdo.Concordancer.Services.Services.ParagraphService;
@@ -18,15 +22,18 @@ namespace Rsdo.Concordancer.Services.RequestHandlers.Concordances;
public class ExportConcordancesHandler : BaseSearchConcordancesHandler, IRequestHandler<ExportConcordances, ExportConcordancesResponse> public class ExportConcordancesHandler : BaseSearchConcordancesHandler, IRequestHandler<ExportConcordances, ExportConcordancesResponse>
{ {
private readonly CorpusDbContext dbContext;
private readonly IQueryFactory<ExportConcordances, ConcordancesQuery> queryFactory; private readonly IQueryFactory<ExportConcordances, ConcordancesQuery> queryFactory;
private readonly ISearchEngine searchEngine; private readonly ISearchEngine searchEngine;
public ExportConcordancesHandler( public ExportConcordancesHandler(
CorpusDbContext dbContext,
IParagraphService paragraphService, IParagraphService paragraphService,
IQueryFactory<ExportConcordances, ConcordancesQuery> queryFactory, IQueryFactory<ExportConcordances, ConcordancesQuery> queryFactory,
ISearchEngine searchEngine) ISearchEngine searchEngine)
: base(paragraphService) : base(paragraphService)
{ {
this.dbContext = dbContext;
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.searchEngine = searchEngine; this.searchEngine = searchEngine;
} }
@@ -42,6 +49,9 @@ public class ExportConcordancesHandler : BaseSearchConcordancesHandler, IRequest
// Get search items // Get search items
var items = await GetItems(query, result.EntityIds); var items = await GetItems(query, result.EntityIds);
// Get sources
var sources = await GetSources(items);
// Export // Export
var stream = new MemoryStream(); var stream = new MemoryStream();
await using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) await using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
@@ -52,7 +62,7 @@ public class ExportConcordancesHandler : BaseSearchConcordancesHandler, IRequest
// Write items // Write items
foreach (var item in items) foreach (var item in items)
{ {
await WriteItem(writer, item); await WriteItem(writer, item, sources[item.ParagraphId]);
} }
} }
@@ -91,16 +101,35 @@ public class ExportConcordancesHandler : BaseSearchConcordancesHandler, IRequest
await writer.WriteAsync("Iskani niz"); await writer.WriteAsync("Iskani niz");
await writer.WriteAsync("\t"); await writer.WriteAsync("\t");
await writer.WriteAsync("Desno besedilo"); await writer.WriteAsync("Desno besedilo");
await writer.WriteAsync("\t");
await writer.WriteAsync("Vir");
await writer.WriteLineAsync(); await writer.WriteLineAsync();
} }
private static async Task WriteItem(StreamWriter writer, SearchConcordancesResponseItem item) private static async Task WriteItem(StreamWriter writer, SearchConcordancesResponseItem item, string source)
{ {
await WriteContext(writer, item.LeftContext); await WriteContext(writer, item.LeftContext);
await writer.WriteAsync("\t"); await writer.WriteAsync("\t");
await writer.WriteAsync(item.CenterContext.Form); await writer.WriteAsync(item.CenterContext.Form);
await writer.WriteAsync("\t"); await writer.WriteAsync("\t");
await WriteContext(writer, item.RightContext); await WriteContext(writer, item.RightContext);
await writer.WriteAsync("\t");
await writer.WriteAsync(source);
await writer.WriteLineAsync(); await writer.WriteLineAsync();
} }
private async Task<Dictionary<Guid, string>> GetSources(List<SearchConcordancesResponseItem> items)
{
var paragraphIds = items.Select(i => i.ParagraphId).ToList();
var sources = await dbContext.Paragraph.Include(p => p.Text)
.Where(p => paragraphIds.Contains(p.Id))
.Select(
p => new
{
p.Id,
p.Text.SourceFile,
})
.ToListAsync();
return sources.ToDictionary(x => x.Id, x => Path.GetFileNameWithoutExtension(x.SourceFile));
}
} }
@@ -109,7 +109,7 @@ public class ImportTermListHandler : IRequestHandler<ImportTermList, ExecutionRe
Lemma = termObj["kandidat"].Value<string>(), Lemma = termObj["kandidat"].Value<string>(),
Msd = termObj["POSoznake"].Value<string>(), Msd = termObj["POSoznake"].Value<string>(),
Weight = termObj["ranking"].Value<decimal>(), Weight = termObj["ranking"].Value<decimal>(),
Frequency = termObj["pogostostpojavljanja"].Value<int>(), Frequency = termObj["pogostostpojavljanja"].Values<int>().First(),
TermListAutoId = termList.AutoId, TermListAutoId = termList.AutoId,
}.ApplyCreateValues(); }.ApplyCreateValues();
@@ -90,39 +90,35 @@ public class IndexTextHandler : IRequestHandler<IndexText, ExecutionResult>
private static List<Concordance> GetConcordances(Text text, Paragraph paragraph, List<Token> tokens) private static List<Concordance> GetConcordances(Text text, Paragraph paragraph, List<Token> tokens)
{ {
// Get window size (max 10)
var windowSize = Math.Min(tokens.Count - 1, 10);
// Create list of tokens with positions
var tokensIdx = tokens.Select((t, i) => new KeyValuePair<int, Token>(i, t)).ToList();
// Get tokens which will appear in current concordance
List<KeyValuePair<int, Token>> concordanceTokens;
var concordances = new List<Concordance>(); var concordances = new List<Concordance>();
while ((concordanceTokens = tokensIdx.Where(x => x.Key >= -windowSize && x.Key <= windowSize).ToList().OrderBy(x => x.Key).ToList()).Any()) for (var i = 0; i < tokens.Count; i++)
{ {
if (concordanceTokens.Last().Key < 0)
{
break;
}
var concordance = new Concordance() var concordance = new Concordance()
{ {
ParagraphId = paragraph.Id, ParagraphId = paragraph.Id,
TextId = text.Id, TextId = text.Id,
}; };
// Loop through tokens and set it in the position // set center token
foreach (var concordanceToken in concordanceTokens) concordance.SetToken(tokens[i], 0);
// set left and right context tokens
for (var c = 1; c <= 10; c++)
{ {
concordance.SetToken(concordanceToken.Value, concordanceToken.Key); // left context
if (i - c >= 0)
{
concordance.SetToken(tokens[i - c], -c);
}
// right context
if (i + c < tokens.Count)
{
concordance.SetToken(tokens[i + c], c);
}
} }
concordances.Add(concordance); concordances.Add(concordance);
// Decrease indexes of tokens (shift tokens to left, relative to window size)
tokensIdx = tokensIdx.Select(x => new KeyValuePair<int, Token>(x.Key - 1, x.Value)).ToList();
} }
return concordances; return concordances;
@@ -1,25 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" /> <PackageReference Include="Autofac" />
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" /> <PackageReference Include="EFCore.NamingConventions" />
<PackageReference Include="FluentValidation" Version="11.2.2" /> <PackageReference Include="FluentValidation" />
<PackageReference Include="Hangfire.Core" Version="1.7.31" /> <PackageReference Include="Hangfire.Core" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Npgsql" Version="6.0.7" /> <PackageReference Include="Npgsql" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Autofac.Features.Indexed; using Autofac.Features.Indexed;
using Rsdo.Concordancer.Core.Extensions;
using Rsdo.Concordancer.ServiceModel.Requests.Concordances; using Rsdo.Concordancer.ServiceModel.Requests.Concordances;
using Rsdo.Concordancer.ServiceModel.Types; using Rsdo.Concordancer.ServiceModel.Types;
using Rsdo.Concordancer.Services.Services.TokenizerService; using Rsdo.Concordancer.Services.Services.TokenizerService;
@@ -21,6 +22,12 @@ public class InputQueryParser : IInputQueryParser
public async Task<IEnumerable<(TokenType type, string form)>> Tokenize(string query) public async Task<IEnumerable<(TokenType type, string form)>> Tokenize(string query)
{ {
if (query.IsWildcardSearch())
{
// Classla tokenizer doesn't support wildcards, so use default tokenizer
return await tokenizers[TokenizerType.Default].Tokenize(query);
}
try try
{ {
return await tokenizers[TokenizerType.Classla].Tokenize(query); return await tokenizers[TokenizerType.Classla].Tokenize(query);
@@ -0,0 +1,7 @@
namespace Rsdo.Concordancer.Services.Services.LemmatizationService;
public enum LemmatizationType
{
Simple,
Wildcard,
}
@@ -7,17 +7,18 @@ using Rsdo.Concordancer.Services.Framework.DbContext;
namespace Rsdo.Concordancer.Services.Services.LemmatizationService; namespace Rsdo.Concordancer.Services.Services.LemmatizationService;
public class LemmatizationService : ILemmatizationService public class SimpleLemmatizationService : ILemmatizationService
{ {
private readonly MasterDbContext dbContext; private readonly MasterDbContext dbContext;
public LemmatizationService(MasterDbContext dbContext) public SimpleLemmatizationService(MasterDbContext dbContext)
{ {
this.dbContext = dbContext; this.dbContext = dbContext;
} }
public async Task<List<string>> GetLemmas(string form) public virtual async Task<List<string>> GetLemmas(string form)
{ {
// Retrieving lemmas without support for wildcard search
var lemmas = await dbContext.LemmaFormPair.Where(f => f.Form.ToLower() == form.ToLower()).Select(f => f.Lemma).Distinct().ToListAsync(); var lemmas = await dbContext.LemmaFormPair.Where(f => f.Form.ToLower() == form.ToLower()).Select(f => f.Lemma).Distinct().ToListAsync();
if (lemmas.IsNullOrEmpty()) if (lemmas.IsNullOrEmpty())
{ {
@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Rsdo.Concordancer.Core.Extensions;
using Rsdo.Concordancer.Services.Framework.DbContext;
namespace Rsdo.Concordancer.Services.Services.LemmatizationService;
public class WildcardLemmatizationService : SimpleLemmatizationService
{
private readonly MasterDbContext dbContext;
public WildcardLemmatizationService(MasterDbContext dbContext)
: base(dbContext)
{
this.dbContext = dbContext;
}
public override async Task<List<string>> GetLemmas(string form)
{
if (!form.IsWildcardSearch())
{
return await base.GetLemmas(form);
}
var lemmas = await dbContext.LemmaFormPair.Where(f => EF.Functions.Like(f.Form.ToLower(), form.Replace("*", "%").Replace("?", "_").ToLower()))
.Select(f => f.Lemma)
.Distinct()
.ToListAsync();
if (lemmas.IsNullOrEmpty())
{
lemmas.Add(form);
}
return lemmas;
}
}
@@ -1,21 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" /> <PackageReference Include="xunit" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit.runner.visualstudio">
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2"> <PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
@@ -1,21 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" /> <PackageReference Include="xunit" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit.runner.visualstudio">
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2"> <PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>