├── .editorconfig ├── .gitignore ├── ElasticsearchCodeSearch.sln ├── LICENSE ├── README.md ├── archive └── indexer-powershell │ └── git_indexer.ps1 ├── doc └── img │ ├── ElasticsearchCodeSearch_DebuggingManageIndex.jpg │ ├── ElasticsearchCodeSearch_IndexGitHubOrganization.jpg │ ├── ElasticsearchCodeSearch_IndexGitHubRepository.jpg │ ├── ElasticsearchCodeSearch_IndexGitRepository.jpg │ ├── ElasticsearchCodeSearch_SearchClusterOverview.jpg │ └── ElasticsearchCodeSearch_SearchCode.jpg ├── docker-compose.yml ├── docker ├── .env ├── codesearch-api │ └── Dockerfile ├── codesearch-web │ └── Dockerfile └── elasticsearch │ ├── elastic-cert │ ├── ca │ │ ├── ca.crt │ │ └── ca.key │ ├── es01.crt │ └── es01.key │ └── instances.yml └── src ├── ElasticsearchCodeSearch.Shared ├── Constants │ └── SourceSystems.cs ├── Dto │ ├── CodeIndexQueueDto.cs │ ├── CodeSearchDocumentDto.cs │ ├── CodeSearchRepositoryDto.cs │ ├── CodeSearchRequestDto.cs │ ├── CodeSearchResultDto.cs │ ├── CodeSearchResultsDto.cs │ ├── CodeSearchStatisticsDto.cs │ ├── GitRepositoryMetadataDto.cs │ ├── HighlightedContentDto.cs │ ├── IndexGitHubOrganizationRequestDto.cs │ ├── IndexGitHubRepositoryRequestDto.cs │ ├── IndexGitRepositoryRequestDto.cs │ ├── SortFieldDto.cs │ └── SortOrderDto.cs ├── Elasticsearch │ ├── Converters │ │ ├── CodeSearchDocumentConverter.cs │ │ ├── CodeSearchRequestConverter.cs │ │ ├── CodeSearchResultConverter.cs │ │ ├── CodeSearchStatisticsConverter.cs │ │ ├── HighlightedContentConverter.cs │ │ ├── SortFieldConverter.cs │ │ └── SortOrderEnumConverter.cs │ ├── ElasticCodeSearchClient.cs │ ├── ElasticCodeSearchOptions.cs │ ├── ElasticsearchConstants.cs │ ├── Models │ │ ├── CodeSearchDocument.cs │ │ ├── CodeSearchRequest.cs │ │ ├── HighlightedContent.cs │ │ ├── SortField.cs │ │ └── SortOrderEnum.cs │ ├── Services │ │ └── ElasticsearchCodeSearchService.cs │ └── Utils │ │ └── ElasticsearchUtils.cs ├── ElasticsearchCodeSearch.Shared.csproj ├── Exceptions │ └── ApiException.cs └── Logging │ └── LoggerExtensions.cs ├── ElasticsearchCodeSearch.Web.Client ├── Components │ ├── NotificationCenter │ │ ├── NotificationCenter.razor │ │ └── NotificationCenterPanel.razor │ ├── Paginator │ │ ├── Paginator.razor │ │ ├── Paginator.razor.cs │ │ ├── Paginator.razor.css │ │ └── PaginatorState.cs │ ├── SearchResult │ │ ├── SearchResult.razor │ │ └── SearchResult.razor.css │ ├── SiteSettings │ │ ├── SiteSettings.razor │ │ ├── SiteSettings.razor.cs │ │ ├── SiteSettingsPanel.razor │ │ └── SiteSettingsPanel.razor.cs │ └── SortOptionSelector │ │ ├── SortOptionSelector.razor │ │ ├── SortOptionSelector.razor.cs │ │ └── SortOptionSelector.razor.css ├── ElasticsearchCodeSearch.Web.Client.csproj ├── Extensions │ ├── StringExtensions.cs │ └── StringLocalizerExtensions.cs ├── Infrastructure │ ├── ApplicationErrorMessageService.cs │ ├── ApplicationErrorTranslator.cs │ ├── DataSizeUtils.cs │ ├── EventCallbackSubscribable.cs │ ├── EventCallbackSubscriber.cs │ ├── SimpleValidator.cs │ ├── StringLocalizerExtensions.cs │ └── TimeFormattingUtils.cs ├── Localization │ ├── LocalizationConstants.cs │ ├── SharedResource.cs │ └── SharedResource.resx ├── Models │ ├── ElasticsearchIndexMetrics.cs │ ├── ElasticsearchMetric.cs │ └── SortOptionEnum.cs ├── Pages │ ├── CodeSearch.razor │ ├── CodeSearch.razor.cs │ ├── CodeSearch.razor.css │ ├── GitHubOrganizationCodeIndex.razor │ ├── GitHubOrganizationCodeIndex.razor.cs │ ├── GitHubOrganizationCodeIndex.razor.css │ ├── GitHubRepositoryCodeIndex.razor │ ├── GitHubRepositoryCodeIndex.razor.cs │ ├── GitRepositoryCodeIndex.razor │ ├── GitRepositoryCodeIndex.razor.cs │ ├── Index.razor │ ├── Index.razor.cs │ ├── Index.razor.css │ ├── ManageSearchIndex.razor │ └── ManageSearchIndex.razor.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Routes.razor ├── Routes.razor.cs ├── Shared │ ├── MainLayout.razor │ ├── MainLayout.razor.cs │ ├── MainLayout.razor.js │ └── NavMenu.razor ├── _Imports.razor └── wwwroot │ └── appsettings.json ├── ElasticsearchCodeSearch.Web.Server ├── App.razor ├── ElasticsearchCodeSearch.Web.Server.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── _Imports.razor ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ └── app.css │ ├── favicon.ico │ ├── icon-192.png │ ├── js │ └── theme.js │ └── sample-data │ └── weather.json └── ElasticsearchCodeSearch ├── Controllers ├── CodeIndexerController.cs └── CodeSearchController.cs ├── ElasticsearchCodeSearch.csproj ├── Git ├── Exceptions │ └── GitException.cs └── GitExecutor.cs ├── GitHub ├── Dto │ ├── PaginatedResultsDto.cs │ ├── RepositoryMetadataDto.cs │ └── RepositoryOwnerDto.cs ├── Exceptions │ └── GitHubApiException.cs ├── GitHubClient.cs └── Options │ └── GitHubClientOptions.cs ├── Hosting └── ElasticsearchIndexerBackgroundService.cs ├── Infrastructure ├── GitRepositoryJobQueue.cs └── PermalinkGenerator.cs ├── Models └── GitRepositoryMetadata.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Services ├── GitHubService.cs ├── GitIndexerOptions.cs └── GitIndexerService.cs ├── appsettings.Docker.json └── appsettings.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # IDE0090: Use 'new(...)' 4 | dotnet_diagnostic.IDE0090.severity = silent 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studo 2015 cache/options directory 25 | .vs/ 26 | 27 | # MSTest test Results 28 | [Tt]est[Rr]esult*/ 29 | [Bb]uild[Ll]og.* 30 | 31 | # NUNIT 32 | *.VisualState.xml 33 | TestResult.xml 34 | 35 | # Build Results of an ATL Project 36 | [Dd]ebugPS/ 37 | [Rr]eleasePS/ 38 | dlldata.c 39 | 40 | *_i.c 41 | *_p.c 42 | *_i.h 43 | *.ilk 44 | *.meta 45 | *.obj 46 | *.pch 47 | *.pdb 48 | *.pgc 49 | *.pgd 50 | *.rsp 51 | *.sbr 52 | *.tlb 53 | *.tli 54 | *.tlh 55 | *.tmp 56 | *.tmp_proj 57 | *.log 58 | *.vspscc 59 | *.vssscc 60 | .builds 61 | *.pidb 62 | *.svclog 63 | *.scc 64 | 65 | # Chutzpah Test files 66 | _Chutzpah* 67 | 68 | # Visual C++ cache files 69 | ipch/ 70 | *.aps 71 | *.ncb 72 | *.opensdf 73 | *.sdf 74 | *.cachefile 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | *.vspx 80 | 81 | # TFS 2012 Local Workspace 82 | $tf/ 83 | 84 | # Guidance Automation Toolkit 85 | *.gpState 86 | 87 | # ReSharper is a .NET coding add-in 88 | _ReSharper*/ 89 | *.[Rr]e[Ss]harper 90 | *.DotSettings.user 91 | 92 | # JustCode is a .NET coding addin-in 93 | .JustCode 94 | 95 | # TeamCity is a build add-in 96 | _TeamCity* 97 | 98 | # DotCover is a Code Coverage Tool 99 | *.dotCover 100 | 101 | # NCrunch 102 | _NCrunch_* 103 | .*crunch*.local.xml 104 | 105 | # MightyMoose 106 | *.mm.* 107 | AutoTest.Net/ 108 | 109 | # Web workbench (sass) 110 | .sass-cache/ 111 | 112 | # Installshield output folder 113 | [Ee]xpress/ 114 | 115 | # DocProject is a documentation generator add-in 116 | DocProject/buildhelp/ 117 | DocProject/Help/*.HxT 118 | DocProject/Help/*.HxC 119 | DocProject/Help/*.hhc 120 | DocProject/Help/*.hhk 121 | DocProject/Help/*.hhp 122 | DocProject/Help/Html2 123 | DocProject/Help/html 124 | 125 | # Click-Once directory 126 | publish/ 127 | 128 | # Publish Web Output 129 | *.[Pp]ublish.xml 130 | *.azurePubxml 131 | # TODO: Comment the next line if you want to checkin your web deploy settings 132 | # but database connection strings (with potential passwords) will be unencrypted 133 | *.pubxml 134 | *.publishproj 135 | 136 | # NuGet Packages 137 | !**/System.Web.OData.Design.Scaffolding/ReferencePackages/* 138 | !**/Microsoft.Restier.Scaffolding/ReferencePackages/* 139 | !**/packages/Microsoft.ApplicationInsights.1.0.1-beta/* 140 | # The packages folder can be ignored because of Package Restore 141 | **/packages/* 142 | # Uncomment the nuget have not been 143 | !**/packages/Microsoft.ApplicationInsights.1.0.1-beta/ 144 | # except build/, which is used as an MSBuild target. 145 | !**/packages/build/ 146 | # Uncomment if necessary however generally it will be regenerated when needed 147 | #!**/packages/repositories.config 148 | 149 | # Windows Azure Build Output 150 | csx/ 151 | *.build.csdef 152 | 153 | # Windows Store app package directory 154 | AppPackages/ 155 | 156 | # Others 157 | *.[Cc]ache 158 | ClientBin/ 159 | [Ss]tyle[Cc]op.* 160 | ~$* 161 | *~ 162 | *.dbmdl 163 | *.dbproj.schemaview 164 | *.pfx 165 | *.publishsettings 166 | node_modules/ 167 | bower_components/ 168 | 169 | # RIA/Silverlight projects 170 | Generated_Code/ 171 | 172 | # Backup & report files from converting an old project file 173 | # to a newer Visual Studio version. Backup files are not needed, 174 | # because we have git ;-) 175 | _UpgradeReport_Files/ 176 | Backup*/ 177 | UpgradeLog*.XML 178 | UpgradeLog*.htm 179 | 180 | # SQL Server files 181 | *.mdf 182 | *.ldf 183 | 184 | # Business Intelligence projects 185 | *.rdl.data 186 | *.bim.layout 187 | *.bim_*.settings 188 | 189 | # Microsoft Fakes 190 | FakesAssemblies/ 191 | 192 | # Node.js Tools for Visual Studio 193 | .ntvs_analysis.dat 194 | 195 | # Visual Studio 6 build log 196 | *.plg 197 | 198 | # Visual Studio 6 workspace options file 199 | *.opt 200 | 201 | # Database Project 202 | *.jfm 203 | elastic-data 204 | codesearch-data -------------------------------------------------------------------------------- /ElasticsearchCodeSearch.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{954D75F2-082A-4CC3-95E4-8DB0D00CEEFF}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElasticsearchCodeSearch", "src\ElasticsearchCodeSearch\ElasticsearchCodeSearch.csproj", "{84B6D091-AF93-4E62-A324-2D5C87FDB44E}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElasticsearchCodeSearch.Shared", "src\ElasticsearchCodeSearch.Shared\ElasticsearchCodeSearch.Shared.csproj", "{846DA4F6-C779-4DD8-8C15-0D14F6DED3D5}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElasticsearchCodeSearch.Web.Client", "src\ElasticsearchCodeSearch.Web.Client\ElasticsearchCodeSearch.Web.Client.csproj", "{D993EBE4-4F76-460B-A978-3FE4E318DCF0}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElasticsearchCodeSearch.Web.Server", "src\ElasticsearchCodeSearch.Web.Server\ElasticsearchCodeSearch.Web.Server.csproj", "{A6EECD18-0800-4142-8AE0-D03B3C55761D}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {84B6D091-AF93-4E62-A324-2D5C87FDB44E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {84B6D091-AF93-4E62-A324-2D5C87FDB44E}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {84B6D091-AF93-4E62-A324-2D5C87FDB44E}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {84B6D091-AF93-4E62-A324-2D5C87FDB44E}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {846DA4F6-C779-4DD8-8C15-0D14F6DED3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {846DA4F6-C779-4DD8-8C15-0D14F6DED3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {846DA4F6-C779-4DD8-8C15-0D14F6DED3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {846DA4F6-C779-4DD8-8C15-0D14F6DED3D5}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {D993EBE4-4F76-460B-A978-3FE4E318DCF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {D993EBE4-4F76-460B-A978-3FE4E318DCF0}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {D993EBE4-4F76-460B-A978-3FE4E318DCF0}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {D993EBE4-4F76-460B-A978-3FE4E318DCF0}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {A6EECD18-0800-4142-8AE0-D03B3C55761D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {A6EECD18-0800-4142-8AE0-D03B3C55761D}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {A6EECD18-0800-4142-8AE0-D03B3C55761D}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {A6EECD18-0800-4142-8AE0-D03B3C55761D}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {85104025-333E-4C05-8CC8-5DD6BB619F0E} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Philipp Wagner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /doc/img/ElasticsearchCodeSearch_DebuggingManageIndex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/doc/img/ElasticsearchCodeSearch_DebuggingManageIndex.jpg -------------------------------------------------------------------------------- /doc/img/ElasticsearchCodeSearch_IndexGitHubOrganization.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/doc/img/ElasticsearchCodeSearch_IndexGitHubOrganization.jpg -------------------------------------------------------------------------------- /doc/img/ElasticsearchCodeSearch_IndexGitHubRepository.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/doc/img/ElasticsearchCodeSearch_IndexGitHubRepository.jpg -------------------------------------------------------------------------------- /doc/img/ElasticsearchCodeSearch_IndexGitRepository.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/doc/img/ElasticsearchCodeSearch_IndexGitRepository.jpg -------------------------------------------------------------------------------- /doc/img/ElasticsearchCodeSearch_SearchClusterOverview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/doc/img/ElasticsearchCodeSearch_SearchClusterOverview.jpg -------------------------------------------------------------------------------- /doc/img/ElasticsearchCodeSearch_SearchCode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/doc/img/ElasticsearchCodeSearch_SearchCode.jpg -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | services: 3 | 4 | services: 5 | elasticsearch: 6 | image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-8.14.1} 7 | container_name: ${ELASTIC_HOSTNAME:-es01} 8 | hostname: ${ELASTIC_HOSTNAME:-es01} 9 | restart: ${RESTART_MODE:-unless-stopped} 10 | healthcheck: 11 | test: ["CMD-SHELL", "curl --user ${ELASTIC_USER:-elastic}:${ELASTIC_PASSWORD:-secret} --silent --fail https://localhost:9200/_cluster/health -k || exit 1" ] 12 | interval: 10s 13 | timeout: 10s 14 | retries: 20 15 | start_period: 30s 16 | profiles: ["elastic", "dev"] 17 | env_file: 18 | - ./docker/.env 19 | environment: 20 | - node.name=es01 21 | - discovery.type=single-node 22 | - ELASTIC_PASSWORD=${ELASTIC_PASSWORD:-secret} 23 | - xpack.security.enabled=${ELASTIC_SECURITY:-true} 24 | - xpack.security.http.ssl.enabled=true 25 | - xpack.security.http.ssl.verification_mode=none 26 | - xpack.security.http.ssl.key=/usr/share/elasticsearch/config/cert/es01.key 27 | - xpack.security.http.ssl.certificate=/usr/share/elasticsearch/config/cert/es01.crt 28 | - xpack.security.http.ssl.certificate_authorities=/usr/share/elasticsearch/config/cert/ca/ca.crt 29 | - xpack.security.transport.ssl.enabled=${ELASTIC_SECURITY:-true} 30 | - xpack.security.transport.ssl.verification_mode=none 31 | - xpack.security.transport.ssl.certificate_authorities=/usr/share/elasticsearch/config/cert/ca/ca.crt 32 | - xpack.security.transport.ssl.certificate=/usr/share/elasticsearch/config/cert/es01.crt 33 | - xpack.security.transport.ssl.key=/usr/share/elasticsearch/config/cert/es01.key 34 | ulimits: 35 | memlock: 36 | soft: -1 37 | hard: -1 38 | volumes: 39 | - ./docker/elasticsearch/elastic-data:/usr/share/elasticsearch/data 40 | - ./docker/elasticsearch/elastic-cert:/usr/share/elasticsearch/config/cert 41 | ports: 42 | - "9200:9200" 43 | - "9300:9300" 44 | codesearch-api: 45 | container_name: codesearch-api 46 | build: 47 | context: . 48 | dockerfile: ./docker/codesearch-api/Dockerfile 49 | environment: 50 | - ASPNETCORE_ENVIRONMENT=Docker 51 | - ASPNETCORE_HTTPS_PORTS=5000 52 | - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx 53 | - ASPNETCORE_Kestrel__Certificates__Default__Password=SuperStrongPassword 54 | profiles: ["api", "dev"] 55 | env_file: 56 | - ./docker/.env 57 | depends_on: 58 | elasticsearch: 59 | condition: service_healthy 60 | ports: 61 | - "5000:5000" 62 | volumes: 63 | - ./docker/codesearch/codesearch-data:/codesearch-data 64 | - ~/.aspnet/https:/https:ro 65 | - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro 66 | codesearch-web: 67 | container_name: codesearch-web 68 | build: 69 | context: . 70 | dockerfile: ./docker/codesearch-web/Dockerfile 71 | environment: 72 | - ASPNETCORE_ENVIRONMENT=Docker 73 | - ASPNETCORE_HTTPS_PORTS=5001 74 | - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx 75 | - ASPNETCORE_Kestrel__Certificates__Default__Password=SuperStrongPassword 76 | profiles: ["web", "dev"] 77 | env_file: 78 | - ./docker/.env 79 | depends_on: 80 | elasticsearch: 81 | condition: service_healthy 82 | ports: 83 | - "5001:5001" 84 | volumes: 85 | - ~/.aspnet/https:/https:ro 86 | - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro 87 | -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | ELASTIC_HOSTNAME=es01 2 | ELASTIC_USERNAME=elastic 3 | ELASTIC_PASSWORD=secret 4 | ELASTIC_PORT=9200 5 | ELASTIC_SECURITY=true 6 | ELASTIC_SCHEME=https 7 | ELASTIC_VERSION=8.14.1 8 | -------------------------------------------------------------------------------- /docker/codesearch-api/Dockerfile: -------------------------------------------------------------------------------- 1 | # Get the dotnet Build Environment: 2 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 3 | WORKDIR /source 4 | 5 | # COPY Project Files: 6 | COPY ../src/ElasticsearchCodeSearch/*.csproj ./ElasticsearchCodeSearch/ 7 | COPY ../src/ElasticsearchCodeSearch.Shared/*.csproj ./ElasticsearchCodeSearch.Shared/ 8 | 9 | # And restore the NuGet Packages: 10 | RUN dotnet restore "ElasticsearchCodeSearch/ElasticsearchCodeSearch.csproj" 11 | 12 | # COPY 13 | COPY ../src/ElasticsearchCodeSearch/. ./src/ElasticsearchCodeSearch/ 14 | COPY ../src/ElasticsearchCodeSearch.Shared/. ./src/ElasticsearchCodeSearch.Shared/ 15 | 16 | RUN dotnet publish ./src/ElasticsearchCodeSearch/ElasticsearchCodeSearch.csproj -c release -o /app 17 | 18 | # Build the final image 19 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 20 | 21 | # Copy Artifacts from Build: 22 | WORKDIR /app 23 | COPY --from=build /app ./ 24 | 25 | # Install Git 26 | RUN apt-get -y update 27 | RUN apt-get -y install git 28 | 29 | # Start the Kestrel Server: 30 | ENTRYPOINT ["dotnet", "ElasticsearchCodeSearch.dll"] -------------------------------------------------------------------------------- /docker/codesearch-web/Dockerfile: -------------------------------------------------------------------------------- 1 | # Get the dotnet Build Environment: 2 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 3 | WORKDIR /source 4 | 5 | # COPY Project Files: 6 | COPY ../src/ElasticsearchCodeSearch.Web.Client/*.csproj ./ElasticsearchCodeSearch.Web.Client/ 7 | COPY ../src/ElasticsearchCodeSearch.Web.Server/*.csproj ./ElasticsearchCodeSearch.Web.Server/ 8 | COPY ../src/ElasticsearchCodeSearch.Shared/*.csproj ./ElasticsearchCodeSearch.Shared/ 9 | 10 | # And restore the NuGet Packages: 11 | RUN dotnet restore "ElasticsearchCodeSearch.Web.Server/ElasticsearchCodeSearch.Web.Server.csproj" 12 | 13 | # COPY 14 | COPY ../src/ElasticsearchCodeSearch.Web.Client/. ./src/ElasticsearchCodeSearch.Web.Client/ 15 | COPY ../src/ElasticsearchCodeSearch.Web.Server/. ./src/ElasticsearchCodeSearch.Web.Server/ 16 | COPY ../src/ElasticsearchCodeSearch.Shared/. ./src/ElasticsearchCodeSearch.Shared/ 17 | 18 | RUN dotnet publish ./src/ElasticsearchCodeSearch.Web.Server/ElasticsearchCodeSearch.Web.Server.csproj -c Release -o /app 19 | 20 | # Build the final image 21 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 22 | 23 | # Copy Artifacts from Build: 24 | WORKDIR /app 25 | COPY --from=build /app ./ 26 | 27 | # Start the Kestrel Server: 28 | ENTRYPOINT ["dotnet", "ElasticsearchCodeSearch.Web.Server.dll"] -------------------------------------------------------------------------------- /docker/elasticsearch/elastic-cert/ca/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDSjCCAjKgAwIBAgIVAIY8PVx5b95idpng6xwVrgvMtH9CMA0GCSqGSIb3DQEB 3 | CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu 4 | ZXJhdGVkIENBMB4XDTI0MDYyMDAyNTg0OVoXDTI3MDYyMDAyNTg0OVowNDEyMDAG 5 | A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew 6 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpIPQlIgJYjCUE72lrTl8o 7 | APRDqM3G10kOKFJpnzIx7NtU3Fq41snc8dM5xbn0WgcTfceAkxP/TkacuaDd/wMV 8 | XebHlx/K1/VAXOXWVE5DB0Bc+1TybE2B225hCVOYHwDXD0TzvfFfkUXDtzCYPvYx 9 | 0UwnCeq/loGuTlHZE7oZY8hOoS+NctiTyxAgGPKU6QI41st1M9Oyd/mEg0mSXlfu 10 | 6vsNNwzPbMmiMuIrOnmqWLJNJ9JMJxjPTicuzPKzZLFgjcZbg9JsCl+xzF44ULTT 11 | ITAjfmZ0lfwBSydi90bn7tdBgMPYi3GpdBMAE2fKm6Fwj8hePcBnCHADC46dgcGN 12 | AgMBAAGjUzBRMB0GA1UdDgQWBBSYquwhYddRUdbByLo9rURPNYiLVTAfBgNVHSME 13 | GDAWgBSYquwhYddRUdbByLo9rURPNYiLVTAPBgNVHRMBAf8EBTADAQH/MA0GCSqG 14 | SIb3DQEBCwUAA4IBAQCBs/+RubVqtSSyJCjcnBhy4OLwajLORREs2Bdl8/rmFBfg 15 | gxf+sJvnGedPjYJnr37xUJnyCw+pNPYNpPIkaI6Y8FGXg8In8wxqNter5OlVpxBw 16 | kD3m8396QAYNAdLwka7KyQGmilvb2LASwkMp6WSdo6MFiuV58P+ynjhFXZ0d7ybQ 17 | 6DmnsteZ1Omw2ERq4xBVFWq4EmS4BUqqR9GwS7ibKCzA6qZD+il8tKBAGwa67t23 18 | I1z43+oSLCtBzJuWREzt8G7BcFTy63PeZvyhnKnwsAvPdqKs3cvWhIkdCmdIniD5 19 | 5ULVwWPLU/ChAOhrtBkwYGCqjRsGtbDVJwc82lPC 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /docker/elasticsearch/elastic-cert/ca/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAqSD0JSICWIwlBO9pa05fKAD0Q6jNxtdJDihSaZ8yMezbVNxa 3 | uNbJ3PHTOcW59FoHE33HgJMT/05GnLmg3f8DFV3mx5cfytf1QFzl1lROQwdAXPtU 4 | 8mxNgdtuYQlTmB8A1w9E873xX5FFw7cwmD72MdFMJwnqv5aBrk5R2RO6GWPITqEv 5 | jXLYk8sQIBjylOkCONbLdTPTsnf5hINJkl5X7ur7DTcMz2zJojLiKzp5qliyTSfS 6 | TCcYz04nLszys2SxYI3GW4PSbApfscxeOFC00yEwI35mdJX8AUsnYvdG5+7XQYDD 7 | 2ItxqXQTABNnypuhcI/IXj3AZwhwAwuOnYHBjQIDAQABAoIBAEe1dC7GU65NhWip 8 | Rc48hXYFqYuCZ/U11IDPMdocqICoh3pcj46taxtl4QQuxKBJB5UJEGyAb8sg2imb 9 | PwzBEgKeNLpNZipwFEk82ipcxm3/BhgmbCb5KoezjQJRnQLzqjyE+dxKnavCgYzx 10 | AAadM2996UboGoMvAj7wcB2VEqOujYeUHo79UTe4XJEMSy+qY7DXXjGH12v5+oHv 11 | ZE9HshlIxkmhiBnTKhMMz7iFmspf9JLmWqHTL43Gh6AGDbQAe++nBHlYESaJi4aE 12 | SPGSIT6/A+MGbgi109BmoFyb9Yl/VUCHoITE/+EEexfwQ2+rDFXlzsC7sq93PMUu 13 | NlwCTbUCgYEAzMpa0S6b1fb/adJf5uqUzANt5uyvAX7I8V5pX8BEvvQf8XwSfn9s 14 | BtG/vR/1wKog13r1QdQu01nBUPM+m7g7o6OUTzYYpRpCNGxJJQDTuICsZxebtCRg 15 | HHggeyDEUhbF4hHW0wXz7R9ZYzCw7vMsSst9EuOXadZhrWOtIlFvRF8CgYEA02u3 16 | ZqSHGGHfl87Q2951rdiD3i1iVAqi6ZAKsPrZSapm87ETlpBjyFlpEV8OoW8wviuQ 17 | T3Epwn9hLEt6m7DRxVUZV02LlWbWFtYvZmFCT2MaASAPF9kNMXbiWCIPtM0imAtr 18 | 1fjzJMeuI4xk4eNceGbK0tOYt7IRRNWbB6464ZMCgYBIvT9QuYtkjlzeS3kA3iWH 19 | 6VfqA/uNPmlFQlGPTw2b/b4y2ez/vWazbWD7XhS+IC+WGfhvL1yKeYDurdd5HBEi 20 | 6IFPOVm7mv4U/LlmSUrqZ9nUrFADxj/VHN38ngDdX3Vd/RQe1Ch1+wKW9r6BwSHk 21 | Vy7PvMMVNq5vFc5zOBWbZQKBgQC1WFGp8zoFyf3V4wn3biuWgH8r1dXfrHfsyybA 22 | g4pZy5YfNSZOIW/VbAvZYKXWBt/SXt+bpL9jG4uoSN3UKajlEId6AQPXlKvSTsm9 23 | kXMlUSX/DRalKAJPCWBApIbAWKxxqmpG0REN3VEbINNvhmvMwq76g5EdD9oMJwSh 24 | Y+sRjQKBgQCwXeTwvbSITkCd5vSMxQg1RNVnLdP+LyJ8NunJlZt+mQI6/m4S0bKA 25 | d5w9DoaHIKIz/BwP1ERclFgzRY4wA8jOGE9NS+x7qQbvgN3lLlii/ySUHDaX2FhD 26 | rAj1lkUTPVlYmTnNdChe8G3AMeVHMJk4+AVVy7SFpotICV3XHqZEuw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /docker/elasticsearch/elastic-cert/es01.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgIVAO6ERxm6MOvaRt4VyG29RYi6c+U0MA0GCSqGSIb3DQEB 3 | CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu 4 | ZXJhdGVkIENBMB4XDTI0MDYyMDAzMjMxNFoXDTI3MDYyMDAzMjMxNFowDzENMAsG 5 | A1UEAxMEZXMwMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALFp/uru 6 | DNQ8HQ22S4uqYZd2YB9uWry61R/afytpGzJdGjmeqjWBF6oesLQr9yffLet/UgG4 7 | DbA6nrhQDFQzzS218fMjsM08hsCeU3lk1J3OUKMCiugKiO61tG/wWbi9/YJ3pQtg 8 | kXnNt4uK74ELmrmXMvs+BHuJzuexOMRqnHvntA5QhilpEIWRoVAL1QxYXWC/lo8u 9 | Ketl8w0yn/RB1InWUftlKiaiooq3M57/yp+5Es+xVvCnSD99eL/oX2+JRMKgi4Zy 10 | EA7Bowem7cVIefDC6VRtAsuUzfqsAiSEOXbTHrnk1XyBWVXlBBtrJ0XiEPfPTH9E 11 | VVS2lpo4HsqTXoUCAwEAAaNvMG0wHQYDVR0OBBYEFAL5QpafWzfww2hgsoih+8rH 12 | iNFxMB8GA1UdIwQYMBaAFJiq7CFh11FR1sHIuj2tRE81iItVMCAGA1UdEQQZMBeC 13 | BGVzMDGHBH8AAAGCCWxvY2FsaG9zdDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQBcH5jj8B5JY5xA+iSqzB669cw6iEWVWSV9GJ5i6nMtVVFE4rV/iUhxrpd/ 15 | k2u3wbBBTZEtkMNv9jmeUOUrohIWufgNUgdD5obJtT1Zgw1FFV4EUjbEn6EjDqxo 16 | S93pm2qK3qHeZtwYOzbZ+6Mal1gLRZ8B2kBzAkUdLBDJ+EMP2vuX13qakwt43TFW 17 | qqi4goLDa92jBGI5bqW2FJ+w8h5junIJItvSdbXKL1yYDfjFMeqdHMBYX8bE/Opl 18 | XfgALMfZgoq+nRFkLm3wDTa4T/ib+CysGXCABwshqGplUJxOcUQWG8xB0dtdXje9 19 | W0wNRjS/ppWK2tv98K0jok2iNXLN 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /docker/elasticsearch/elastic-cert/es01.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAsWn+6u4M1DwdDbZLi6phl3ZgH25avLrVH9p/K2kbMl0aOZ6q 3 | NYEXqh6wtCv3J98t639SAbgNsDqeuFAMVDPNLbXx8yOwzTyGwJ5TeWTUnc5QowKK 4 | 6AqI7rW0b/BZuL39gnelC2CRec23i4rvgQuauZcy+z4Ee4nO57E4xGqce+e0DlCG 5 | KWkQhZGhUAvVDFhdYL+Wjy4p62XzDTKf9EHUidZR+2UqJqKiircznv/Kn7kSz7FW 6 | 8KdIP314v+hfb4lEwqCLhnIQDsGjB6btxUh58MLpVG0Cy5TN+qwCJIQ5dtMeueTV 7 | fIFZVeUEG2snReIQ989Mf0RVVLaWmjgeypNehQIDAQABAoIBAAeI7rh//byx0bLe 8 | ycL+LIDoGFlqMKGQkDN3IZMj0D58vi7+S+pT0XNHNGgI8GLmhqbyB5CZ7L+0VAz6 9 | t8G68koW6FZUO+xFJGmTD9m5bF8U/4KJpBtwy+bStTDsroYH6hFoqKWc2op0Yzw8 10 | KinhgH1eksRUgPcIn+hiALA1R71Q95sulKrWMFhKh1Qc+MNUQ3p0GqNEeg2g+fTv 11 | +MTs6DQtcnkenZoribwuWckJs/myA4veAA/H+tFFS5YuQ91Wn0aomUXCskqhYTul 12 | Hwr0U+tzwvs+wmZP4l8Y+CrvSoj4F0cKfmM1NXBcQp5WFJL/YtODoA8fATcNk7Jd 13 | M5uCfG0CgYEA2pQxmH35yIdf745u9CaHAImsiKuGYGJ3nohTacPuhzqDsqkiDiZC 14 | 61x8lHSDxc+dcLsDXPVwvT+k8U41Wd5HKy7gLAm0JWOtGPhyahwibRqz/eTsPyZe 15 | ZMvlOGM9y9eLzYIbomFNQHnPx2nHv38TaR7tYev8qfZxkHFwQETxoq8CgYEAz8mk 16 | gbWL8STVs0d7D9mswaH4aQUDU7AzejfEgtSxrHVNofkmVCl5KguCmoNcHv0jqvU/ 17 | cukETpYaMXEM/IRRlkSS0MQZGEHbCAr6gey3H3QZQSLbgGM0UxefePSgvXfkm0zG 18 | 6oLbviO4sefbY46Ys5xSefL2s5Xb/pq+5Suw7wsCgYEAvr5KFYABxSvV3XCXhLpG 19 | X4LFLLM6XcwwBQmEeSzBcALxQKz2ChD7nvajxM103N+Tzfd1NN7/Fjd/EhEk35ro 20 | 0ldiyytgqKw2Ny9AcTbCGCIQZoUqYOVzxFRmYPHB0Kv11U4wHWD6EET8vFGkPYmA 21 | f+C8WRKd+BgW/Gzx/zPrBgMCgYEAgpqivSjIikz1yZcPYdoXPSo6goA1JCAnaxWs 22 | ffOErfqZTkrVbacX0najo80XVR8VkTpPpEGUhHTSh+sgF4Rv57y4b2Iix9109+w2 23 | ov2P6MRHr2pif6NbWzMI+LUCZ7T5SygKC5Mu3aeESsaKXlxd3N9P8/jkWeLDAZhw 24 | jolU0BsCgYBrJx5sYfGuJDKncHjtL5TsD13hpqJAV+sgG2Qog8CQSUxka8KPxFvN 25 | tegW2puhyzhZ7+w3ZT4eacJgneTTl4E7xi4TJe1KZRWJ5Q82ide+NQWqmka2oWSL 26 | 5zYjidL35IksJifGBsGMduv6vEiFXNqBVTG2cEQiJ0JttXSrYOC2Wg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /docker/elasticsearch/instances.yml: -------------------------------------------------------------------------------- 1 | instances: 2 | - name: es01 3 | dns: 4 | - es01 5 | - localhost 6 | ip: 7 | - 127.0.0.1 -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Constants/SourceSystems.cs: -------------------------------------------------------------------------------- 1 | namespace ElasticsearchCodeSearch.Shared.Constants 2 | { 3 | public static class SourceSystems 4 | { 5 | /// 6 | /// Source is GitHub. 7 | /// 8 | public const string GitHub = "GitHub"; 9 | 10 | /// 11 | /// Source is GitLab. 12 | /// 13 | public const string GitLab = "GitLab"; 14 | 15 | /// 16 | /// Source is Codeberg. 17 | /// 18 | public const string Codeberg = "Codeberg"; 19 | 20 | /// 21 | /// Source is Unknown. 22 | /// 23 | public const string Unknown = "Unknown"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeIndexQueueDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | public class CodeIndexQueueDto 8 | { 9 | /// 10 | /// Gets or sets the current repositories queued for indexing. 11 | /// 12 | [JsonPropertyName("repositories")] 13 | public required List Repositories { get; set; } = []; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeSearchDocumentDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace ElasticsearchCodeSearch.Shared.Dto 7 | { 8 | /// 9 | /// A code document, which should be indexed and searchable by Elasticsearch. 10 | /// 11 | public class CodeSearchDocumentDto 12 | { 13 | /// 14 | /// Gets or sets the Id. 15 | /// 16 | [Required] 17 | [JsonPropertyName("id")] 18 | public required string Id { get; set; } 19 | 20 | /// 21 | /// Gets or sets the owner (user or organization). 22 | /// 23 | [Required] 24 | [JsonPropertyName("owner")] 25 | public required string Owner { get; set; } 26 | 27 | /// 28 | /// Gets or sets the Repository Name. 29 | /// 30 | [Required] 31 | [JsonPropertyName("repository")] 32 | public required string Repository { get; set; } 33 | 34 | /// 35 | /// Gets or sets the filename. 36 | /// 37 | [Required] 38 | [JsonPropertyName("filename")] 39 | public required string Filename { get; set; } 40 | 41 | /// 42 | /// Gets or sets the relative file path. 43 | /// 44 | [Required] 45 | [JsonPropertyName("path")] 46 | public required string Path { get; set; } 47 | 48 | /// 49 | /// Gets or sets the commit hash. 50 | /// 51 | [Required] 52 | [JsonPropertyName("commitHash")] 53 | public required string CommitHash { get; set; } 54 | 55 | /// 56 | /// Gets or sets the content to index. 57 | /// 58 | [JsonPropertyName("content")] 59 | public string? Content { get; set; } = string.Empty; 60 | 61 | /// 62 | /// Gets or sets the branch. 63 | /// 64 | [JsonPropertyName("content")] 65 | public string? Branch { get; set; } = string.Empty; 66 | 67 | /// 68 | /// Gets or sets the Permalink to the file. 69 | /// 70 | [Required] 71 | [JsonPropertyName("permalink")] 72 | public required string Permalink { get; set; } 73 | 74 | /// 75 | /// Gets or sets the latest commit date. 76 | /// 77 | [Required] 78 | [JsonPropertyName("latestCommitDate")] 79 | public required DateTimeOffset LatestCommitDate { get; set; } 80 | } 81 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeSearchRepositoryDto.cs: -------------------------------------------------------------------------------- 1 | namespace ElasticsearchCodeSearch.Shared.Dto 2 | { 3 | public class CodeSearchRepositoryDto 4 | { 5 | /// 6 | /// Gets or sets a unique idenfitifer. 7 | /// 8 | public required string Id { get; set; } 9 | 10 | /// 11 | /// Gets or sets the name of the repository. 12 | /// 13 | public required string Name { get; set; } 14 | 15 | /// 16 | /// Gets or sets the owner of the repository. 17 | /// 18 | public required string Owner { get; set; } 19 | 20 | /// 21 | /// Gets or sets the URL for cloning. 22 | /// 23 | public string? Url { get; set; } 24 | 25 | /// 26 | /// Gets or sets the SSH URL for cloning. 27 | /// 28 | public string? SshUrl { get; set; } 29 | 30 | /// 31 | /// Gets or sets the time the repository has been updated at. 32 | /// 33 | public required DateTime UpdatedAt { get; set; } 34 | 35 | /// 36 | /// Gets or sets the time the repository has been created at. 37 | /// 38 | public required DateTime CreatedAt { get; set; } 39 | 40 | /// 41 | /// Gets or sets the time the repository has been pushed to. 42 | /// 43 | public required DateTime PushedAt { get; set; } 44 | 45 | /// 46 | /// Gets or sets the size in kilobytes for the repository. 47 | /// 48 | public required int SizeInKilobytes { get; set; } 49 | 50 | /// 51 | /// Gets or sets the language of the code in the repository. 52 | /// 53 | public string? Language { get; set; } 54 | } 55 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeSearchRequestDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace ElasticsearchCodeSearch.Shared.Dto 7 | { 8 | /// 9 | /// The Client sends a to filter for 10 | /// documents, paginate and sort the results. The Search Query is given as 11 | /// a Query String. 12 | /// 13 | public class CodeSearchRequestDto 14 | { 15 | /// 16 | /// Gets or sets the Query String. 17 | /// 18 | [Required] 19 | [JsonPropertyName("query")] 20 | public required string Query { get; set; } 21 | 22 | /// 23 | /// Gets or sets the number of hits to skip, defaulting to 0. 24 | /// 25 | [Required] 26 | [JsonPropertyName("from")] 27 | public int From { get; set; } = 0; 28 | 29 | /// 30 | /// Gets or sets the number of hits to return, defaulting to 10. 31 | /// 32 | [Required] 33 | [JsonPropertyName("size")] 34 | public int Size { get; set; } = 10; 35 | 36 | /// 37 | /// Gets or sets the sort fields for the results. 38 | /// 39 | [Required] 40 | [JsonPropertyName("sort")] 41 | public List Sort { get; set; } = new List(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeSearchResultDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | /// 8 | /// Holds the Search Results along with the highlighted matches. 9 | /// 10 | public class CodeSearchResultDto 11 | { 12 | /// 13 | /// Gets or sets the Id. 14 | /// 15 | [JsonPropertyName("Id")] 16 | public required string Id { get; set; } 17 | 18 | /// 19 | /// Gets or sets the owner. 20 | /// 21 | [JsonPropertyName("owner")] 22 | public required string Owner { get; set; } 23 | 24 | /// 25 | /// Gets or sets the repository name. 26 | /// 27 | [JsonPropertyName("repository")] 28 | public required string Repository { get; set; } 29 | 30 | /// 31 | /// Gets or sets the relative file path. 32 | /// 33 | [JsonPropertyName("path")] 34 | public required string Path { get; set; } 35 | 36 | /// 37 | /// Gets or sets the filename. 38 | /// 39 | [JsonPropertyName("filename")] 40 | public required string Filename { get; set; } 41 | 42 | /// 43 | /// Gets or sets the Permalink. 44 | /// 45 | [JsonPropertyName("permalink")] 46 | public required string Permalink { get; set; } 47 | 48 | /// 49 | /// Gets or sets the Highlighted Content, which is the lines. 50 | /// 51 | [JsonPropertyName("content")] 52 | public required List Content { get; set; } 53 | 54 | /// 55 | /// Gets or sets the latest commit date. 56 | /// 57 | [JsonPropertyName("latestCommitDate")] 58 | public required DateTimeOffset LatestCommitDate { get; set; } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeSearchResultsDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | /// 8 | /// Holds the Paginated Code Search Results. 9 | /// 10 | public class CodeSearchResultsDto 11 | { 12 | /// 13 | /// Gets or sets the query string, that has been used. 14 | /// 15 | [JsonPropertyName("query")] 16 | public required string Query { get; set; } 17 | 18 | /// 19 | /// Gets or sets the number of hits to skip, defaulting to 0. 20 | /// 21 | [JsonPropertyName("from")] 22 | public required int From { get; set; } 23 | 24 | /// 25 | /// Gets or sets the number of hits to return, defaulting to 10. 26 | /// 27 | [JsonPropertyName("size")] 28 | public required int Size { get; set; } 29 | 30 | /// 31 | /// Gets or sets the total Number of matched documents. 32 | /// 33 | [JsonPropertyName("total")] 34 | public required long Total { get; set; } 35 | 36 | /// 37 | /// Gets or sets the the time in milliseconds it took Elasticsearch to execute the request. 38 | /// 39 | [JsonPropertyName("tookInMilliseconds")] 40 | public required long TookInMilliseconds { get; set; } 41 | 42 | /// 43 | /// Gets or sets the sort fields used. 44 | /// 45 | [JsonPropertyName("sort")] 46 | public required List Sort { get; set; } = new List(); 47 | 48 | /// 49 | /// Gets or sets the search results. 50 | /// 51 | [JsonPropertyName("results")] 52 | public required List Results { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/CodeSearchStatisticsDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | /// 8 | /// Summarizes useful metrics about the Elasticsearch instance. 9 | /// 10 | /// There is a great guide for Elasticsearch monitoring and statistics by the DataDog team: 11 | /// 12 | /// https://www.datadoghq.com/blog/monitor-elasticsearch-performance-metrics/#search-performance-metrics 13 | /// 14 | /// 15 | public class CodeSearchStatisticsDto 16 | { 17 | /// 18 | /// Index Name. 19 | /// 20 | [JsonPropertyName("indexName")] 21 | public required string IndexName { get; set; } 22 | 23 | /// 24 | /// Total Index Size in bytes (indices.store.size_in_bytes). 25 | /// 26 | [JsonPropertyName("indexSizeInBytes")] 27 | public required long? IndexSizeInBytes { get; set; } 28 | 29 | /// 30 | /// Total number of documents indexed (indices.docs.count). 31 | /// 32 | [JsonPropertyName("totalNumberOfDocumentsIndexed")] 33 | public required long? TotalNumberOfDocumentsIndexed { get; set; } 34 | 35 | /// 36 | /// Number of documents currently being indexed (indices.indexing.index_current). 37 | /// 38 | [JsonPropertyName("numberOfDocumentsCurrentlyBeingIndexed")] 39 | public required long? NumberOfDocumentsCurrentlyBeingIndexed { get; set; } 40 | 41 | /// 42 | /// Total time spent indexing documents (indices.bulk.total_time_in_millis). 43 | /// 44 | [JsonPropertyName("totalTimeSpentBulkIndexingDocumentsInMilliseconds")] 45 | public required long? TotalTimeSpentBulkIndexingDocumentsInMilliseconds { get; set; } 46 | 47 | /// 48 | /// Total time spent indexing documents (indices.indexing.index_time_in_millis). 49 | /// 50 | [JsonPropertyName("totalTimeSpentIndexingDocumentsInMilliseconds")] 51 | public required long? TotalTimeSpentIndexingDocumentsInMilliseconds { get; set; } 52 | 53 | /// 54 | /// Total number of queries (indices.search.query_total). 55 | /// 56 | [JsonPropertyName("totalNumberOfQueries")] 57 | public required long? TotalNumberOfQueries { get; set; } 58 | 59 | /// 60 | /// Total time spent on queries (indices.search.query_time_in_millis). 61 | /// 62 | [JsonPropertyName("totalTimeSpentOnQueriesInMilliseconds")] 63 | public required long? TotalTimeSpentOnQueriesInMilliseconds { get; set; } 64 | 65 | /// 66 | /// Number of queries currently in progress (indices.search.query_current). 67 | /// 68 | [JsonPropertyName("numberOfQueriesCurrentlyInProgress")] 69 | public required long? NumberOfQueriesCurrentlyInProgress { get; set; } 70 | 71 | /// 72 | /// Total number of fetches (indices.search.fetch_total). 73 | /// 74 | [JsonPropertyName("totalNumberOfFetches")] 75 | public required long? TotalNumberOfFetches { get; set; } 76 | 77 | /// 78 | /// Total time spent on fetches (indices.search.fetch_time_in_millis). 79 | /// 80 | [JsonPropertyName("totalTimeSpentOnFetchesInMilliseconds")] 81 | public required long? TotalTimeSpentOnFetchesInMilliseconds { get; set; } 82 | 83 | /// 84 | /// Number of fetches currently in progress (indices.search.fetch_current). 85 | /// 86 | [JsonPropertyName("numberOfFetchesCurrentlyInProgress")] 87 | public required long? NumberOfFetchesCurrentlyInProgress { get; set; } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/GitRepositoryMetadataDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace ElasticsearchCodeSearch.Shared.Dto 4 | { 5 | /// 6 | /// Git Repository Metadata. 7 | /// 8 | public class GitRepositoryMetadataDto 9 | { 10 | /// 11 | /// Gets or sets the owner of the Repository, for example a Username or Organization. 12 | /// 13 | [JsonPropertyName("owner")] 14 | public required string Owner { get; set; } 15 | 16 | /// 17 | /// Gets or sets the Name of the Repository. 18 | /// 19 | [JsonPropertyName("name")] 20 | public required string Name { get; set; } 21 | 22 | /// 23 | /// Gets the FullName given by Owner and Repository Name. 24 | /// 25 | [JsonPropertyName("fullName")] 26 | public string FullName => $"{Owner}/{Name}"; 27 | 28 | /// 29 | /// Gets or sets the Branch to index. 30 | /// 31 | [JsonPropertyName("branch")] 32 | public required string Branch { get; set; } 33 | 34 | /// 35 | /// Gets or sets the Repositories languages, if any. 36 | /// 37 | [JsonPropertyName("language")] 38 | public string Language { get; set; } = string.Empty; 39 | 40 | /// 41 | /// Gets or sets the Url to clone from. 42 | /// 43 | [JsonPropertyName("cloneUrl")] 44 | public required string CloneUrl { get; set; } 45 | 46 | /// 47 | /// Gets or sets the Source System of this Repository. 48 | /// 49 | [JsonPropertyName("Source")] 50 | public required string Source { get; set; } 51 | 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/HighlightedContentDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Shared.Dto 4 | { 5 | /// 6 | /// Holds the line number and line content for a match, and it has the 7 | /// information if the content needs highlighting. 8 | /// 9 | public class HighlightedContentDto 10 | { 11 | /// 12 | /// Gets or sets the line number. 13 | /// 14 | public int LineNo { get; set; } 15 | 16 | /// 17 | /// Gets or sets the line content. 18 | /// 19 | public string Content { get; set; } = string.Empty; 20 | 21 | /// 22 | /// Gets or sets the flag, if this line needs to be highlighted. 23 | /// 24 | public bool IsHighlight { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/IndexGitHubOrganizationRequestDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | /// 8 | /// Holds the Search Results along with the highlighted matches. 9 | /// 10 | public class IndexGitHubOrganizationRequestDto 11 | { 12 | /// 13 | /// Gets or sets the organization to index. 14 | /// 15 | [JsonPropertyName("organization")] 16 | public required string Organization { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/IndexGitHubRepositoryRequestDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | /// 8 | /// A Request for indexing a repository. 9 | /// 10 | public class IndexGitHubRepositoryRequestDto 11 | { 12 | /// 13 | /// Gets or sets the repository url to index. 14 | /// 15 | [JsonPropertyName("owner")] 16 | public required string Owner { get; set; } 17 | 18 | [JsonPropertyName("repository")] 19 | public required string Repository { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/IndexGitRepositoryRequestDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Shared.Dto 6 | { 7 | /// 8 | /// A Request for indexing a repository. 9 | /// 10 | public class IndexGitRepositoryRequestDto 11 | { 12 | /// 13 | /// Gets or sets the repository url to index. 14 | /// 15 | [JsonPropertyName("url")] 16 | public required string Url { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/SortFieldDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace ElasticsearchCodeSearch.Shared.Dto 7 | { 8 | /// 9 | /// Sort Field. 10 | /// 11 | public class SortFieldDto 12 | { 13 | /// 14 | /// Gets or sets the field name to sort. 15 | /// 16 | [Required] 17 | [JsonPropertyName("field")] 18 | public required string Field { get; set; } 19 | 20 | /// 21 | /// Gets or sets the sort order. 22 | /// 23 | [Required] 24 | [JsonPropertyName("order")] 25 | [JsonConverter(typeof(JsonStringEnumConverter))] 26 | public SortOrderEnumDto Order { get; set; } = SortOrderEnumDto.Asc; 27 | } 28 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Dto/SortOrderDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Shared.Dto 4 | { 5 | /// 6 | /// Sort Order. 7 | /// 8 | public enum SortOrderEnumDto 9 | { 10 | /// 11 | /// Ascending. 12 | /// 13 | Asc = 1, 14 | 15 | /// 16 | /// Descending. 17 | /// 18 | Desc = 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/CodeSearchDocumentConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | 6 | namespace ElasticsearchCodeSearch.Converters 7 | { 8 | public static class CodeSearchDocumentConverter 9 | { 10 | public static List Convert(List source) 11 | { 12 | return source 13 | .Select(x => Convert(x)) 14 | .ToList(); 15 | } 16 | 17 | public static CodeSearchDocument Convert(CodeSearchDocumentDto source) 18 | { 19 | return new CodeSearchDocument 20 | { 21 | Id = source.Id, 22 | Owner = source.Owner, 23 | Repository = source.Repository, 24 | Filename = source.Filename, 25 | Path = source.Path, 26 | CommitHash = source.CommitHash, 27 | Content = source.Content ?? string.Empty, 28 | Permalink = source.Permalink, 29 | LatestCommitDate = source.LatestCommitDate, 30 | }; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/CodeSearchRequestConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | 6 | namespace ElasticsearchCodeSearch.Converters 7 | { 8 | public static class CodeSearchRequestConverter 9 | { 10 | public static CodeSearchRequest Convert(CodeSearchRequestDto source) 11 | { 12 | return new CodeSearchRequest 13 | { 14 | Query = source.Query, 15 | From = source.From, 16 | Size = source.Size, 17 | Sort = SortFieldConverter.Convert(source.Sort) 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/CodeSearchResultConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Elastic.Clients.Elasticsearch; 4 | using ElasticsearchCodeSearch.Models; 5 | using ElasticsearchCodeSearch.Shared.Dto; 6 | using ElasticsearchCodeSearch.Shared.Elasticsearch.Utils; 7 | 8 | namespace ElasticsearchCodeSearch.Converters 9 | { 10 | public static class CodeSearchResultConverter 11 | { 12 | public static List Convert(SearchResponse source) 13 | { 14 | List results = new List(); 15 | 16 | foreach (var hit in source.Hits) 17 | { 18 | if (hit.Source == null) 19 | { 20 | continue; 21 | } 22 | 23 | var result = new CodeSearchResultDto 24 | { 25 | Id = hit.Source.Id, 26 | Owner = hit.Source.Owner, 27 | Repository = hit.Source.Repository, 28 | Filename = hit.Source.Filename, 29 | Path = hit.Source.Path, 30 | Permalink = hit.Source.Permalink, 31 | LatestCommitDate = hit.Source.LatestCommitDate, 32 | Content = GetContent(hit.Highlight), 33 | }; 34 | 35 | results.Add(result); 36 | } 37 | 38 | return results; 39 | } 40 | 41 | private static List GetContent(IReadOnlyDictionary>? highlight) 42 | { 43 | if (highlight == null) 44 | { 45 | return new(); 46 | } 47 | 48 | highlight.TryGetValue("content", out var matchesForContent); 49 | 50 | if(matchesForContent == null) 51 | { 52 | return new(); 53 | } 54 | 55 | var match = matchesForContent.FirstOrDefault(); 56 | 57 | if (match == null) 58 | { 59 | return new(); 60 | } 61 | 62 | var highlightedContent = ElasticsearchUtils.GetHighlightedContent(match); 63 | 64 | return HighlightedContentConverter.Convert(highlightedContent); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/CodeSearchStatisticsConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Elastic.Clients.Elasticsearch.IndexManagement; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | 6 | namespace ElasticsearchCodeSearch.Converters 7 | { 8 | public static class CodeSearchStatisticsConverter 9 | { 10 | public static List Convert(IndicesStatsResponse indicesStatsResponse) 11 | { 12 | if (indicesStatsResponse.Indices == null) 13 | { 14 | throw new Exception("No statistics available"); 15 | } 16 | 17 | return indicesStatsResponse.Indices 18 | .Select(x => Convert(x.Key, x.Value)) 19 | .ToList(); 20 | } 21 | 22 | public static CodeSearchStatisticsDto Convert(string indexName, IndicesStats indexStats) 23 | { 24 | return new CodeSearchStatisticsDto 25 | { 26 | IndexName = indexName, 27 | IndexSizeInBytes = indexStats.Total?.Store?.SizeInBytes, 28 | TotalNumberOfDocumentsIndexed = indexStats.Total?.Docs?.Count, 29 | NumberOfDocumentsCurrentlyBeingIndexed = indexStats.Total?.Indexing?.IndexCurrent, 30 | TotalNumberOfFetches = indexStats.Total?.Search?.FetchTotal, 31 | NumberOfFetchesCurrentlyInProgress = indexStats.Total?.Search?.FetchCurrent, 32 | TotalNumberOfQueries = indexStats.Total?.Search?.QueryTotal, 33 | NumberOfQueriesCurrentlyInProgress = indexStats.Total?.Search?.QueryCurrent, 34 | TotalTimeSpentBulkIndexingDocumentsInMilliseconds = indexStats.Total?.Bulk?.TotalTimeInMillis, 35 | TotalTimeSpentIndexingDocumentsInMilliseconds = indexStats.Total?.Bulk?.TotalTimeInMillis, 36 | TotalTimeSpentOnFetchesInMilliseconds = indexStats.Total?.Search?.FetchTimeInMillis, 37 | TotalTimeSpentOnQueriesInMilliseconds = indexStats.Total?.Search?.QueryTimeInMillis, 38 | }; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/HighlightedContentConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | 6 | namespace ElasticsearchCodeSearch.Converters 7 | { 8 | public static class HighlightedContentConverter 9 | { 10 | public static List Convert(List source) 11 | { 12 | return source 13 | .Select(x => Convert(x)) 14 | .ToList(); 15 | } 16 | 17 | public static HighlightedContentDto Convert(HighlightedContent source) 18 | { 19 | return new HighlightedContentDto 20 | { 21 | LineNo = source.LineNo, 22 | Content = source.Content, 23 | IsHighlight = source.IsHighlight, 24 | }; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/SortFieldConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | 6 | namespace ElasticsearchCodeSearch.Converters 7 | { 8 | public static class SortFieldConverter 9 | { 10 | 11 | public static List Convert(List source) 12 | { 13 | return source 14 | .Select(x => Convert(x)) 15 | .ToList(); 16 | } 17 | 18 | public static SortField Convert(SortFieldDto source) 19 | { 20 | return new SortField 21 | { 22 | Field = source.Field, 23 | Order = SortOrderEnumConverter.Convert(source.Order) 24 | }; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Converters/SortOrderEnumConverter.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | 6 | namespace ElasticsearchCodeSearch.Converters 7 | { 8 | public static class SortOrderEnumConverter 9 | { 10 | public static SortOrderEnum Convert(SortOrderEnumDto source) 11 | { 12 | return source switch 13 | { 14 | SortOrderEnumDto.Asc => SortOrderEnum.Ascending, 15 | SortOrderEnumDto.Desc => SortOrderEnum.Descending, 16 | _ => throw new ArgumentException($"Cannot convert from '{source}'"), 17 | }; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/ElasticCodeSearchOptions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Shared.Elasticsearch 4 | { 5 | /// 6 | /// Elasticsearch options. 7 | /// 8 | public class ElasticCodeSearchOptions 9 | { 10 | /// 11 | /// Endpoint of the Elasticsearch Node. 12 | /// 13 | public required string Uri { get; set; } 14 | 15 | /// 16 | /// Index to use for Code Search. 17 | /// 18 | public required string IndexName { get; set; } 19 | 20 | /// 21 | /// Elasticsearch Username. 22 | /// 23 | public required string Username { get; set; } 24 | 25 | /// 26 | /// Elasticsearch Password. 27 | /// 28 | public required string Password { get; set; } 29 | 30 | /// 31 | /// Certificate Fingerprint for trusting the Certificate. 32 | /// 33 | public required string CertificateFingerprint { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/ElasticsearchConstants.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Shared.Elasticsearch 4 | { 5 | /// 6 | /// Constants used by the Frontend and Backend. 7 | /// 8 | public static class ElasticsearchConstants 9 | { 10 | /// 11 | /// A tag used to find the highlightning start position. 12 | /// 13 | public static readonly string HighlightStartTag = "elasticsearchcodesearch→"; 14 | 15 | /// 16 | /// A tag used to find the highlightning end position. 17 | /// 18 | public static readonly string HighlightEndTag = "←elasticsearchcodesearch"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Models/CodeSearchDocument.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Models 4 | { 5 | /// 6 | /// A code document, which should be indexed and searchable by Elasticsearch. 7 | /// 8 | public class CodeSearchDocument 9 | { 10 | /// 11 | /// Gets or sets the Id. 12 | /// 13 | public required string Id { get; set; } 14 | 15 | /// 16 | /// Gets or sets the owner (organization or user). 17 | /// 18 | public required string Owner { get; set; } 19 | 20 | /// 21 | /// Gets or sets the repository. 22 | /// 23 | public required string Repository { get; set; } 24 | 25 | /// 26 | /// Gets or sets the filepath. 27 | public required string Path { get; set; } 28 | 29 | /// 30 | /// Gets or sets the filename. 31 | public required string Filename { get; set; } 32 | 33 | /// 34 | /// Gets or sets the commit hash. 35 | public required string CommitHash { get; set; } 36 | 37 | /// 38 | /// Gets or sets the content to index. 39 | /// 40 | public string Content { get; set; } = string.Empty; 41 | 42 | /// 43 | /// Gets or sets the branch. 44 | /// 45 | public string Branch { get; set; } = string.Empty; 46 | 47 | /// 48 | /// Gets or sets the Permalink to the file. 49 | /// 50 | public string Permalink { get; set; } = string.Empty; 51 | 52 | /// 53 | /// Gets or sets the latest commit date. 54 | /// 55 | public required DateTimeOffset LatestCommitDate { get; set; } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Models/CodeSearchRequest.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Models 4 | { 5 | /// 6 | /// A Search Request, which will be converted to Elasticsearch. 7 | /// 8 | public class CodeSearchRequest 9 | { 10 | /// 11 | /// Gets or sets the Search Query. 12 | /// 13 | public required string Query { get; set; } 14 | 15 | /// 16 | /// Gets or sets the number of documents to skip. 17 | /// 18 | public required int From { get; set; } = 0; 19 | 20 | /// 21 | /// Gets or sets the number of documents to fetch. 22 | /// 23 | public required int Size { get; set; } = 10; 24 | 25 | /// 26 | /// Gets or sets the sort fields. 27 | /// 28 | public required List Sort { get; set; } = new List(); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Models/HighlightedContent.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Models 4 | { 5 | /// 6 | /// Holds the line number and line content for a match, and it 7 | /// has the information if the content needs highlighting. 8 | /// 9 | public class HighlightedContent 10 | { 11 | /// 12 | /// Gets or sets the line number. 13 | /// 14 | public int LineNo { get; set; } 15 | 16 | /// 17 | /// Gets or sets the line content. 18 | /// 19 | public string Content { get; set; } = string.Empty; 20 | 21 | /// 22 | /// Gets or sets the flag, if this line needs to be highlighted. 23 | /// 24 | public bool IsHighlight { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Models/SortField.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Models 4 | { 5 | /// 6 | /// Sort Field. 7 | /// 8 | public class SortField 9 | { 10 | /// 11 | /// Gets or sets the Sort Field. 12 | /// 13 | public required string Field { get; set; } 14 | 15 | /// 16 | /// Gets or sets the Sort Order. 17 | /// 18 | public required SortOrderEnum Order { get; set; } = SortOrderEnum.Ascending; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Models/SortOrderEnum.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Models 4 | { 5 | /// 6 | /// Sort Order. 7 | /// 8 | public enum SortOrderEnum 9 | { 10 | /// 11 | /// Ascending. 12 | /// 13 | Ascending = 1, 14 | 15 | /// 16 | /// Descending. 17 | /// 18 | Descending = 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Services/ElasticsearchCodeSearchService.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Shared.Dto; 4 | using ElasticsearchCodeSearch.Shared.Exceptions; 5 | using ElasticsearchCodeSearch.Shared.Logging; 6 | using Microsoft.Extensions.Logging; 7 | using System.Globalization; 8 | using System.Net.Http.Json; 9 | 10 | namespace ElasticsearchCodeSearch.Shared.Services 11 | { 12 | public class ElasticsearchCodeSearchService 13 | { 14 | private readonly ILogger _logger; 15 | private readonly HttpClient _httpClient; 16 | 17 | public ElasticsearchCodeSearchService(ILogger logger, HttpClient httpClient) 18 | { 19 | _httpClient = httpClient; 20 | _logger = logger; 21 | } 22 | 23 | public async Task QueryAsync(CodeSearchRequestDto codeSearchRequestDto, CancellationToken cancellationToken) 24 | { 25 | _logger.TraceMethodEntry(); 26 | 27 | var response = await _httpClient 28 | .PostAsJsonAsync("search-documents", codeSearchRequestDto, cancellationToken) 29 | .ConfigureAwait(false); 30 | 31 | if (!response.IsSuccessStatusCode) 32 | { 33 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 34 | "HTTP Request failed with Status: '{0}' ({1})", 35 | (int)response.StatusCode, 36 | response.StatusCode)) 37 | { 38 | StatusCode = response.StatusCode 39 | }; 40 | } 41 | 42 | return await response.Content 43 | .ReadFromJsonAsync(cancellationToken: cancellationToken) 44 | .ConfigureAwait(false); 45 | } 46 | 47 | public async Task CreateSearchIndexAsync(CancellationToken cancellationToken) 48 | { 49 | _logger.TraceMethodEntry(); 50 | 51 | var response = await _httpClient 52 | .PostAsync("create-index", null, cancellationToken) 53 | .ConfigureAwait(false); 54 | 55 | if (!response.IsSuccessStatusCode) 56 | { 57 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 58 | "HTTP Request failed with Status: '{0}' ({1})", 59 | (int)response.StatusCode, 60 | response.StatusCode)) 61 | { 62 | StatusCode = response.StatusCode 63 | }; 64 | } 65 | } 66 | 67 | public async Task DeleteSearchIndexAsync(CancellationToken cancellationToken) 68 | { 69 | _logger.TraceMethodEntry(); 70 | 71 | var response = await _httpClient 72 | .PostAsync("delete-index", null, cancellationToken) 73 | .ConfigureAwait(false); 74 | 75 | if (!response.IsSuccessStatusCode) 76 | { 77 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 78 | "HTTP Request failed with Status: '{0}' ({1})", 79 | (int)response.StatusCode, 80 | response.StatusCode)) 81 | { 82 | StatusCode = response.StatusCode 83 | }; 84 | } 85 | } 86 | 87 | public async Task DeleteAllDocumentsAsync(CancellationToken cancellationToken) 88 | { 89 | _logger.TraceMethodEntry(); 90 | 91 | var response = await _httpClient 92 | .PostAsync("delete-all-documents", null, cancellationToken) 93 | .ConfigureAwait(false); 94 | 95 | if (!response.IsSuccessStatusCode) 96 | { 97 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 98 | "HTTP Request failed with Status: '{0}' ({1})", 99 | (int)response.StatusCode, 100 | response.StatusCode)) 101 | { 102 | StatusCode = response.StatusCode 103 | }; 104 | } 105 | } 106 | 107 | public async Task IndexGitRepositoryAsync(GitRepositoryMetadataDto repositoryMetadata, CancellationToken cancellationToken) 108 | { 109 | _logger.TraceMethodEntry(); 110 | 111 | var response = await _httpClient 112 | .PostAsJsonAsync("index-git-repository", repositoryMetadata, cancellationToken) 113 | .ConfigureAwait(false); 114 | 115 | if (!response.IsSuccessStatusCode) 116 | { 117 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 118 | "HTTP Request failed with Status: '{0}' ({1})", 119 | (int)response.StatusCode, 120 | response.StatusCode)) 121 | { 122 | StatusCode = response.StatusCode 123 | }; 124 | } 125 | } 126 | 127 | public async Task IndexGitHubOrganizationAsync(IndexGitHubOrganizationRequestDto indexOrganizationRequest, CancellationToken cancellationToken) 128 | { 129 | _logger.TraceMethodEntry(); 130 | 131 | var response = await _httpClient 132 | .PostAsJsonAsync("index-github-organization", indexOrganizationRequest, cancellationToken) 133 | .ConfigureAwait(false); 134 | 135 | if (!response.IsSuccessStatusCode) 136 | { 137 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 138 | "HTTP Request failed with Status: '{0}' ({1})", 139 | (int)response.StatusCode, 140 | response.StatusCode)) 141 | { 142 | StatusCode = response.StatusCode 143 | }; 144 | } 145 | } 146 | 147 | public async Task IndexGitHubRepositoryAsync(IndexGitHubRepositoryRequestDto indexRepositoryRequest, CancellationToken cancellationToken) 148 | { 149 | _logger.TraceMethodEntry(); 150 | 151 | var response = await _httpClient 152 | .PostAsJsonAsync("index-github-repository", indexRepositoryRequest, cancellationToken) 153 | .ConfigureAwait(false); 154 | 155 | if (!response.IsSuccessStatusCode) 156 | { 157 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 158 | "HTTP Request failed with Status: '{0}' ({1})", 159 | (int)response.StatusCode, 160 | response.StatusCode)) 161 | { 162 | StatusCode = response.StatusCode 163 | }; 164 | } 165 | } 166 | 167 | public async Task IndexDocumentsAsync(List documents, CancellationToken cancellationToken) 168 | { 169 | _logger.TraceMethodEntry(); 170 | 171 | var response = await _httpClient 172 | .PostAsJsonAsync("index-documents", documents, cancellationToken) 173 | .ConfigureAwait(false); 174 | 175 | if (!response.IsSuccessStatusCode) 176 | { 177 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 178 | "HTTP Request failed with Status: '{0}' ({1})", 179 | (int)response.StatusCode, 180 | response.StatusCode)) 181 | { 182 | StatusCode = response.StatusCode 183 | }; 184 | } 185 | } 186 | 187 | public async Task?> SearchStatisticsAsync(CancellationToken cancellationToken) 188 | { 189 | _logger.TraceMethodEntry(); 190 | 191 | var response = await _httpClient 192 | .GetAsync("search-statistics", cancellationToken) 193 | .ConfigureAwait(false); 194 | 195 | if (!response.IsSuccessStatusCode) 196 | { 197 | throw new ApiException(string.Format(CultureInfo.InvariantCulture, 198 | "HTTP Request failed with Status: '{0}' ({1})", 199 | (int)response.StatusCode, 200 | response.StatusCode)) 201 | { 202 | StatusCode = response.StatusCode 203 | }; 204 | } 205 | 206 | return await response.Content 207 | .ReadFromJsonAsync>(cancellationToken: cancellationToken) 208 | .ConfigureAwait(false); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Elasticsearch/Utils/ElasticsearchUtils.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace ElasticsearchCodeSearch.Shared.Elasticsearch.Utils 7 | { 8 | /// 9 | /// Auxillary methods to simplify working with Elasticsearch. 10 | /// 11 | public static class ElasticsearchUtils 12 | { 13 | /// 14 | /// Matches all content between a start and an end tag. 15 | /// 16 | private static readonly Regex regex = new Regex($"{ElasticsearchConstants.HighlightStartTag}(.*){ElasticsearchConstants.HighlightEndTag}"); 17 | 18 | /// 19 | /// Returns the Highlighted Content, with the line number, line content and the 20 | /// information wether to highlight a line or not. 21 | /// 22 | /// Matching Content from the Elasticsearch response 23 | /// List of highlighted content 24 | public static List GetHighlightedContent(string content) 25 | { 26 | // We want to highlight entire lines of code and don't want to only 27 | // highlight the match. So we need to get the number of matched lines 28 | // to highlight first: 29 | int matchedLinesCount = GetMatchedLinesCount(content); 30 | 31 | // We now want to process each line separately. We don't need to scale 32 | // massively, so we read the entire file content into memory. This won't 33 | // be too much hopefully ... 34 | var lines = content.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.None); 35 | 36 | // We need to know the total number of lines, so we know the maximum number of lines, 37 | // that we can take and highlight: 38 | int totalLines = lines.Length; 39 | 40 | // Now we need the start index, we begin the highlightning at: 41 | bool highlightFound = TryGetHighlightStartIndex(lines, out var startIdx); 42 | 43 | // Holds the Search Results: 44 | var result = new List(); 45 | 46 | // If no highlight was found, we return an empty list, because there is 47 | // nothing to highlight in the results anyway. Probably the filename 48 | // matched? 49 | if (!highlightFound) 50 | { 51 | return result; 52 | } 53 | 54 | // If there are at least 2 preceeding lines, we will 55 | // use the two preceeding lines in the snippet. 56 | int from = startIdx >= 2 ? startIdx - 2 : startIdx; 57 | 58 | // If there are more than 2 lines left, we will use 59 | // these trailing two lines in the snippet. 60 | int to = totalLines - startIdx > 3 ? startIdx + 2 : startIdx; 61 | 62 | // Build the result. 63 | for (int lineIdx = from; lineIdx <= to; lineIdx++) 64 | { 65 | // The raw line with the possible match tags. 66 | var line = lines[lineIdx]; 67 | 68 | // Remove the Start and End Tags from the content. 69 | var sanitizedLine = line 70 | .Replace(ElasticsearchConstants.HighlightStartTag, string.Empty) 71 | .Replace(ElasticsearchConstants.HighlightEndTag, string.Empty); 72 | 73 | // Check if this line has been a match. We could probably simplify the code 74 | // but I don't know. 75 | bool isHighlight = lineIdx >= startIdx && lineIdx < startIdx + matchedLinesCount; 76 | 77 | result.Add(new HighlightedContent 78 | { 79 | LineNo = lineIdx + 1, 80 | IsHighlight = isHighlight, 81 | Content = sanitizedLine 82 | }); 83 | } 84 | 85 | return result; 86 | } 87 | 88 | private static int GetMatchedLinesCount(string content) 89 | { 90 | var match = regex.Match(content); 91 | 92 | if (match.Groups.Count == 0) 93 | { 94 | // Just return 5 lines by default... 95 | return 0; 96 | } 97 | 98 | string matchedContent = match.Groups[1].Value; 99 | 100 | int matchedLinesCount = matchedContent 101 | .Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries) 102 | .Length; 103 | 104 | return matchedLinesCount; 105 | } 106 | 107 | private static bool TryGetHighlightStartIndex(string[] lines, out int startIdx) 108 | { 109 | startIdx = 0; 110 | 111 | for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++) 112 | { 113 | var line = lines[lineIdx]; 114 | 115 | if (line.Contains(ElasticsearchConstants.HighlightStartTag)) 116 | { 117 | startIdx = lineIdx; 118 | return true; 119 | } 120 | } 121 | 122 | return false; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/ElasticsearchCodeSearch.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Exceptions/ApiException.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Net; 4 | using System.Runtime.Serialization; 5 | 6 | namespace ElasticsearchCodeSearch.Shared.Exceptions 7 | { 8 | [Serializable] 9 | public class ApiException : Exception 10 | { 11 | public ApiException() 12 | { 13 | } 14 | 15 | public ApiException(string? message) : base(message) 16 | { 17 | } 18 | 19 | public ApiException(string? message, Exception? innerException) : base(message, innerException) 20 | { 21 | } 22 | 23 | protected ApiException(SerializationInfo info, StreamingContext context) : base(info, context) 24 | { 25 | } 26 | 27 | /// 28 | /// Http status code. 29 | /// 30 | public required HttpStatusCode StatusCode { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Shared/Logging/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.Extensions.Logging; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace ElasticsearchCodeSearch.Shared.Logging 7 | { 8 | public static class LoggerExtensions 9 | { 10 | public static bool IsDebugEnabled(this ILogger logger) 11 | { 12 | return logger.IsEnabled(LogLevel.Debug); 13 | } 14 | 15 | public static bool IsCriticalEnabled(this ILogger logger) 16 | { 17 | return logger.IsEnabled(LogLevel.Critical); 18 | } 19 | 20 | public static bool IsErrorEnabled(this ILogger logger) 21 | { 22 | return logger.IsEnabled(LogLevel.Error); 23 | } 24 | 25 | public static bool IsInformationEnabled(this ILogger logger) 26 | { 27 | return logger.IsEnabled(LogLevel.Information); 28 | } 29 | 30 | public static bool IsTraceEnabled(this ILogger logger) 31 | { 32 | return logger.IsEnabled(LogLevel.Trace); 33 | } 34 | 35 | public static bool IsWarningEnabled(this ILogger logger) 36 | { 37 | return logger.IsEnabled(LogLevel.Warning); 38 | } 39 | 40 | public static void TraceMethodEntry(this ILogger logger, [CallerMemberName] string callerMemberName = "") 41 | { 42 | if (logger.IsTraceEnabled()) 43 | { 44 | logger.LogTrace("Method Entry: {Method}", callerMemberName); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/NotificationCenter/NotificationCenter.razor: -------------------------------------------------------------------------------- 1 | @implements IDisposable 2 | @inject IDialogService DialogService 3 | @inject IMessageService MessageService 4 | 5 | @namespace ElasticsearchCodeSearch.Web.Client.Components 6 | 7 | 8 | @if (MessageService.Count(Routes.MESSAGES_NOTIFICATION_CENTER) > 0) 9 | { 10 | 17 | 18 | @NotificationIcon() 19 | 20 | 21 | } 22 | else 23 | { 24 | @NotificationIcon() 25 | } 26 | 27 | 28 | @code { 29 | private IDialogReference? _dialog; 30 | 31 | protected override void OnInitialized() 32 | { 33 | MessageService.OnMessageItemsUpdated += UpdateCount; 34 | } 35 | 36 | private void UpdateCount() 37 | { 38 | InvokeAsync(StateHasChanged); 39 | } 40 | 41 | private RenderFragment NotificationIcon() => 42 | @; 43 | 44 | private async Task OpenNotificationCenterAsync() 45 | { 46 | _dialog = await DialogService.ShowPanelAsync(new DialogParameters() 47 | { 48 | Alignment = HorizontalAlignment.Right, 49 | Title = $"Notifications", 50 | PrimaryAction = null, 51 | SecondaryAction = null, 52 | ShowDismiss = true 53 | }); 54 | DialogResult result = await _dialog.Result; 55 | HandlePanel(result); 56 | } 57 | 58 | private static void HandlePanel(DialogResult result) 59 | { 60 | if (result.Cancelled) 61 | { 62 | return; 63 | } 64 | 65 | if (result.Data is not null) 66 | { 67 | return; 68 | } 69 | } 70 | 71 | public void Dispose() 72 | { 73 | MessageService.OnMessageItemsUpdated -= UpdateCount; 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/NotificationCenter/NotificationCenterPanel.razor: -------------------------------------------------------------------------------- 1 | @implements IDialogContentComponent 2 | @inject IMessageService MessageService 3 | 4 | @namespace ElasticsearchCodeSearch.Web.Client.Components 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | Dismiss all 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | @code { 22 | [Parameter] 23 | public GlobalState Content { get; set; } = default!; 24 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/Paginator/Paginator.razor: -------------------------------------------------------------------------------- 1 | @namespace ElasticsearchCodeSearch.Web.Client.Components 2 | 3 | @inherits FluentComponentBase 4 |
5 | @if (State.TotalItemCount.HasValue) 6 | { 7 | 24 | } 25 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/Paginator/Paginator.razor.cs: -------------------------------------------------------------------------------- 1 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.FluentUI.AspNetCore.Components; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Components 6 | { 7 | /// 8 | /// A component that provides a user interface for . 9 | /// 10 | public partial class Paginator : FluentComponentBase, IDisposable 11 | { 12 | private readonly EventCallbackSubscriber _totalItemCountChanged; 13 | 14 | [Parameter] 15 | public EventCallback CurrentPageIndexChanged { get; set; } 16 | 17 | /// 18 | /// Disables the pagination buttons 19 | /// 20 | [Parameter] 21 | public bool Disabled { get; set; } 22 | 23 | /// 24 | /// Gets or sets the associated . This parameter is required. 25 | /// 26 | [Parameter, EditorRequired] 27 | public PaginatorState State { get; set; } = default!; 28 | 29 | /// 30 | /// Optionally supplies a template for rendering the page count summary. 31 | /// The following values can be included: 32 | /// {your State parameter name}.TotalItemCount (for the total number of items) 33 | /// 34 | [Parameter] 35 | public RenderFragment? SummaryTemplate { get; set; } 36 | 37 | /// 38 | /// Optionally supplies a template for rendering the pagination summary. 39 | /// The following values can be included: 40 | /// {your State parameter name}.CurrentPageIndex (zero-based, so +1 for the current page number) 41 | /// {your State parameter name}.LastPageIndex (zero-based, so +1 for the total number of pages) 42 | /// 43 | [Parameter] 44 | public RenderFragment? PaginationTextTemplate { get; set; } 45 | 46 | /// 47 | /// Constructs an instance of . 48 | /// 49 | public Paginator() 50 | { 51 | // The "total item count" handler doesn't need to do anything except cause this component to re-render 52 | _totalItemCountChanged = new(new EventCallback(this, null)); 53 | } 54 | 55 | private Task GoFirstAsync() => GoToPageAsync(0); 56 | private Task GoPreviousAsync() => GoToPageAsync(State.CurrentPageIndex - 1); 57 | private Task GoNextAsync() => GoToPageAsync(State.CurrentPageIndex + 1); 58 | private Task GoLastAsync() => GoToPageAsync(State.LastPageIndex.GetValueOrDefault(0)); 59 | 60 | private bool CanGoBack => State.CurrentPageIndex > 0; 61 | private bool CanGoForwards => State.CurrentPageIndex < State.LastPageIndex; 62 | 63 | private async Task GoToPageAsync(int pageIndex) 64 | { 65 | await State.SetCurrentPageIndexAsync(pageIndex); 66 | if (CurrentPageIndexChanged.HasDelegate) 67 | { 68 | await CurrentPageIndexChanged.InvokeAsync(State.CurrentPageIndex); 69 | } 70 | } 71 | 72 | /// 73 | protected override void OnParametersSet() 74 | => _totalItemCountChanged.SubscribeOrMove(State.TotalItemCountChangedSubscribable); 75 | 76 | /// 77 | public void Dispose() 78 | => _totalItemCountChanged.Dispose(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/Paginator/Paginator.razor.css: -------------------------------------------------------------------------------- 1 | .paginator { 2 | display: flex; 3 | /*border-top: 1px solid var(--neutral-stroke-divider-rest);*/ 4 | margin-top: 0.5rem; 5 | padding: 0.25rem 0; 6 | align-items: center; 7 | } 8 | 9 | .pagination-text { 10 | margin: 0 0.5rem; 11 | } 12 | 13 | .paginator-nav { 14 | padding: 0; 15 | display: flex; 16 | margin-inline-start: auto; 17 | margin-inline-end: 0; 18 | gap: 0.5rem; 19 | align-items: center; 20 | } 21 | 22 | 23 | [dir="rtl"] * ::deep fluent-button > svg { 24 | transform: rotate(180deg); 25 | } 26 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/Paginator/PaginatorState.cs: -------------------------------------------------------------------------------- 1 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Components 4 | { 5 | /// 6 | /// Holds state to represent pagination in a . 7 | /// 8 | public class PaginatorState 9 | { 10 | /// 11 | /// Gets or sets the number of items on each page. 12 | /// 13 | public int ItemsPerPage { get; set; } = 10; 14 | 15 | /// 16 | /// Gets the current zero-based page index. To set it, call . 17 | /// 18 | public int CurrentPageIndex { get; private set; } 19 | 20 | /// 21 | /// Gets the total number of items across all pages, if known. The value will be null until an 22 | /// associated assigns a value after loading data. 23 | /// 24 | public int? TotalItemCount { get; private set; } 25 | 26 | /// 27 | /// Gets the zero-based index of the last page, if known. The value will be null until is known. 28 | /// 29 | public int? LastPageIndex => (TotalItemCount - 1) / ItemsPerPage; 30 | 31 | /// 32 | /// An event that is raised when the total item count has changed. 33 | /// 34 | public event EventHandler? TotalItemCountChanged; 35 | 36 | internal EventCallbackSubscribable CurrentPageItemsChanged { get; } = new(); 37 | internal EventCallbackSubscribable TotalItemCountChangedSubscribable { get; } = new(); 38 | 39 | /// 40 | public override int GetHashCode() 41 | => HashCode.Combine(ItemsPerPage, CurrentPageIndex, TotalItemCount); 42 | 43 | /// 44 | /// Sets the current page index, and notifies any associated 45 | /// to fetch and render updated data. 46 | /// 47 | /// The new, zero-based page index. 48 | /// A representing the completion of the operation. 49 | public Task SetCurrentPageIndexAsync(int pageIndex) 50 | { 51 | CurrentPageIndex = pageIndex; 52 | return CurrentPageItemsChanged.InvokeCallbacksAsync(this); 53 | } 54 | 55 | public Task SetTotalItemCountAsync(int totalItemCount) 56 | { 57 | if (totalItemCount == TotalItemCount) 58 | { 59 | return Task.CompletedTask; 60 | } 61 | 62 | TotalItemCount = totalItemCount; 63 | 64 | if (CurrentPageIndex > 0 && CurrentPageIndex > LastPageIndex) 65 | { 66 | // If the number of items has reduced such that the current page index is no longer valid, move 67 | // automatically to the final valid page index and trigger a further data load. 68 | SetCurrentPageIndexAsync(LastPageIndex.Value); 69 | } 70 | 71 | // Under normal circumstances, we just want any associated pagination UI to update 72 | TotalItemCountChanged?.Invoke(this, TotalItemCount); 73 | return TotalItemCountChangedSubscribable.InvokeCallbacksAsync(this); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SearchResult/SearchResult.razor: -------------------------------------------------------------------------------- 1 | @namespace ElasticsearchCodeSearch.Web.Client.Components 2 | 3 | @using ElasticsearchCodeSearch.Shared.Dto; 4 | 5 |
6 |
7 | @Item.Owner/@Item.Repository - @Item.Path - (Updated at @Item.LatestCommitDate.ToString("g")) 8 |
9 |
10 | @foreach (var line in @Item.Content) 11 | { 12 |
13 |
14 |
15 | @line.LineNo 16 |
17 |
18 |
@line.Content
19 |
20 | } 21 |
22 |
23 | 24 | @code { 25 | /// 26 | /// Determines the classes to add for the Line. 27 | /// 28 | /// Highlighted Content 29 | /// 30 | string codeLineClass(HighlightedContentDto highlightedContent) => highlightedContent.IsHighlight ? "highlighted-line" : string.Empty; 31 | 32 | /// 33 | /// Filename. 34 | /// 35 | [Parameter] 36 | public required CodeSearchResultDto Item { get; set; } 37 | } 38 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SearchResult/SearchResult.razor.css: -------------------------------------------------------------------------------- 1 | .code-box { 2 | display: grid; 3 | grid-template-rows: auto 1fr auto; 4 | grid-template-columns: 1fr; 5 | grid-template-areas: 6 | "code-box-title" 7 | "code-box-content"; 8 | border: 1px solid var(--neutral-foreground-rest); 9 | background-color: var(--neutral-layer-1); 10 | } 11 | 12 | 13 | .code-box .code-box-title { 14 | grid-area: code-box-title; 15 | background-color: var(--neutral-layer-4); 16 | border-bottom: 1px solid var(--neutral-foreground-rest); 17 | padding: 10px 16px; 18 | } 19 | 20 | .code-box .code-box-content { 21 | display: grid; 22 | grid-area: code-box-content; 23 | grid-template-columns: 1fr; 24 | overflow: auto; 25 | white-space: nowrap; 26 | color: var(--neutral-foreground-rest); 27 | font-family: "JetBrains Mono","Menlo","DejaVu Sans Mono","Liberation Mono","Consolas","Ubuntu Mono","Courier New","andale mono","lucida console",monospace; 28 | } 29 | 30 | .code-box .code-box-content .highlighted-line { 31 | background-color: #f8eec7ab !important; 32 | } 33 | 34 | .code-box .code-box-content .code-line { 35 | display: grid; 36 | grid-template-columns: auto 1fr; 37 | grid-column-gap: 5px; 38 | } 39 | 40 | .code-box .code-box-content .code-line .code-line-number { 41 | display: grid; 42 | text-align: right; 43 | padding-right: 0.5rem; 44 | border-right: 1px solid var(--neutral-foreground-rest); 45 | min-width: 4rem; 46 | } 47 | 48 | .code-box .code-box-content .code-line .code-line-content { 49 | display: grid; 50 | text-align: left; 51 | white-space: pre; 52 | } 53 | 54 | 55 | .noselect { 56 | -webkit-touch-callout: none; /* iOS Safari */ 57 | -webkit-user-select: none; /* Safari */ 58 | -moz-user-select: none; /* Old versions of Firefox */ 59 | -ms-user-select: none; /* Internet Explorer/Edge */ 60 | user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */ 61 | } 62 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SiteSettings/SiteSettings.razor: -------------------------------------------------------------------------------- 1 | @namespace ElasticsearchCodeSearch.Web.Client.Components 2 | 3 | @inject IDialogService DialogService 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SiteSettings/SiteSettings.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.FluentUI.AspNetCore.Components; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Components; 6 | 7 | public partial class SiteSettings 8 | { 9 | private IDialogReference? _dialog; 10 | 11 | private async Task OpenSiteSettingsAsync() 12 | { 13 | 14 | _dialog = await DialogService.ShowPanelAsync(new DialogParameters() 15 | { 16 | ShowTitle = true, 17 | Title = "Site settings", 18 | Alignment = HorizontalAlignment.Right, 19 | PrimaryAction = "OK", 20 | SecondaryAction = null, 21 | ShowDismiss = true 22 | }); 23 | 24 | DialogResult result = await _dialog.Result; 25 | } 26 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SiteSettings/SiteSettingsPanel.razor: -------------------------------------------------------------------------------- 1 | @namespace ElasticsearchCodeSearch.Web.Client.Components 2 | @using Microsoft.FluentUI.AspNetCore.Components.Extensions 3 | @implements IDialogContentComponent 4 | 5 |
6 | 10 | 11 | 12 | 16 | 17 | 22 | 23 | 24 | 27 | @context 28 | 29 | 30 | 31 | 32 | 37 | 38 | These values (except for Direction) are persisted in the LocalStorage. 39 | You can recover this style during your next visits. 40 | 41 | 42 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SiteSettings/SiteSettingsPanel.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.FluentUI.AspNetCore.Components; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Components 6 | { 7 | public partial class SiteSettingsPanel 8 | { 9 | public DesignThemeModes Mode { get; set; } 10 | 11 | public OfficeColor? OfficeColor { get; set; } 12 | 13 | public bool Direction { get; set; } = true; 14 | 15 | private IEnumerable AllModes => Enum.GetValues(); 16 | 17 | private IEnumerable AllOfficeColors 18 | { 19 | get 20 | { 21 | return Enum.GetValues().Select(i => (OfficeColor?)i).Union(new[] { (OfficeColor?)null }); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SortOptionSelector/SortOptionSelector.razor: -------------------------------------------------------------------------------- 1 | @namespace ElasticsearchCodeSearch.Web.Client.Components 2 | 3 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 4 | @using ElasticsearchCodeSearch.Web.Client.Models; 5 | 6 | @inherits FluentComponentBase 7 | 18 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SortOptionSelector/SortOptionSelector.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Web.Client.Localization; 4 | using ElasticsearchCodeSearch.Web.Client.Models; 5 | using Microsoft.AspNetCore.Components; 6 | using Microsoft.Extensions.Localization; 7 | 8 | namespace ElasticsearchCodeSearch.Web.Client.Components 9 | { 10 | public partial class SortOptionSelector 11 | { 12 | /// 13 | /// Localizer. 14 | /// 15 | [Inject] 16 | public IStringLocalizer Loc { get; set; } = default!; 17 | 18 | /// 19 | /// Text used on aria-label attribute. 20 | /// 21 | [Parameter] 22 | public virtual string? Title { get; set; } 23 | 24 | /// 25 | /// If true, will disable the list of items. 26 | /// 27 | [Parameter] 28 | public virtual bool Disabled { get; set; } = false; 29 | 30 | /// 31 | /// Gets or sets the content to be rendered inside the component. 32 | /// In this case list of FluentOptions 33 | /// 34 | [Parameter] 35 | public virtual RenderFragment? ChildContent { get; set; } 36 | 37 | /// 38 | /// All selectable Sort Options. 39 | /// 40 | [Parameter] 41 | public required SortOptionEnum[] SortOptions { get; set; } 42 | 43 | /// 44 | /// The Sort Option. 45 | /// 46 | [Parameter] 47 | public SortOptionEnum SortOption { get; set; } 48 | 49 | /// 50 | /// Invoked, when the SortOption has changed. 51 | /// 52 | [Parameter] 53 | public EventCallback SortOptionChanged { get; set; } 54 | 55 | /// 56 | /// Value. 57 | /// 58 | string? _value { get; set; } 59 | 60 | /// 61 | /// Filter Operator. 62 | /// 63 | private SortOptionEnum _sortOption { get; set; } 64 | 65 | protected override void OnParametersSet() 66 | { 67 | _sortOption = SortOption; 68 | _value = SortOption.ToString(); 69 | } 70 | 71 | public void OnSelectedValueChanged(SortOptionEnum value) 72 | { 73 | _sortOption = value; 74 | _value = value.ToString(); 75 | 76 | SortOptionChanged.InvokeAsync(_sortOption); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Components/SortOptionSelector/SortOptionSelector.razor.css: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/ElasticsearchCodeSearch.Web.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | Default 9 | 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | true 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | PreserveNewest 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.AspNetCore.Components; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Extensions 6 | { 7 | public static class StringExtensions 8 | { 9 | public static MarkupString? AsMarkupString(this string? source) 10 | { 11 | if(source == null) 12 | { 13 | return null; 14 | } 15 | 16 | return (MarkupString?)source; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Extensions/StringLocalizerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.Extensions.Localization; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Extensions 6 | { 7 | public static class StringLocalizerExtensions 8 | { 9 | public static string TranslateEnum(this IStringLocalizer localizer, TEnum enumValue) 10 | { 11 | var key = $"{typeof(TEnum).Name}_{enumValue}"; 12 | 13 | var res = localizer.GetString(key); 14 | 15 | return res; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/ApplicationErrorMessageService.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.Extensions.Localization; 5 | using Microsoft.FluentUI.AspNetCore.Components; 6 | using ElasticsearchCodeSearch.Web.Client.Localization; 7 | 8 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 9 | { 10 | public class ApplicationErrorMessageService 11 | { 12 | private readonly IStringLocalizer _sharedLocalizer; 13 | private readonly ApplicationErrorTranslator _applicationErrorTranslator; 14 | private readonly IMessageService _messageService; 15 | private readonly NavigationManager _navigationManager; 16 | 17 | 18 | public ApplicationErrorMessageService(IStringLocalizer sharedLocalizer, IMessageService messageService, NavigationManager navigationManager, ApplicationErrorTranslator applicationErrorTranslator) 19 | { 20 | _sharedLocalizer = sharedLocalizer; 21 | _navigationManager = navigationManager; 22 | _applicationErrorTranslator = applicationErrorTranslator; 23 | _messageService = messageService; 24 | } 25 | 26 | public void ShowErrorMessage(Exception exception, Action? configure = null) 27 | { 28 | (var errorCode, var errorMessage) = _applicationErrorTranslator.GetErrorMessage(exception); 29 | 30 | _messageService.ShowMessageBar(options => 31 | { 32 | options.Section = Routes.MESSAGES_TOP; 33 | options.Intent = MessageIntent.Error; 34 | options.ClearAfterNavigation = false; 35 | options.Title = _sharedLocalizer["Message_Error_Title"]; 36 | options.Body = errorMessage; 37 | options.Timestamp = DateTime.Now; 38 | options.Link = new ActionLink 39 | { 40 | Text = _sharedLocalizer["Message_ShowHelp"], 41 | OnClick = (message) => 42 | { 43 | _navigationManager.NavigateTo($"https://www.bytefish.de"); 44 | 45 | return Task.CompletedTask; 46 | } 47 | }; 48 | 49 | // If we need to customize it like using a different section or intent, we should 50 | // use the action passed to us ... 51 | if (configure != null) 52 | { 53 | configure(options); 54 | } 55 | }); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/ApplicationErrorTranslator.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Web.Client.Localization; 4 | using Microsoft.Extensions.Localization; 5 | 6 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 7 | { 8 | public class ApplicationErrorTranslator 9 | { 10 | private readonly IStringLocalizer _sharedLocalizer; 11 | 12 | public ApplicationErrorTranslator(IStringLocalizer sharedLocalizer) 13 | { 14 | _sharedLocalizer = sharedLocalizer; 15 | } 16 | 17 | public (string ErrorCode, string ErrorMessage) GetErrorMessage(Exception exception) 18 | { 19 | return exception switch 20 | { 21 | Exception e => (LocalizationConstants.ClientError_UnexpectedError, GetErrorMessageFromException(e)), 22 | }; 23 | } 24 | 25 | private string GetErrorMessageFromException(Exception e) 26 | { 27 | string errorMessage = _sharedLocalizer["ApplicationError_Exception"]; 28 | 29 | return errorMessage; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/DataSizeUtils.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 4 | { 5 | public class DataSizeUtils 6 | { 7 | /// 8 | /// The size of a byte, in bytes. Always 1, provided for consistency only. 9 | /// 10 | public const long ByteSize = 1L; 11 | 12 | /// 13 | /// The size of a kilobyte, in bytes. This structure defines a KB as 1,024 bytes. 14 | /// 15 | public const long KilobyteSize = 1024L; 16 | 17 | /// 18 | /// The size of a megabyte, in bytes. This structure defines a MB as 1,024^2 or 1,048,576 bytes. 19 | /// 20 | public const long MegabyteSize = 1024L * 1024L; 21 | 22 | /// 23 | /// The size of a gigabyte, in bytes. This structure defines a GB as 1,024^3 or 1,073,741,824 bytes. 24 | /// 25 | public const long GigabyteSize = 1024L * 1024L * 1024L; 26 | 27 | /// 28 | /// The size of a terabyte, in bytes. This structure defines a TB as 1,024^4 or 1,099,511,627,776 bytes. 29 | /// 30 | public const long TerabyteSize = 1024L * 1024L * 1024L * 1024L; 31 | 32 | /// 33 | /// Gets the value in terabytes. 34 | /// 35 | public static decimal TotalTerabytes(long bytes) 36 | { 37 | return bytes / (decimal)TerabyteSize; 38 | } 39 | 40 | /// 41 | /// Gets the value in gigabytes. 42 | /// 43 | public static decimal TotalGigabytes(long bytes) 44 | { 45 | return bytes / (decimal)GigabyteSize; 46 | } 47 | 48 | /// 49 | /// Gets the value in megabytes. 50 | /// 51 | public static decimal TotalMegabytes(long bytes) 52 | { 53 | return bytes / (decimal)MegabyteSize; 54 | } 55 | 56 | /// 57 | /// Gets the value in kilobytes. 58 | /// 59 | public static decimal TotalKilobytes(long bytes) 60 | { 61 | return bytes / (decimal)KilobyteSize; 62 | } 63 | 64 | /// 65 | /// Gets the value in terabytes. 66 | /// 67 | public static string TotalTerabytesString(long bytes) 68 | { 69 | var terabyte = TotalTerabytes(bytes); 70 | 71 | return $"{terabyte:F2} TB"; 72 | 73 | } 74 | 75 | /// 76 | /// Gets the value in gigabytes. 77 | /// 78 | public static string TotalGigabytesString(long bytes) 79 | { 80 | var gigabytes = TotalGigabytes(bytes); 81 | 82 | return $"{gigabytes:F2} GB"; 83 | } 84 | 85 | /// 86 | /// Gets the value in megabytes. 87 | /// 88 | public static string TotalMegabytesString(long bytes) 89 | { 90 | var megabytes = TotalMegabytes(bytes); 91 | 92 | return $"{megabytes:F2} MB"; 93 | } 94 | 95 | /// 96 | /// Gets the value in kilobytes. 97 | /// 98 | public static string TotalKilobytesString(long bytes) 99 | { 100 | var kilobytes = TotalKilobytes(bytes); 101 | 102 | return $"{kilobytes:F2} KB"; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/EventCallbackSubscribable.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.AspNetCore.Components; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 6 | { 7 | public sealed class EventCallbackSubscribable 8 | { 9 | private readonly Dictionary, EventCallback> _callbacks = new(); 10 | 11 | /// 12 | /// Invokes all the registered callbacks sequentially, in an undefined order. 13 | /// 14 | public async Task InvokeCallbacksAsync(T eventArg) 15 | { 16 | foreach (var callback in _callbacks.Values) 17 | { 18 | await callback.InvokeAsync(eventArg); 19 | } 20 | } 21 | 22 | // Don't call this directly - it gets called by EventCallbackSubscription 23 | public void Subscribe(EventCallbackSubscriber owner, EventCallback callback) 24 | => _callbacks.Add(owner, callback); 25 | 26 | // Don't call this directly - it gets called by EventCallbackSubscription 27 | public void Unsubscribe(EventCallbackSubscriber owner) 28 | => _callbacks.Remove(owner); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/EventCallbackSubscriber.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.AspNetCore.Components; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 6 | { 7 | public sealed class EventCallbackSubscriber : IDisposable 8 | { 9 | private readonly EventCallback _handler; 10 | private EventCallbackSubscribable? _existingSubscription; 11 | 12 | public EventCallbackSubscriber(EventCallback handler) 13 | { 14 | _handler = handler; 15 | } 16 | 17 | /// 18 | /// Creates a subscription on the , or moves any existing subscription to it 19 | /// by first unsubscribing from the previous . 20 | /// 21 | /// If the supplied is null, no new subscription will be created, but any 22 | /// existing one will still be unsubscribed. 23 | /// 24 | /// 25 | public void SubscribeOrMove(EventCallbackSubscribable? subscribable) 26 | { 27 | if (subscribable != _existingSubscription) 28 | { 29 | _existingSubscription?.Unsubscribe(this); 30 | subscribable?.Subscribe(this, _handler); 31 | _existingSubscription = subscribable; 32 | } 33 | } 34 | 35 | public void Dispose() 36 | { 37 | _existingSubscription?.Unsubscribe(this); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/SimpleValidator.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.AspNetCore.Components.Forms; 4 | using Microsoft.AspNetCore.Components; 5 | using System.Reflection.Metadata; 6 | 7 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 8 | { 9 | /// 10 | /// Validation Error for a Property 11 | /// 12 | public record ValidationError 13 | { 14 | /// 15 | /// Gets or sets the PropertyName. 16 | /// 17 | public required string PropertyName { get; set; } 18 | 19 | /// 20 | /// Gets or sets the ErrorMessage. 21 | /// 22 | public required string ErrorMessage { get; set; } 23 | } 24 | 25 | /// 26 | /// Provides a SimpleValidator, which takes a Validation function for the model to be validated. 27 | /// 28 | /// Type of the Model in the 29 | public class SimpleValidator : ComponentBase, IDisposable 30 | { 31 | private IDisposable? _subscriptions; 32 | private EditContext? _originalEditContext; 33 | 34 | [CascadingParameter] EditContext? CurrentEditContext { get; set; } 35 | 36 | [Parameter] 37 | public Func> ValidationFunc { get; set; } = null!; 38 | 39 | /// 40 | protected override void OnInitialized() 41 | { 42 | if (CurrentEditContext == null) 43 | { 44 | throw new InvalidOperationException($"{nameof(SimpleValidator)} requires a cascading " + 45 | $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " + 46 | $"inside an EditForm."); 47 | } 48 | 49 | _subscriptions = CurrentEditContext.EnableSimpleValidation(ValidationFunc); 50 | _originalEditContext = CurrentEditContext; 51 | } 52 | 53 | /// 54 | protected override void OnParametersSet() 55 | { 56 | if (CurrentEditContext != _originalEditContext) 57 | { 58 | // While we could support this, there's no known use case presently. Since InputBase doesn't support it, 59 | // it's more understandable to have the same restriction. 60 | throw new InvalidOperationException($"{GetType()} does not support changing the {nameof(EditContext)} dynamically."); 61 | } 62 | } 63 | 64 | /// 65 | protected virtual void Dispose(bool disposing) 66 | { 67 | } 68 | 69 | void IDisposable.Dispose() 70 | { 71 | _subscriptions?.Dispose(); 72 | _subscriptions = null; 73 | 74 | Dispose(disposing: true); 75 | } 76 | } 77 | 78 | public static class EditContextSimpleValidationExtensions 79 | { 80 | /// 81 | /// Enables validation support for the . 82 | /// 83 | /// The . 84 | /// Validation function to apply 85 | /// A disposable object whose disposal will remove DataAnnotations validation support from the . 86 | public static IDisposable EnableSimpleValidation(this EditContext editContext, Func> validationFunc) 87 | { 88 | return new SimpleValidationEventSubscriptions(editContext, validationFunc); 89 | } 90 | 91 | private sealed class SimpleValidationEventSubscriptions : IDisposable 92 | { 93 | private readonly EditContext _editContext; 94 | private readonly Func> _validationFunc; 95 | private readonly ValidationMessageStore _messages; 96 | 97 | public SimpleValidationEventSubscriptions(EditContext editContext, Func> validationFunc) 98 | { 99 | _editContext = editContext ?? throw new ArgumentNullException(nameof(editContext)); 100 | _validationFunc = validationFunc; 101 | _messages = new ValidationMessageStore(_editContext); 102 | 103 | _editContext.OnFieldChanged += OnFieldChanged; 104 | _editContext.OnValidationRequested += OnValidationRequested; 105 | } 106 | 107 | private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) 108 | { 109 | _messages.Clear(); 110 | 111 | var validationErrors = _validationFunc((TModel)_editContext.Model); 112 | 113 | foreach (var validationError in validationErrors) 114 | { 115 | _messages.Add(_editContext.Field(validationError.PropertyName), validationError.ErrorMessage); 116 | } 117 | 118 | _editContext.NotifyValidationStateChanged(); 119 | } 120 | 121 | private void OnValidationRequested(object? sender, ValidationRequestedEventArgs eventArgs) 122 | { 123 | _messages.Clear(); 124 | 125 | var validationErrors = _validationFunc((TModel)_editContext.Model); 126 | 127 | foreach (var validationError in validationErrors) 128 | { 129 | _messages.Add(_editContext.Field(validationError.PropertyName), validationError.ErrorMessage); 130 | } 131 | 132 | _editContext.NotifyValidationStateChanged(); 133 | } 134 | 135 | public void Dispose() 136 | { 137 | _messages.Clear(); 138 | _editContext.OnFieldChanged -= OnFieldChanged; 139 | _editContext.OnValidationRequested -= OnValidationRequested; 140 | _editContext.NotifyValidationStateChanged(); 141 | } 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/StringLocalizerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.Extensions.Localization; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 6 | { 7 | public static class StringLocalizerExtensions 8 | { 9 | public static string TranslateEnum(this IStringLocalizer localizer, TEnum enumValue) 10 | { 11 | var key = $"{typeof(TEnum).Name}_{enumValue}"; 12 | 13 | var res = localizer.GetString(key); 14 | 15 | return res; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Infrastructure/TimeFormattingUtils.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Globalization; 4 | 5 | namespace ElasticsearchCodeSearch.Web.Client.Infrastructure 6 | { 7 | public static class TimeFormattingUtils 8 | { 9 | public static string MillisecondsToSeconds(long? milliseconds, string defaultValue) 10 | { 11 | if(!milliseconds.HasValue) 12 | { 13 | return defaultValue; 14 | } 15 | 16 | var timeSpan = TimeSpan.FromMilliseconds(milliseconds.Value); 17 | 18 | return timeSpan.TotalSeconds.ToString("F"); 19 | } 20 | 21 | public static string MillisecondsToSeconds(long? milliseconds, string defaultValue, CultureInfo cultureInfo) 22 | { 23 | if (!milliseconds.HasValue) 24 | { 25 | return defaultValue; 26 | } 27 | 28 | var timeSpan = TimeSpan.FromMilliseconds(milliseconds.Value); 29 | 30 | return timeSpan.TotalSeconds.ToString("F", cultureInfo); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Localization/LocalizationConstants.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Localization 4 | { 5 | public static class LocalizationConstants 6 | { 7 | public const string ClientError_UnexpectedError = "ClientError_UnexpectedError"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Localization/SharedResource.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Localization 4 | { 5 | public class SharedResource 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Models/ElasticsearchIndexMetrics.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Models 4 | { 5 | public class ElasticsearchIndexMetrics 6 | { 7 | /// 8 | /// Index. 9 | /// 10 | public required string Index { get; set; } 11 | 12 | /// 13 | /// Value. 14 | /// 15 | public required List Metrics { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Models/ElasticsearchMetric.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Models 4 | { 5 | public class ElasticsearchMetric 6 | { 7 | /// 8 | /// Name. 9 | /// 10 | public required string Name { get; set; } 11 | 12 | /// 13 | /// Elasticsearch Key. 14 | /// 15 | public required string Key { get; set; } 16 | 17 | /// 18 | /// Value. 19 | /// 20 | public required string? Value { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Models/SortOptionEnum.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Web.Client.Models 4 | { 5 | /// 6 | /// Sort Options for sorting search results. 7 | /// 8 | public enum SortOptionEnum 9 | { 10 | /// 11 | /// Sorts by Owner in ascending order. 12 | /// 13 | OwnerAscending = 1, 14 | 15 | /// 16 | /// Sorts by Owner in descending order. 17 | /// 18 | OwnerDescending = 2, 19 | 20 | /// 21 | /// Sorts by Repository in ascending order. 22 | /// 23 | RepositoryAscending = 3, 24 | 25 | /// 26 | /// Sorts by Respository in ascending order. 27 | /// 28 | RepositoryDescending = 4, 29 | 30 | /// 31 | /// Sorts by Latest Commit Date in ascending order. 32 | /// 33 | LatestCommitDateAscending = 5, 34 | 35 | /// 36 | /// Sorts by Latest Commit Date in descending order. 37 | /// 38 | LatestCommitDateDescending = 6, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/CodeSearch.razor: -------------------------------------------------------------------------------- 1 | @page "/CodeSearch" 2 | 3 | @using ElasticsearchCodeSearch.Web.Client.Components 4 | @using ElasticsearchCodeSearch.Web.Client.Extensions; 5 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | @using ElasticsearchCodeSearch.Shared.Dto; 7 | 8 | Elasticsearch Code Search Experiments 9 | 10 |
11 |
12 | 13 | 19 |
20 |
21 | @_totalItemCount Results (@_tookInSeconds seconds) 22 |
23 |
24 | @foreach (var searchResult in _codeSearchResults) 25 | { 26 | 27 | } 28 |
29 |
30 | 31 |
32 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/CodeSearch.razor.css: -------------------------------------------------------------------------------- 1 | .search-container { 2 | display: grid; 3 | height: 100%; 4 | grid-template-rows: auto auto 1fr auto; 5 | grid-template-columns: 1fr; 6 | grid-row-gap: 10px; 7 | grid-template-areas: 8 | "search-header" 9 | "search-results-total" 10 | "search-results" 11 | "search-paginator" 12 | } 13 | 14 | .search-header { 15 | display: grid; 16 | grid-area: search-header; 17 | grid-template-columns: minmax(auto, 900px); 18 | grid-template-rows: auto 1fr; 19 | justify-content: center; 20 | padding: 1rem; 21 | border-bottom: 1px solid var(--neutral-foreground-rest); 22 | } 23 | 24 | .search-header .search-title { 25 | color: black; 26 | } 27 | 28 | .search-title { 29 | display: grid; 30 | justify-content: center; 31 | } 32 | 33 | .search-box { 34 | display: grid; 35 | min-width: 500px; 36 | justify-content: center; 37 | grid-template-columns: 1fr auto auto; 38 | grid-column-gap: 10px; 39 | } 40 | 41 | .search-results-total { 42 | display: grid; 43 | grid-area: search-results-total; 44 | justify-content: center; 45 | grid-template-columns: auto; 46 | } 47 | 48 | .search-results { 49 | display: grid; 50 | grid-area: search-results; 51 | grid-template-columns: 1fr; 52 | grid-auto-rows: max-content; 53 | max-width: 1000px; 54 | margin: 0 auto; 55 | grid-row-gap: 20px; 56 | } 57 | 58 | .search-paginator { 59 | display: grid; 60 | grid-area: search-paginator; 61 | min-width: 500px; 62 | justify-content: center; 63 | grid-template-columns: auto; 64 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitHubOrganizationCodeIndex.razor: -------------------------------------------------------------------------------- 1 | @page "/IndexGitHubOrganization" 2 | 3 | @using ElasticsearchCodeSearch.Web.Client.Components 4 | @using ElasticsearchCodeSearch.Web.Client.Extensions; 5 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | @using ElasticsearchCodeSearch.Models 7 | @using ElasticsearchCodeSearch.Shared.Dto; 8 | @using ElasticsearchCodeSearch.Shared.Services 9 | 10 | @inject ElasticsearchCodeSearchService ElasticsearchCodeSearchService 11 | @inject IStringLocalizer Loc 12 | 13 | Index GitHub Repository 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 | Submit Organization to Index 29 | Discard Changes 30 | 31 |
32 |
33 | 34 |
35 |
36 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitHubOrganizationCodeIndex.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Shared.Services; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | using Microsoft.Extensions.Localization; 7 | 8 | namespace ElasticsearchCodeSearch.Web.Client.Pages 9 | { 10 | public partial class GitHubOrganizationCodeIndex 11 | { 12 | /// 13 | /// GitHub Repositories. 14 | /// 15 | private IndexGitHubOrganizationRequestDto CurrentGitRepository = new IndexGitHubOrganizationRequestDto 16 | { 17 | Organization = string.Empty 18 | }; 19 | 20 | /// 21 | /// Submits the Form and reloads the updated data. 22 | /// 23 | /// An awaitable 24 | private async Task HandleValidSubmitAsync() 25 | { 26 | await ElasticsearchCodeSearchService.IndexGitHubOrganizationAsync(CurrentGitRepository, default); 27 | 28 | CurrentGitRepository = new IndexGitHubOrganizationRequestDto 29 | { 30 | Organization = string.Empty 31 | }; 32 | } 33 | 34 | /// 35 | /// Submits the Form and reloads the updated data. 36 | /// 37 | /// An awaitable 38 | private Task HandleDiscardAsync() 39 | { 40 | CurrentGitRepository = new IndexGitHubOrganizationRequestDto 41 | { 42 | Organization = string.Empty 43 | }; 44 | 45 | return Task.CompletedTask; 46 | } 47 | 48 | /// 49 | /// Validates a . 50 | /// 51 | /// Item to validate 52 | /// The list of validation errors for the EditContext model fields 53 | private IEnumerable ValidateGitRepository(IndexGitHubOrganizationRequestDto repository) 54 | { 55 | if (string.IsNullOrWhiteSpace(repository.Organization)) 56 | { 57 | yield return new ValidationError 58 | { 59 | PropertyName = nameof(repository.Organization), 60 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Organization)) 61 | }; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitHubOrganizationCodeIndex.razor.css: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitHubRepositoryCodeIndex.razor: -------------------------------------------------------------------------------- 1 | @page "/IndexGitHubRepository" 2 | 3 | @using ElasticsearchCodeSearch.Web.Client.Components 4 | @using ElasticsearchCodeSearch.Web.Client.Extensions; 5 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | @using ElasticsearchCodeSearch.Models 7 | @using ElasticsearchCodeSearch.Shared.Dto; 8 | @using ElasticsearchCodeSearch.Shared.Services 9 | 10 | @inject ElasticsearchCodeSearchService ElasticsearchCodeSearchService 11 | @inject IStringLocalizer Loc 12 | 13 | Index GitHub Repository 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | Submit Repository to Index 37 | Discard Changes 38 | 39 |
40 |
41 | 42 |
43 |
44 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitHubRepositoryCodeIndex.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Microsoft.AspNetCore.Components; 4 | using ElasticsearchCodeSearch.Shared.Services; 5 | using ElasticsearchCodeSearch.Shared.Dto; 6 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 7 | using Microsoft.Extensions.Localization; 8 | 9 | namespace ElasticsearchCodeSearch.Web.Client.Pages 10 | { 11 | public partial class GitHubRepositoryCodeIndex 12 | { 13 | /// 14 | /// GitHub Repositories. 15 | /// 16 | private IndexGitHubRepositoryRequestDto CurrentGitRepository = new IndexGitHubRepositoryRequestDto 17 | { 18 | Owner = string.Empty, 19 | Repository = string.Empty, 20 | }; 21 | 22 | /// 23 | /// Submits the Form and reloads the updated data. 24 | /// 25 | /// An awaitable 26 | private async Task HandleValidSubmitAsync() 27 | { 28 | await ElasticsearchCodeSearchService.IndexGitHubRepositoryAsync(CurrentGitRepository, default); 29 | 30 | CurrentGitRepository = new IndexGitHubRepositoryRequestDto 31 | { 32 | Owner = string.Empty, 33 | Repository = string.Empty, 34 | }; 35 | } 36 | 37 | /// 38 | /// Submits the Form and reloads the updated data. 39 | /// 40 | /// An awaitable 41 | private Task HandleDiscardAsync() 42 | { 43 | CurrentGitRepository = new IndexGitHubRepositoryRequestDto 44 | { 45 | Owner = string.Empty, 46 | Repository = string.Empty, 47 | }; 48 | 49 | return Task.CompletedTask; 50 | } 51 | 52 | /// 53 | /// Validates a . 54 | /// 55 | /// Item to validate 56 | /// The list of validation errors for the EditContext model fields 57 | private IEnumerable ValidateGitRepository(IndexGitHubRepositoryRequestDto repository) 58 | { 59 | if (string.IsNullOrWhiteSpace(repository.Owner)) 60 | { 61 | yield return new ValidationError 62 | { 63 | PropertyName = nameof(repository.Owner), 64 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Owner)) 65 | }; 66 | } 67 | 68 | if (string.IsNullOrWhiteSpace(repository.Repository)) 69 | { 70 | yield return new ValidationError 71 | { 72 | PropertyName = nameof(repository.Repository), 73 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Repository)) 74 | }; 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitRepositoryCodeIndex.razor: -------------------------------------------------------------------------------- 1 | @page "/IndexGitRepository" 2 | 3 | @using ElasticsearchCodeSearch.Web.Client.Components 4 | @using ElasticsearchCodeSearch.Web.Client.Extensions; 5 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | @using ElasticsearchCodeSearch.Models 7 | @using ElasticsearchCodeSearch.Shared.Constants 8 | @using ElasticsearchCodeSearch.Shared.Dto; 9 | @using ElasticsearchCodeSearch.Shared.Services 10 | 11 | @inject ElasticsearchCodeSearchService ElasticsearchCodeSearchService 12 | @inject IStringLocalizer Loc 13 | 14 | Index Git Repository 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 | 57 |
58 |
59 |
60 | 61 |
62 | 63 | @Loc["SourceSystems_GitHub"] 64 | @Loc["SourceSystems_GitLab"] 65 | @Loc["SourceSystems_Codeberg"] 66 | @Loc["SourceSystems_Custom"] 67 | 68 |
69 | 70 |
71 |
72 |
73 | 74 | Submit Repository to Index 75 | Discard Changes 76 | 77 |
78 |
79 | 80 |
81 |
82 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/GitRepositoryCodeIndex.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Shared.Services; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | using Microsoft.Extensions.Localization; 7 | using ElasticsearchCodeSearch.Shared.Constants; 8 | 9 | namespace ElasticsearchCodeSearch.Web.Client.Pages 10 | { 11 | public partial class GitRepositoryCodeIndex 12 | { 13 | /// 14 | /// GitHub Repositories. 15 | /// 16 | private GitRepositoryMetadataDto CurrentGitRepository = new GitRepositoryMetadataDto 17 | { 18 | Owner = string.Empty, 19 | Name = string.Empty, 20 | Branch = string.Empty, 21 | CloneUrl = string.Empty, 22 | Language = string.Empty, 23 | Source = SourceSystems.GitHub, 24 | }; 25 | 26 | 27 | /// 28 | /// Submits the Form and reloads the updated data. 29 | /// 30 | /// An awaitable 31 | private async Task HandleValidSubmitAsync() 32 | { 33 | await ElasticsearchCodeSearchService.IndexGitRepositoryAsync(CurrentGitRepository, default); 34 | 35 | CurrentGitRepository = new GitRepositoryMetadataDto 36 | { 37 | Branch = string.Empty, 38 | Name = string.Empty, 39 | CloneUrl = string.Empty, 40 | Owner = string.Empty, 41 | Source = SourceSystems.GitHub, 42 | }; 43 | } 44 | 45 | /// 46 | /// Submits the Form and reloads the updated data. 47 | /// 48 | /// An awaitable 49 | private Task HandleDiscardAsync() 50 | { 51 | CurrentGitRepository = new GitRepositoryMetadataDto 52 | { 53 | Branch = string.Empty, 54 | Name = string.Empty, 55 | CloneUrl = string.Empty, 56 | Owner = string.Empty, 57 | Source = SourceSystems.GitHub 58 | }; 59 | 60 | return Task.CompletedTask; 61 | } 62 | 63 | /// 64 | /// Validates a . 65 | /// 66 | /// Item to validate 67 | /// The list of validation errors for the EditContext model fields 68 | private IEnumerable ValidateGitRepository(GitRepositoryMetadataDto repository) 69 | { 70 | if (string.IsNullOrWhiteSpace(repository.Owner)) 71 | { 72 | yield return new ValidationError 73 | { 74 | PropertyName = nameof(repository.Owner), 75 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Owner)) 76 | }; 77 | } 78 | 79 | if (string.IsNullOrWhiteSpace(repository.Name)) 80 | { 81 | yield return new ValidationError 82 | { 83 | PropertyName = nameof(repository.Name), 84 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Name)) 85 | }; 86 | } 87 | 88 | if (string.IsNullOrWhiteSpace(repository.Branch)) 89 | { 90 | yield return new ValidationError 91 | { 92 | PropertyName = nameof(repository.Branch), 93 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Branch)) 94 | }; 95 | } 96 | 97 | if (string.IsNullOrWhiteSpace(repository.CloneUrl)) 98 | { 99 | yield return new ValidationError 100 | { 101 | PropertyName = nameof(repository.CloneUrl), 102 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.CloneUrl)) 103 | }; 104 | } 105 | 106 | if (string.IsNullOrWhiteSpace(repository.Source)) 107 | { 108 | yield return new ValidationError 109 | { 110 | PropertyName = nameof(repository.Source), 111 | ErrorMessage = Loc.GetString("Validation_IsRequired", nameof(repository.Source)) 112 | }; 113 | } 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 2 | @page "/" 3 | 4 | Search Cluster Overview 5 | 6 |

Search Cluster Overview

7 | 8 |

9 | This page gives you an overview for all indices in your Elasticsearch cluster. 10 |

11 | 12 | @foreach (var indexMetric in _elasticsearchIndexMetrics) 13 | { 14 |

Index "@indexMetric.Index"

15 | 16 | 17 | 18 | 19 | 20 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/Index.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 4 | using ElasticsearchCodeSearch.Web.Client.Localization; 5 | using ElasticsearchCodeSearch.Web.Client.Models; 6 | using ElasticsearchCodeSearch.Shared.Dto; 7 | using ElasticsearchCodeSearch.Shared.Services; 8 | using Microsoft.AspNetCore.Components; 9 | using Microsoft.Extensions.Localization; 10 | 11 | namespace ElasticsearchCodeSearch.Web.Client.Pages 12 | { 13 | public partial class Index 14 | { 15 | /// 16 | /// Elasticsearch Search Client. 17 | /// 18 | [Inject] 19 | public ElasticsearchCodeSearchService ElasticsearchCodeSearchService { get; set; } = default!; 20 | 21 | /// 22 | /// Shared String Localizer. 23 | /// 24 | [Inject] 25 | public IStringLocalizer Loc { get; set; } = default!; 26 | 27 | /// 28 | /// Search Statistics. 29 | /// 30 | private List _elasticsearchIndexMetrics = new List(); 31 | 32 | protected override async Task OnInitializedAsync() 33 | { 34 | var codeSearchStatistics = await ElasticsearchCodeSearchService.SearchStatisticsAsync(default); 35 | 36 | _elasticsearchIndexMetrics = ConvertToElasticsearchIndexMetric(codeSearchStatistics); 37 | } 38 | 39 | private List ConvertToElasticsearchIndexMetric(List? codeSearchStatistics) 40 | { 41 | if(codeSearchStatistics == null) 42 | { 43 | return new List(); 44 | } 45 | 46 | return codeSearchStatistics 47 | .Select(x => new ElasticsearchIndexMetrics 48 | { 49 | Index = x.IndexName, 50 | Metrics = ConvertToElasticsearchMetrics(x) 51 | }).ToList(); 52 | 53 | } 54 | 55 | private List ConvertToElasticsearchMetrics(CodeSearchStatisticsDto codeSearchStatistic) 56 | { 57 | return new List() 58 | { 59 | new ElasticsearchMetric 60 | { 61 | Name = Loc["Metrics_IndexName"], 62 | Key = "indices[i]", 63 | Value = codeSearchStatistic.IndexName 64 | }, 65 | new ElasticsearchMetric 66 | { 67 | Name = Loc["Metrics_IndexSize"], 68 | Key = "indices.store.size_in_bytes", 69 | Value = DataSizeUtils.TotalMegabytesString(codeSearchStatistic.IndexSizeInBytes ?? 0) 70 | }, 71 | new ElasticsearchMetric 72 | { 73 | Name = Loc["Metrics_TotalNumberOfDocumentsIndexed"], 74 | Key = "indices.docs.count", 75 | Value = codeSearchStatistic.TotalNumberOfDocumentsIndexed?.ToString() 76 | }, 77 | new ElasticsearchMetric 78 | { 79 | Name = Loc["Metrics_NumberOfDocumentsCurrentlyBeingIndexed"], 80 | Key = "indices.indexing.index_current", 81 | Value = codeSearchStatistic.NumberOfDocumentsCurrentlyBeingIndexed?.ToString() 82 | }, 83 | new ElasticsearchMetric 84 | { 85 | Name = Loc["Metrics_TotalTimeSpentIndexingDocuments"], 86 | Key = "indices.indexing.index_time_in_millis", 87 | Value = TimeFormattingUtils.MillisecondsToSeconds(codeSearchStatistic.TotalTimeSpentIndexingDocumentsInMilliseconds, string.Empty) 88 | }, 89 | new ElasticsearchMetric 90 | { 91 | Name = Loc["Metrics_TotalTimeSpentBulkIndexingDocuments"], 92 | Key = "indices.bulk.total_time_in_millis", 93 | Value = TimeFormattingUtils.MillisecondsToSeconds(codeSearchStatistic.TotalTimeSpentBulkIndexingDocumentsInMilliseconds, string.Empty) 94 | }, 95 | new ElasticsearchMetric 96 | { 97 | Name = Loc["Metrics_TotalNumberOfQueries"], 98 | Key = "indices.search.query_total", 99 | Value = codeSearchStatistic.TotalNumberOfQueries?.ToString() 100 | }, 101 | 102 | new ElasticsearchMetric 103 | { 104 | Name = Loc["Metrics_TotalTimeSpentOnQueries"], 105 | Key = "indices.search.query_time_in_millis", 106 | Value = TimeFormattingUtils.MillisecondsToSeconds(codeSearchStatistic.TotalTimeSpentOnQueriesInMilliseconds, string.Empty) 107 | }, 108 | new ElasticsearchMetric 109 | { 110 | Name = Loc["Metrics_NumberOfQueriesCurrentlyInProgress"], 111 | Key = "indices.search.query_current", 112 | Value = codeSearchStatistic.NumberOfQueriesCurrentlyInProgress?.ToString() 113 | }, 114 | new ElasticsearchMetric 115 | { 116 | Name = Loc["Metrics_TotalNumberOfFetches"], 117 | Key = "indices.search.fetch_total", 118 | Value = codeSearchStatistic.TotalNumberOfFetches?.ToString() 119 | }, 120 | new ElasticsearchMetric 121 | { 122 | Name = Loc["Metrics_TotalTimeSpentOnFetches"], 123 | Key = "indices.search.fetch_time_in_millis", 124 | Value = TimeFormattingUtils.MillisecondsToSeconds(codeSearchStatistic.TotalTimeSpentOnFetchesInMilliseconds, string.Empty) 125 | }, 126 | new ElasticsearchMetric 127 | { 128 | Name = Loc["Metrics_NumberOfFetchesCurrentlyInProgress"], 129 | Key = "indices.search.fetch_current", 130 | Value = codeSearchStatistic.NumberOfFetchesCurrentlyInProgress?.ToString() 131 | }, 132 | }; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/Index.razor.css: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/ManageSearchIndex.razor: -------------------------------------------------------------------------------- 1 | @page "/ManageSearchIndex" 2 | 3 | @using ElasticsearchCodeSearch.Web.Client.Components 4 | @using ElasticsearchCodeSearch.Web.Client.Extensions; 5 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | @using ElasticsearchCodeSearch.Models 7 | @using ElasticsearchCodeSearch.Shared.Dto; 8 | @using ElasticsearchCodeSearch.Shared.Services 9 | 10 | @inject ElasticsearchCodeSearchService ElasticsearchCodeSearchService 11 | @inject IStringLocalizer Loc 12 | 13 | Manage Search Index (Debugging) 14 | 15 | 16 | @Loc["ManageSearchIndex_CreateSearchIndex"] 17 | @Loc["ManageSearchIndex_DeleteSearchIndex"] 18 | @Loc["ManageSearchIndex_RecreateSearchIndex"] 19 | @Loc["ManageSearchIndex_DeleteAllDocuments"] 20 | 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Pages/ManageSearchIndex.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Shared.Services; 4 | using ElasticsearchCodeSearch.Shared.Dto; 5 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 6 | using Microsoft.Extensions.Localization; 7 | 8 | namespace ElasticsearchCodeSearch.Web.Client.Pages 9 | { 10 | public partial class ManageSearchIndex 11 | { 12 | /// 13 | /// Recreates the Search Index. 14 | /// 15 | /// An awaitable 16 | private async Task HandleRecreateSearchIndexAsync() 17 | { 18 | await ElasticsearchCodeSearchService.DeleteSearchIndexAsync(default); 19 | await ElasticsearchCodeSearchService.CreateSearchIndexAsync(default); 20 | } 21 | 22 | /// 23 | /// Creates the Search Index. 24 | /// 25 | /// An awaitable 26 | private async Task HandleCreateSearchIndexAsync() 27 | { 28 | await ElasticsearchCodeSearchService.CreateSearchIndexAsync(default); 29 | } 30 | 31 | /// 32 | /// Deletes the Search Index. 33 | /// 34 | /// An awaitable 35 | private async Task HandleDeleteSearchIndexAsync() 36 | { 37 | await ElasticsearchCodeSearchService.DeleteSearchIndexAsync(default); 38 | } 39 | 40 | /// 41 | /// Deletes all Documents from Index. 42 | /// 43 | /// An awaitable 44 | private async Task HandleDeleteAllDocumentsAsync() 45 | { 46 | await ElasticsearchCodeSearchService.DeleteAllDocumentsAsync(default); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Web.Client; 4 | using ElasticsearchCodeSearch.Web.Client.Infrastructure; 5 | using ElasticsearchCodeSearch.Shared.Services; 6 | using Microsoft.AspNetCore.Components.Web; 7 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 8 | using Microsoft.FluentUI.AspNetCore.Components; 9 | 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | 12 | builder.Services.AddScoped(); 13 | builder.Services.AddScoped(); 14 | 15 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 16 | 17 | builder.Services.AddHttpClient((services, client) => 18 | { 19 | client.BaseAddress = new Uri(builder.Configuration["ElasticsearchCodeSearchApi:BaseAddress"]!); 20 | }); 21 | 22 | builder.Services.AddLocalization(); 23 | 24 | // Fluent UI 25 | builder.Services.AddFluentUIComponents(); 26 | 27 | await builder.Build().RunAsync(); 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54273", 7 | "sslPort": 44381 8 | } 9 | }, 10 | "profiles": { 11 | "BlazorDataGridExample": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "https://localhost:7247;http://localhost:5247", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Routes.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Not found 9 | 10 |

Sorry, there's nothing at this address.

11 |
12 |
13 |
-------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Routes.razor.cs: -------------------------------------------------------------------------------- 1 | namespace ElasticsearchCodeSearch.Web.Client 2 | { 3 | public partial class Routes 4 | { 5 | public const string MESSAGES_NOTIFICATION_CENTER = "NOTIFICATION_CENTER"; 6 | public const string MESSAGES_TOP = "TOP"; 7 | public const string MESSAGES_DIALOG = "DIALOG"; 8 | public const string MESSAGES_CARD = "CARD"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @using ElasticsearchCodeSearch.Web.Client.Components 2 | @using Microsoft.AspNetCore.Components 3 | @using System.Runtime.InteropServices 4 | 5 | @namespace ElasticsearchCodeSearch.Web.Client.Shared 6 | 7 | @inject IStringLocalizer Loc 8 | @inject ApplicationErrorMessageService ApplicationErrorMessageService 9 | 10 | Elasticsearch Code Search 11 |
12 | 13 | 14 | 15 | Elasticsearch Code Search 16 | 17 | 18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 |
@Body
34 |
35 | 36 | @{ 37 | ApplicationErrorMessageService.ShowErrorMessage(exception); 38 | } 39 | 40 |
41 |
42 | 45 | 46 | 47 | 48 |
49 |
50 |
51 | 52 | 53 |
54 | 55 | Version: @_version 56 |  -  57 | Powered by @RuntimeInformation.FrameworkDescription 58 | 59 |
60 | 61 | 62 |
63 | © 2023. All rights reserved. 64 |
65 |
66 | 67 |
68 |
69 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Shared/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Reflection; 4 | using Microsoft.AspNetCore.Components; 5 | using Microsoft.AspNetCore.Components.Routing; 6 | using Microsoft.AspNetCore.Components.Web; 7 | using Microsoft.JSInterop; 8 | 9 | namespace ElasticsearchCodeSearch.Web.Client.Shared 10 | { 11 | public partial class MainLayout 12 | { 13 | private const string JAVASCRIPT_FILE = "./Shared/MainLayout.razor.js"; 14 | private string? _version; 15 | private bool _mobile; 16 | private string? _prevUri; 17 | private bool _menuChecked = true; 18 | 19 | [Inject] 20 | private NavigationManager NavigationManager { get; set; } = default!; 21 | 22 | [Inject] 23 | public IJSRuntime JSRuntime { get; set; } = default!; 24 | 25 | [Parameter] 26 | public RenderFragment? Body { get; set; } 27 | 28 | private ErrorBoundary? _errorBoundary; 29 | 30 | protected override void OnInitialized() 31 | { 32 | _version = Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion; 33 | _prevUri = NavigationManager.Uri; 34 | NavigationManager.LocationChanged += LocationChanged; 35 | } 36 | 37 | protected override void OnParametersSet() 38 | { 39 | _errorBoundary?.Recover(); 40 | } 41 | 42 | protected override async Task OnAfterRenderAsync(bool firstRender) 43 | { 44 | if (firstRender) 45 | { 46 | var jsModule = await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE); 47 | _mobile = await jsModule.InvokeAsync("isDevice"); 48 | await jsModule.DisposeAsync(); 49 | } 50 | } 51 | 52 | private void HandleChecked() 53 | { 54 | _menuChecked = !_menuChecked; 55 | } 56 | 57 | private void LocationChanged(object? sender, LocationChangedEventArgs e) 58 | { 59 | if (!e.IsNavigationIntercepted && new Uri(_prevUri!).AbsolutePath != new Uri(e.Location).AbsolutePath) 60 | { 61 | _prevUri = e.Location; 62 | if (_mobile && _menuChecked == true) 63 | { 64 | _menuChecked = false; 65 | StateHasChanged(); 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Shared/MainLayout.razor.js: -------------------------------------------------------------------------------- 1 | export function isDevice() { 2 | return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(navigator.userAgent); 3 | } 4 | 5 | export function isDarkMode() { 6 | let matched = window.matchMedia("(prefers-color-scheme: dark)").matches; 7 | 8 | if (matched) 9 | return true; 10 | else 11 | return false; 12 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  23 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using Microsoft.FluentUI.AspNetCore.Components 10 | @using Microsoft.Extensions.Localization 11 | @using ElasticsearchCodeSearch.Web.Client 12 | @using ElasticsearchCodeSearch.Web.Client.Shared 13 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure 14 | @using ElasticsearchCodeSearch.Web.Client.Localization 15 | @using static Microsoft.AspNetCore.Components.Web.RenderMode -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Client/wwwroot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ElasticsearchCodeSearchApi": { 3 | "BaseAddress": "https://localhost:5000" 4 | } 5 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | An unhandled error has occurred. 22 | Reload 23 | 🗙 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/ElasticsearchCodeSearch.Web.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Web.Server; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | // Add services to the container. 8 | builder.Services.AddRazorPages(); 9 | 10 | builder.Services.AddRazorComponents() 11 | .AddInteractiveWebAssemblyComponents(); 12 | 13 | var app = builder.Build(); 14 | 15 | // Configure the HTTP request pipeline. 16 | if (app.Environment.IsDevelopment()) 17 | { 18 | app.UseWebAssemblyDebugging(); 19 | } 20 | else 21 | { 22 | app.UseHsts(); 23 | 24 | } 25 | 26 | app.UseHttpsRedirection(); 27 | app.UseStaticFiles(); 28 | 29 | app.MapRazorComponents() 30 | .AddInteractiveWebAssemblyRenderMode() 31 | .AddAdditionalAssemblies(typeof(ElasticsearchCodeSearch.Web.Client._Imports).Assembly); 32 | 33 | app.UseRouting(); 34 | app.UseAntiforgery(); 35 | 36 | app.Run(); 37 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54273", 7 | "sslPort": 44381 8 | } 9 | }, 10 | "profiles": { 11 | "https": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "https://localhost:7247;http://localhost:5247", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using Microsoft.FluentUI.AspNetCore.Components 10 | @using Microsoft.Extensions.Localization 11 | @using ElasticsearchCodeSearch.Web.Server 12 | @using ElasticsearchCodeSearch.Web.Client 13 | @using ElasticsearchCodeSearch.Web.Client.Shared 14 | @using ElasticsearchCodeSearch.Web.Client.Infrastructure 15 | @using ElasticsearchCodeSearch.Web.Client.Localization 16 | @using static Microsoft.AspNetCore.Components.Web.RenderMode -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/src/ElasticsearchCodeSearch.Web.Server/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytefish/ElasticsearchCodeSearch/00ad75fb1894086113f4a0c6250aada825d1490f/src/ElasticsearchCodeSearch.Web.Server/wwwroot/icon-192.png -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/wwwroot/js/theme.js: -------------------------------------------------------------------------------- 1 | import { 2 | baseLayerLuminance, 3 | StandardLuminance 4 | } from "/_content/Microsoft.FluentUI.AspNetCore.Components/js/web-components-v2.5.16.min.js"; 5 | 6 | const currentThemeCookieName = "currentTheme"; 7 | const themeSettingSystem = "System"; 8 | const themeSettingDark = "Dark"; 9 | const themeSettingLight = "Light"; 10 | 11 | /** 12 | * Returns the current system theme (Light or Dark) 13 | * @returns {string} 14 | */ 15 | export function getSystemTheme() { 16 | let matched = window.matchMedia('(prefers-color-scheme: dark)').matches; 17 | 18 | if (matched) { 19 | return themeSettingDark; 20 | } else { 21 | return themeSettingLight; 22 | } 23 | } 24 | 25 | /** 26 | * Sets the currentTheme cookie to the specified value. 27 | * @param {string} theme 28 | */ 29 | export function setThemeCookie(theme) { 30 | document.cookie = `${currentThemeCookieName}=${theme}`; 31 | } 32 | 33 | /** 34 | * Returns the value of the currentTheme cookie, or System if the cookie is not set. 35 | * @returns {string} 36 | */ 37 | export function getThemeCookieValue() { 38 | return getCookieValue(currentThemeCookieName) ?? themeSettingSystem; 39 | } 40 | 41 | export function switchHighlightStyle(dark) { 42 | if (dark) { 43 | document.querySelector(`link[title="dark"]`)?.removeAttribute("disabled"); 44 | document.querySelector(`link[title="light"]`)?.setAttribute("disabled", "disabled"); 45 | } 46 | else { 47 | document.querySelector(`link[title="light"]`)?.removeAttribute("disabled"); 48 | document.querySelector(`link[title="dark"]`)?.setAttribute("disabled", "disabled"); 49 | } 50 | } 51 | 52 | /** 53 | * Returns the value of the specified cookie, or the empty string if the cookie is not present 54 | * @param {string} cookieName 55 | * @returns {string} 56 | */ 57 | function getCookieValue(cookieName) { 58 | const cookiePieces = document.cookie.split(';'); 59 | for (let index = 0; index < cookiePieces.length; index++) { 60 | if (cookiePieces[index].trim().startsWith(cookieName)) { 61 | const cookieKeyValue = cookiePieces[index].split('='); 62 | if (cookieKeyValue.length > 1) { 63 | return cookieKeyValue[1]; 64 | } 65 | } 66 | } 67 | 68 | return ""; 69 | } 70 | 71 | function setInitialBaseLayerLuminance() { 72 | let theme = getThemeCookieValue(); 73 | 74 | if (!theme || theme === themeSettingSystem) { 75 | theme = getSystemTheme(); 76 | } 77 | 78 | if (theme === themeSettingDark) { 79 | baseLayerLuminance.withDefault(StandardLuminance.DarkMode); 80 | switchHighlightStyle(true); 81 | } else /* Light */ { 82 | baseLayerLuminance.withDefault(StandardLuminance.LightMode); 83 | switchHighlightStyle(false); 84 | } 85 | } 86 | 87 | setInitialBaseLayerLuminance(); -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch.Web.Server/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2018-05-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2018-05-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2018-05-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2018-05-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2018-05-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Controllers/CodeSearchController.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Converters; 4 | using ElasticsearchCodeSearch.Shared.Logging; 5 | using Microsoft.AspNetCore.Mvc; 6 | using ElasticsearchCodeSearch.Shared.Elasticsearch; 7 | using ElasticsearchCodeSearch.Shared.Dto; 8 | 9 | namespace ElasticsearchCodeSearch.Controllers 10 | { 11 | [ApiController] 12 | public class CodeSearchController : ControllerBase 13 | { 14 | private readonly ILogger _logger; 15 | private readonly ElasticCodeSearchClient _elasticsearchClient; 16 | 17 | public CodeSearchController(ILogger logger, ElasticCodeSearchClient elasticsearchClient) 18 | { 19 | _elasticsearchClient = elasticsearchClient; 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet] 24 | [Route("/search-statistics")] 25 | public async Task GetSearchStatistics(CancellationToken cancellationToken) 26 | { 27 | _logger.TraceMethodEntry(); 28 | 29 | try 30 | { 31 | var indicesStatResponse = await _elasticsearchClient.GetSearchStatistics(cancellationToken); 32 | 33 | if (!indicesStatResponse.IsValidResponse) 34 | { 35 | if (_logger.IsErrorEnabled()) 36 | { 37 | indicesStatResponse.TryGetOriginalException(out var originalException); 38 | 39 | _logger.LogError(originalException, "Elasticsearch failed with an unhandeled Exception"); 40 | } 41 | 42 | return BadRequest("Invalid Search Response from Elasticsearch"); 43 | } 44 | 45 | var result = CodeSearchStatisticsConverter.Convert(indicesStatResponse); 46 | 47 | return Ok(result); 48 | } 49 | catch (Exception e) 50 | { 51 | if (_logger.IsErrorEnabled()) 52 | { 53 | _logger.LogError(e, "An unhandeled exception occured"); 54 | } 55 | 56 | return StatusCode(500, "An internal Server Error occured"); 57 | } 58 | } 59 | 60 | [HttpPost] 61 | [Route("/search-documents")] 62 | public async Task SearchDocuments([FromBody] CodeSearchRequestDto request, CancellationToken cancellationToken) 63 | { 64 | _logger.TraceMethodEntry(); 65 | 66 | try 67 | { 68 | var searchRequest = CodeSearchRequestConverter.Convert(request); 69 | 70 | var searchResponse = await _elasticsearchClient.SearchAsync(searchRequest, cancellationToken); 71 | 72 | if (!searchResponse.IsValidResponse) 73 | { 74 | if (_logger.IsErrorEnabled()) 75 | { 76 | searchResponse.TryGetOriginalException(out var originalException); 77 | 78 | _logger.LogError(originalException, "Elasticsearch failed with an unhandeled Exception"); 79 | } 80 | 81 | return BadRequest("Invalid Search Response from Elasticsearch"); 82 | } 83 | 84 | var codeSearchResults = new CodeSearchResultsDto 85 | { 86 | Query = request.Query, 87 | From = request.From, 88 | Size = request.Size, 89 | Sort = request.Sort, 90 | Total = searchResponse.Total, 91 | TookInMilliseconds = searchResponse.Took, 92 | Results = CodeSearchResultConverter.Convert(searchResponse) 93 | }; 94 | 95 | return Ok(codeSearchResults); 96 | } 97 | catch (Exception e) 98 | { 99 | if (_logger.IsErrorEnabled()) 100 | { 101 | _logger.LogError(e, "An unhandeled exception occured"); 102 | } 103 | 104 | return StatusCode(500, "An internal Server Error occured"); 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/ElasticsearchCodeSearch.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | f59920c1-b8bc-4e86-854b-44f7da4557b4 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | true 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Git/Exceptions/GitException.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Git.Exceptions 4 | { 5 | public class GitException : Exception 6 | { 7 | public readonly int ExitCode; 8 | public readonly string Errors; 9 | 10 | public GitException(int exitCode, string errors) 11 | { 12 | ExitCode = exitCode; 13 | Errors = errors; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/GitHub/Dto/PaginatedResultsDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Indexer.GitHub.Dto 4 | { 5 | public class PaginatedResultsDto 6 | { 7 | /// 8 | /// Gets or sets the entities fetched. 9 | /// 10 | public required List? Values { get; set; } 11 | 12 | /// 13 | /// Gets or sets the Page Number. 14 | /// 15 | public required int PageNumber { get; set; } 16 | 17 | /// 18 | /// Gets or sets the Page Size. 19 | /// 20 | public required int PageSize { get; set; } 21 | 22 | /// 23 | /// Gets or sets the link to the first page. 24 | /// 25 | public string? FirstPage { get; set; } 26 | 27 | /// 28 | /// Gets or sets the link to the previous page. 29 | /// 30 | public string? PreviousPage { get; set; } 31 | 32 | /// 33 | /// Gets or sets the link to the next page. 34 | /// 35 | public string? NextPage { get; set; } 36 | 37 | /// 38 | /// Gets or sets the link to the last page. 39 | /// 40 | public string? LastPage { get; set; } 41 | } 42 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/GitHub/Dto/RepositoryMetadataDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Indexer.GitHub.Dto 6 | { 7 | public class RepositoryMetadataDto 8 | { 9 | [JsonPropertyName("id")] 10 | public required int Id { get; set; } 11 | 12 | [JsonPropertyName("node_id")] 13 | public required string NodeId { get; set; } 14 | 15 | [JsonPropertyName("name")] 16 | public required string Name { get; set; } 17 | 18 | [JsonPropertyName("full_name")] 19 | public required string FullName { get; set; } 20 | 21 | [JsonPropertyName("default_branch")] 22 | public required string DefaultBranch { get; set; } 23 | 24 | [JsonPropertyName("owner")] 25 | public required RepositoryOwnerDto Owner { get; set; } 26 | 27 | [JsonPropertyName("url")] 28 | public string? Url { get; set; } 29 | 30 | [JsonPropertyName("git_url")] 31 | public string? GitUrl { get; set; } 32 | 33 | [JsonPropertyName("clone_url")] 34 | public string? CloneUrl { get; set; } 35 | 36 | [JsonPropertyName("sshUrl")] 37 | public string? SshUrl { get; set; } 38 | 39 | [JsonPropertyName("updated_at")] 40 | public required DateTime UpdatedAt { get; set; } 41 | 42 | [JsonPropertyName("created_at")] 43 | public required DateTime CreatedAt { get; set; } 44 | 45 | [JsonPropertyName("pushed_at")] 46 | public required DateTime PushedAt { get; set; } 47 | 48 | [JsonPropertyName("size")] 49 | public required int Size { get; set; } 50 | 51 | [JsonPropertyName("language")] 52 | public string? Language { get; set; } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/GitHub/Dto/RepositoryOwnerDto.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace ElasticsearchCodeSearch.Indexer.GitHub.Dto 6 | { 7 | public class RepositoryOwnerDto 8 | { 9 | [JsonPropertyName("login")] 10 | public required string Login { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/GitHub/Exceptions/GitHubApiException.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Indexer.GitHub.Exceptions 4 | { 5 | public class GitHubApiException : Exception 6 | { 7 | public GitHubApiException() 8 | { 9 | } 10 | 11 | public GitHubApiException(string? message) : base(message) 12 | { 13 | } 14 | 15 | public GitHubApiException(string? message, Exception? innerException) : base(message, innerException) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/GitHub/Options/GitHubClientOptions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Indexer.GitHub.Options 4 | { 5 | /// 6 | /// GitHub Client Options. 7 | /// 8 | public class GitHubClientOptions 9 | { 10 | /// 11 | /// The Fine-Grained Access Token. 12 | /// 13 | public string AccessToken { get; set; } = string.Empty; 14 | 15 | /// 16 | /// Time to delay multiple requests. 17 | /// 18 | public int RequestDelayInMilliseconds { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Hosting/ElasticsearchIndexerBackgroundService.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using Elastic.Clients.Elasticsearch; 4 | using ElasticsearchCodeSearch.Infrastructure; 5 | using ElasticsearchCodeSearch.Models; 6 | using ElasticsearchCodeSearch.Services; 7 | using ElasticsearchCodeSearch.Shared.Logging; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace ElasticsearchCodeSearch.Hosting 11 | { 12 | /// 13 | /// A very simple Background Service to Process Indexing Requests in the Background. It basically 14 | /// contains two concurrent queues to queue the repositories or organization to be indexed. This 15 | /// should be replaced by a proper framework, such as Quartz.NET probably? 16 | /// 17 | public class ElasticsearchIndexerBackgroundService : BackgroundService 18 | { 19 | private readonly ILogger _logger; 20 | 21 | /// 22 | /// Indexer for GitHub Repositories. 23 | /// 24 | private readonly GitIndexerService _gitIndexerService; 25 | 26 | /// 27 | /// Options for Git Repository indexing. 28 | /// 29 | private readonly GitIndexerOptions _gitIndexerOptions; 30 | 31 | /// 32 | /// Indexer Job Queues to process. 33 | /// 34 | private readonly GitRepositoryJobQueue _jobQueue; 35 | 36 | /// 37 | /// Creates a new Elasticsearch Indexer Background Service. 38 | /// 39 | /// Logger 40 | /// GitHub Indexer Service 41 | public ElasticsearchIndexerBackgroundService(ILogger logger, GitRepositoryJobQueue jobQueue, GitIndexerService gitIndexerService, IOptions gitIndexerOptions) 42 | { 43 | _logger = logger; 44 | _gitIndexerService = gitIndexerService; 45 | _jobQueue = jobQueue; 46 | _gitIndexerOptions = gitIndexerOptions.Value; 47 | } 48 | 49 | protected override async Task ExecuteAsync(CancellationToken cancellationToken) 50 | { 51 | _logger.TraceMethodEntry(); 52 | 53 | try 54 | { 55 | await _gitIndexerService.CreateSearchIndexAsync(cancellationToken); 56 | } 57 | catch (Exception e) 58 | { 59 | _logger.LogError(e, "Failed to create Search Index"); 60 | } 61 | 62 | while (!cancellationToken.IsCancellationRequested) 63 | { 64 | try 65 | { 66 | ParallelOptions parallelOptions = new() 67 | { 68 | MaxDegreeOfParallelism = _gitIndexerOptions.MaxParallelClones, 69 | CancellationToken = cancellationToken, 70 | }; 71 | 72 | await Parallel.ForEachAsync( 73 | source: _jobQueue.ToAsyncEnumerable(cancellationToken), 74 | parallelOptions: parallelOptions, 75 | body: (x, ct) => ProcessRepositoryAsync(x, ct)); 76 | } 77 | catch(Exception e) 78 | { 79 | _logger.LogError(e, "Indexing stopped due an uncaught Exception. Restarting the Indexing Loop."); 80 | } 81 | } 82 | } 83 | 84 | public override Task StopAsync(CancellationToken cancellationToken) 85 | { 86 | _logger.TraceMethodEntry(); 87 | 88 | return Task.CompletedTask; 89 | } 90 | 91 | private async ValueTask ProcessRepositoryAsync(GitRepositoryMetadata repository, CancellationToken cancellationToken) 92 | { 93 | _logger.TraceMethodEntry(); 94 | 95 | try 96 | { 97 | await _gitIndexerService.IndexRepositoryAsync(repository, cancellationToken); 98 | } 99 | catch (Exception e) 100 | { 101 | _logger.LogError(e, "Failed to index Organization '{Repository}'", repository.FullName); 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Infrastructure/GitRepositoryJobQueue.cs: -------------------------------------------------------------------------------- 1 | using ElasticsearchCodeSearch.Models; 2 | using System.Threading.Channels; 3 | 4 | namespace ElasticsearchCodeSearch.Infrastructure 5 | { 6 | /// 7 | /// Simple In-Memory Job Queues to be processed by the Indexer. 8 | /// 9 | public class GitRepositoryJobQueue 10 | { 11 | public readonly Channel Channel = System.Threading.Channels.Channel.CreateUnbounded(); 12 | 13 | public bool Post(GitRepositoryMetadata repository) 14 | { 15 | return Channel.Writer.TryWrite(repository); 16 | } 17 | 18 | public IAsyncEnumerable ToAsyncEnumerable(CancellationToken cancellationToken) 19 | { 20 | return Channel.Reader.ReadAllAsync(cancellationToken); 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Infrastructure/PermalinkGenerator.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Models; 4 | using ElasticsearchCodeSearch.Shared.Constants; 5 | 6 | namespace ElasticsearchCodeSearch.Infrastructure 7 | { 8 | public class PermalinkGenerator 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public PermalinkGenerator(ILogger logger) 13 | { 14 | _logger = logger; 15 | } 16 | 17 | public virtual string GeneratePermalink(GitRepositoryMetadata repository, string commitHash, string relativeFilename) 18 | { 19 | switch (repository.Source) 20 | { 21 | case SourceSystems.GitHub: 22 | return $"https://github.com/{repository.Owner}/{repository.Name}/blob/{commitHash}/{relativeFilename}"; 23 | case SourceSystems.Codeberg: 24 | return $"https://codeberg.org/{repository.Owner}/{repository.Name}/src/commit/{commitHash}/{relativeFilename}"; 25 | case SourceSystems.GitLab: 26 | return $"https://gitlab.com/{repository.Owner}/{repository.Name}/-/blob/{commitHash}/{relativeFilename}"; // TODO Is this really the Commit Hash? 27 | default: 28 | return "Unknown Source System"; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Models/GitRepositoryMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace ElasticsearchCodeSearch.Models 2 | { 3 | /// 4 | /// Git Repository Metadata. 5 | /// 6 | public record GitRepositoryMetadata 7 | { 8 | /// 9 | /// Gets or sets the owner of the Repository, for example a Username or Organization. 10 | /// 11 | public required string Owner { get; set; } 12 | 13 | /// 14 | /// Gets or sets the Name of the Repository. 15 | /// 16 | public required string Name { get; set; } 17 | 18 | /// 19 | /// Gets the FullName given by Owner and Repository Name. 20 | /// 21 | public string FullName => $"{Owner}/{Name}"; 22 | 23 | /// 24 | /// Gets or sets the Branch to index. 25 | /// 26 | public required string Branch { get; set; } 27 | 28 | /// 29 | /// Gets or sets the Repositories languages, if any. 30 | /// 31 | public string Language { get; set; } = string.Empty; 32 | 33 | /// 34 | /// Gets or sets the Url to clone from. 35 | /// 36 | public required string CloneUrl { get; set; } 37 | 38 | /// 39 | /// Gets or sets the Source System. 40 | /// 41 | public required string Source { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Hosting; 4 | using System.Text.Json.Serialization; 5 | using ElasticsearchCodeSearch.Shared.Elasticsearch; 6 | using ElasticsearchCodeSearch.Indexer.GitHub.Options; 7 | using ElasticsearchCodeSearch.Indexer.GitHub; 8 | using ElasticsearchCodeSearch.Services; 9 | using ElasticsearchCodeSearch.Infrastructure; 10 | using ElasticsearchCodeSearch.Git; 11 | 12 | var builder = WebApplication.CreateBuilder(args); 13 | 14 | // Add Configuration Sources 15 | builder.Configuration 16 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 17 | .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true) 18 | .AddEnvironmentVariables() 19 | .AddUserSecrets(); 20 | 21 | 22 | // Add Options 23 | builder.Services.AddOptions(); 24 | 25 | 26 | 27 | ConfigureElasticsearch(builder); 28 | ConfigureIndexingServices(builder); 29 | 30 | // Add CORS Services 31 | builder.Services.AddCors(); 32 | 33 | builder.Services.AddControllers().AddJsonOptions(options => 34 | { 35 | options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); 36 | }); 37 | 38 | builder.Services.AddSwaggerGen(); 39 | 40 | var app = builder.Build(); 41 | 42 | // Configure the HTTP request pipeline. 43 | app.UseHttpsRedirection(); 44 | 45 | app.UseSwagger(); 46 | app.UseSwaggerUI(); 47 | 48 | app.UseCors(x => x 49 | .AllowAnyOrigin() 50 | .AllowAnyHeader() 51 | .AllowAnyMethod()); 52 | 53 | app.MapControllers(); 54 | 55 | app.Run(); 56 | 57 | static void ConfigureElasticsearch(WebApplicationBuilder builder) 58 | { 59 | builder.Services.Configure(builder.Configuration.GetSection("Elasticsearch")); 60 | 61 | // Add Client 62 | builder.Services.AddSingleton(); 63 | } 64 | 65 | static void ConfigureIndexingServices(WebApplicationBuilder builder) 66 | { 67 | // Create the GitClientOptions by using the GH_TOKEN Key: 68 | builder.Services.Configure(builder.Configuration.GetSection("GitHubClient")); 69 | builder.Services.Configure(builder.Configuration.GetSection("GitHubIndexer")); 70 | 71 | builder.Services.AddSingleton(); 72 | builder.Services.AddSingleton(); 73 | 74 | builder.Services.AddSingleton(); 75 | builder.Services.AddSingleton(); 76 | builder.Services.AddSingleton(); 77 | builder.Services.AddSingleton(); 78 | 79 | // Background Service Processing Queued Jobs 80 | builder.Services.AddHostedService(); 81 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ElasticsearchFulltextExample.Web": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "https://localhost:5000/swagger/index.html", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "applicationUrl": "https://localhost:5000" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Services/GitHubService.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | using ElasticsearchCodeSearch.Git; 4 | using ElasticsearchCodeSearch.Indexer.GitHub; 5 | using ElasticsearchCodeSearch.Indexer.GitHub.Dto; 6 | using ElasticsearchCodeSearch.Models; 7 | using ElasticsearchCodeSearch.Shared.Constants; 8 | using ElasticsearchCodeSearch.Shared.Elasticsearch; 9 | using ElasticsearchCodeSearch.Shared.Logging; 10 | 11 | namespace ElasticsearchCodeSearch.Services 12 | { 13 | /// 14 | /// Reads the from GitHub. 15 | /// 16 | public class GitHubService 17 | { 18 | private readonly ILogger _logger; 19 | 20 | private readonly GitHubClient _gitHubClient; 21 | 22 | public GitHubService(ILogger logger, GitExecutor gitExecutor, GitHubClient gitHubClient, ElasticCodeSearchClient elasticCodeSearchClient) 23 | { 24 | _logger = logger; 25 | _gitHubClient = gitHubClient; 26 | } 27 | 28 | /// 29 | /// Gets all Repositories for a given Organization. 30 | /// 31 | /// Organization Name, for example "dotnet" 32 | /// Cancellation Token to cancel asynchronous operations 33 | /// List of Git Repositories 34 | public async Task> GetAllRepositoriesByOrganizationAsync(string organization, CancellationToken cancellationToken) 35 | { 36 | _logger.TraceMethodEntry(); 37 | 38 | // Gets all Repositories of the Organization: 39 | var repositories = await _gitHubClient 40 | .GetAllRepositoriesByOrganizationAsync(organization, 20, cancellationToken) 41 | .ConfigureAwait(false); 42 | 43 | return repositories 44 | .Select(repository => Convert(repository)) 45 | .ToList(); 46 | } 47 | 48 | /// 49 | /// Gets the from a given GitHub Repository. 50 | /// 51 | /// Owner of the Repository, which is either an Organization or User 52 | /// Repository Name 53 | /// Cancellation Token to cancel asynchronous operations 54 | /// 55 | /// Thrown, when the Metadata couldn't be read 56 | public async Task GetRepositoryByOwnerAndNameAsync(string owner, string repository, CancellationToken cancellationToken) 57 | { 58 | _logger.TraceMethodEntry(); 59 | 60 | var repositoryMetadata = await _gitHubClient 61 | .GetRepositoryByOwnerAndRepositoryAsync(owner, repository, cancellationToken) 62 | .ConfigureAwait(false); 63 | 64 | if (repositoryMetadata == null) 65 | { 66 | throw new Exception($"Unable to read repository metadata for Owner '{owner}' and Repository '{repository}'"); 67 | } 68 | 69 | var result = Convert(repositoryMetadata); 70 | 71 | return result; 72 | } 73 | 74 | private GitRepositoryMetadata Convert(RepositoryMetadataDto source) 75 | { 76 | _logger.TraceMethodEntry(); 77 | 78 | var result = new GitRepositoryMetadata 79 | { 80 | Owner = source.Owner.Login, 81 | Name = source.Name, 82 | Branch = source.DefaultBranch, 83 | CloneUrl = source.CloneUrl!, 84 | Language = source.Language!, 85 | Source = SourceSystems.GitHub, 86 | }; 87 | 88 | return result; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/Services/GitIndexerOptions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | 3 | namespace ElasticsearchCodeSearch.Services 4 | { 5 | /// 6 | /// AppSettings for the Indexer. 7 | /// 8 | public class GitIndexerOptions 9 | { 10 | /// 11 | /// Gets or sets the base directory to clone to. 12 | /// 13 | public required string BaseDirectory { get; set; } 14 | 15 | /// 16 | /// Gets or sets the allowed extensions. 17 | /// 18 | public required string[] AllowedExtensions { get; set; } 19 | 20 | /// 21 | /// Gets or sets the allowed filenames. 22 | /// 23 | public required string[] AllowedFilenames { get; set; } 24 | 25 | /// 26 | /// Gets or sets the allowed filenames. 27 | /// 28 | public string[] FilterLanguages { get; set; } = []; 29 | 30 | /// 31 | /// Batch Size. 32 | /// 33 | public int BatchSize { get; set; } 34 | 35 | /// 36 | /// Gets or sets the degree of parallelism for clones. 37 | /// 38 | public int MaxParallelClones { get; set; } 39 | 40 | /// 41 | /// Gets or sets the degree of parallelism for bulk requests. 42 | /// 43 | public int MaxParallelBulkRequests { get; set; } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Elasticsearch": { 3 | "Uri": "https://es01:9200", 4 | "IndexName": "documents", 5 | "Username": "elastic", 6 | "Password": "secret", 7 | "CertificateFingerprint": "31a63ffca5275df7ea7d6fc7e92b42cfa774a0feed7d7fa8488c5e46ea9ade3f" 8 | }, 9 | "GitHubClient": { 10 | "RequestDelayInMilliseconds": 0, 11 | "AccessToken": "" 12 | }, 13 | "GitHubIndexer": { 14 | "BaseDirectory": "/codesearch-data", 15 | "MaxParallelClones": 1, 16 | "MaxParallelBulkRequests": 1, 17 | "BatchSize": 20, 18 | "FilterLangauges": [ 19 | "C#", 20 | "Java", 21 | "Python", 22 | "SQL", 23 | "C++" 24 | ], 25 | "AllowedFilenames": [ 26 | ".gitignore", 27 | ".editorconfig", 28 | "README", 29 | "CHANGELOG" 30 | ], 31 | "AllowedExtensions": [ 32 | ".c", 33 | ".cpp", 34 | ".h", 35 | ".hpp", 36 | ".cs", 37 | ".cshtml", 38 | ".csproj", 39 | ".fs", 40 | ".razor", 41 | ".sln", 42 | ".xaml", 43 | ".css", 44 | ".scss", 45 | ".sass", 46 | ".csv", 47 | ".tsv", 48 | ".html", 49 | ".htm", 50 | ".json", 51 | ".js", 52 | ".jsx", 53 | ".spec.js", 54 | ".config.js", 55 | ".ts", 56 | ".tsx", 57 | ".txt", 58 | ".ps1", 59 | ".py", 60 | ".ini", 61 | ".config", 62 | ".xml", 63 | ".xsl", 64 | ".xsd", 65 | ".dtd", 66 | ".wsdl", 67 | ".md", 68 | ".markdown", 69 | ".rst", 70 | ".tex", 71 | ".bib", 72 | ".bbx", 73 | ".cbx" 74 | ] 75 | }, 76 | "Logging": { 77 | "LogLevel": { 78 | "Default": "Debug", 79 | "Microsoft": "Debug", 80 | "Microsoft.Hosting.Lifetime": "Debug" 81 | } 82 | }, 83 | "AllowedHosts": "*" 84 | } 85 | -------------------------------------------------------------------------------- /src/ElasticsearchCodeSearch/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Elasticsearch": { 3 | "Uri": "https://localhost:9200", 4 | "IndexName": "documents", 5 | "Username": "elastic", 6 | "Password": "secret", 7 | "CertificateFingerprint": "31a63ffca5275df7ea7d6fc7e92b42cfa774a0feed7d7fa8488c5e46ea9ade3f" 8 | }, 9 | "GitHubClient": { 10 | "RequestDelayInMilliseconds": 0, 11 | "AccessToken": "" 12 | }, 13 | "GitHubIndexer": { 14 | "BaseDirectory": "C:\\Temp", 15 | "MaxParallelClones": 1, 16 | "MaxParallelBulkRequests": 1, 17 | "BatchSize": 20, 18 | "AllowedFilenames": [ 19 | ".gitignore", 20 | ".editorconfig", 21 | "README", 22 | "CHANGELOG" 23 | ], 24 | "FilterLangauges": [ 25 | "C#", 26 | "Java", 27 | "Python", 28 | "SQL", 29 | "C++" 30 | ], 31 | "AllowedExtensions": [ 32 | ".c", 33 | ".cpp", 34 | ".h", 35 | ".hpp", 36 | ".cs", 37 | ".cshtml", 38 | ".csproj", 39 | ".fs", 40 | ".razor", 41 | ".sln", 42 | ".xaml", 43 | ".css", 44 | ".scss", 45 | ".sass", 46 | ".csv", 47 | ".tsv", 48 | ".html", 49 | ".htm", 50 | ".json", 51 | ".js", 52 | ".jsx", 53 | ".spec.js", 54 | ".config.js", 55 | ".ts", 56 | ".tsx", 57 | ".txt", 58 | ".ps1", 59 | ".py", 60 | ".ini", 61 | ".config", 62 | ".xml", 63 | ".xsl", 64 | ".xsd", 65 | ".dtd", 66 | ".wsdl", 67 | ".md", 68 | ".markdown", 69 | ".rst", 70 | ".tex", 71 | ".bib", 72 | ".bbx", 73 | ".cbx" 74 | ] 75 | }, 76 | "Logging": { 77 | "LogLevel": { 78 | "Default": "Debug", 79 | "Microsoft": "Debug", 80 | "Microsoft.Hosting.Lifetime": "Debug" 81 | } 82 | }, 83 | "AllowedHosts": "*" 84 | } --------------------------------------------------------------------------------