From e9716176e110182a0e88a2adff233b5254322a9f Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Thu, 8 Jan 2026 16:42:50 -0600 Subject: [PATCH 1/9] Tweaking names, updating YML --- ...roduction.yml => wigg-app-prod-course.yml} | 14 ++--- .github/workflows/wigg-app-staging-course.yml | 57 +++++++++++++++++++ ...roduction.yml => wigg-fun-prod-course.yml} | 14 ++--- .github/workflows/wigg-fun-staging-course.yml | 43 ++++++++++++++ ProgramInformationV2.Function/Program.cs | 4 +- ProgramInformationV2.sln | 12 ++-- ProgramInformationV2/Program.cs | 4 +- 7 files changed, 121 insertions(+), 27 deletions(-) rename .github/workflows/{app_production.yml => wigg-app-prod-course.yml} (75%) create mode 100644 .github/workflows/wigg-app-staging-course.yml rename .github/workflows/{fun_production.yml => wigg-fun-prod-course.yml} (77%) create mode 100644 .github/workflows/wigg-fun-staging-course.yml diff --git a/.github/workflows/app_production.yml b/.github/workflows/wigg-app-prod-course.yml similarity index 75% rename from .github/workflows/app_production.yml rename to .github/workflows/wigg-app-prod-course.yml index aba85f8..6d3ad3b 100644 --- a/.github/workflows/app_production.yml +++ b/.github/workflows/wigg-app-prod-course.yml @@ -9,9 +9,6 @@ on: - main workflow_dispatch: -env: - DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use - jobs: build: runs-on: windows-latest @@ -19,17 +16,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment + - name: Set up .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + dotnet-version: '8.x' include-prerelease: true - name: Build with dotnet run: dotnet build --configuration Release - name: dotnet publish - run: dotnet publish ProgramInformationV2 -c Release -r linux-x64 --property:PublishDir=${{env.DOTNET_ROOT}}/myapp + run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp - name: Upload artifact for deployment job uses: actions/upload-artifact@v4 @@ -54,7 +51,6 @@ jobs: id: deploy-to-webapp uses: azure/webapps-deploy@v2 with: - app-name: itp-app-programrepository - package: . + app-name: 'wigg-course-infra-winapp01' publish-profile: ${{ secrets.AZURE_WEB_PUBLISH_PROFILE_PRODUCTION }} - slot-name: 'deploy' + package: . \ No newline at end of file diff --git a/.github/workflows/wigg-app-staging-course.yml b/.github/workflows/wigg-app-staging-course.yml new file mode 100644 index 0000000..6ac00ba --- /dev/null +++ b/.github/workflows/wigg-app-staging-course.yml @@ -0,0 +1,57 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Staging WIGG Build and deploy ASP.Net Core app to Azure Web App + +on: + push: + branches: + - develop + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.x' + include-prerelease: true + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: windows-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'wigg-course-infra-winapp01' + slot-name: 'staging' + publish-profile: ${{ secrets.AZURE_WEB_PUBLISH_PROFILE_STAGING }} + package: . \ No newline at end of file diff --git a/.github/workflows/fun_production.yml b/.github/workflows/wigg-fun-prod-course.yml similarity index 77% rename from .github/workflows/fun_production.yml rename to .github/workflows/wigg-fun-prod-course.yml index 7ef9d72..68d23ec 100644 --- a/.github/workflows/fun_production.yml +++ b/.github/workflows/wigg-fun-prod-course.yml @@ -10,12 +10,12 @@ on: workflow_dispatch: env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: 'ProgramInformationV2.Function' # set this to the path to your web app project, defaults to the repository root + AZURE_FUNCTIONAPP_PACKAGE_PATH: 'uofi-itp-course-function' # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use - + jobs: build-and-deploy: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - name: 'Checkout GitHub Action' @@ -25,20 +25,18 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - name: 'Resolve Project Dependencies Using Dotnet' shell: pwsh run: | pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' - dotnet build --configuration Release --output ./output -r linux-x64 + dotnet build --configuration Release --output ./output popd - name: 'Run Azure Functions Action' uses: Azure/functions-action@v1 id: fa with: - app-name: 'itp-function-programrepository' + app-name: 'wigg-course-infra-funapp01' package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output' - publish-profile: ${{ secrets.AZURE_FUNCTION_PUBLISH_PROFILE_PRODUCTION }} - slot-name: 'deploy' + publish-profile: ${{ secrets.AZURE_FUNCTION_PUBLISH_PROFILE_PRODUCTION }} \ No newline at end of file diff --git a/.github/workflows/wigg-fun-staging-course.yml b/.github/workflows/wigg-fun-staging-course.yml new file mode 100644 index 0000000..29a6da8 --- /dev/null +++ b/.github/workflows/wigg-fun-staging-course.yml @@ -0,0 +1,43 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Staging WIGG Build and deploy ASP.Net Core app to Azure Function App + +on: + push: + branches: + - develop + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: 'uofi-itp-course-function' # set this to the path to your web app project, defaults to the repository root + DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use + +jobs: + build-and-deploy: + runs-on: windows-latest + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: 'Resolve Project Dependencies Using Dotnet' + shell: pwsh + run: | + pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' + dotnet build --configuration Release --output ./output + popd + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'wigg-course-infra-funapp01' + slot-name: 'staging' + package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output' + publish-profile: ${{ secrets.AZURE_FUNCTION_PUBLISH_PROFILE_STAGING }} \ No newline at end of file diff --git a/ProgramInformationV2.Function/Program.cs b/ProgramInformationV2.Function/Program.cs index a1993eb..43bd5c4 100644 --- a/ProgramInformationV2.Function/Program.cs +++ b/ProgramInformationV2.Function/Program.cs @@ -26,8 +26,8 @@ _ = services.ConfigureFunctionsApplicationInsights(); _ = services.AddDbContextFactory(options => options.UseSqlServer(hostContext.Configuration["AppConnection"]).EnableSensitiveDataLogging(true)); _ = services.AddScoped(); - _ = services.AddSingleton(c => OpenSearchFactory.CreateLowLevelClient(hostContext.Configuration["SearchUrl"], hostContext.Configuration["AccessKey"], hostContext.Configuration["SecretKey"], hostContext.Configuration["Debug"] == "true")); - _ = services.AddSingleton(c => OpenSearchFactory.CreateClient(hostContext.Configuration["SearchUrl"], hostContext.Configuration["AccessKey"], hostContext.Configuration["SecretKey"], true)); + _ = services.AddSingleton(c => OpenSearchFactory.CreateLowLevelClient(hostContext.Configuration["SearchUrl"], hostContext.Configuration["AccessKey"], hostContext.Configuration["SecretKey"], hostContext.Configuration["SearchDebug"] == "true")); + _ = services.AddSingleton(c => OpenSearchFactory.CreateClient(hostContext.Configuration["SearchUrl"], hostContext.Configuration["AccessKey"], hostContext.Configuration["SecretKey"], hostContext.Configuration["SearchDebug"] == "true")); _ = services.AddScoped(); _ = services.AddScoped(); _ = services.AddScoped(); diff --git a/ProgramInformationV2.sln b/ProgramInformationV2.sln index cda617a..8936717 100644 --- a/ProgramInformationV2.sln +++ b/ProgramInformationV2.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34728.123 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProgramInformationV2", "ProgramInformationV2\ProgramInformationV2.csproj", "{B547452F-BE87-4536-80C0-230EA18AD030}" EndProject @@ -13,11 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProgramInformationV2.Functi EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64244983-1B95-4158-AAD6-313765A46B73}" ProjectSection(SolutionItems) = preProject - .github\workflows\app_production.yml = .github\workflows\app_production.yml - .github\workflows\app_staging.yml = .github\workflows\app_staging.yml - .github\workflows\fun_production.yml = .github\workflows\fun_production.yml - .github\workflows\fun_staging.yml = .github\workflows\fun_staging.yml README.md = README.md + .github\workflows\wigg-app-prod-course.yml = .github\workflows\wigg-app-prod-course.yml + .github\workflows\wigg-app-staging-course.yml = .github\workflows\wigg-app-staging-course.yml + .github\workflows\wigg-fun-prod-course.yml = .github\workflows\wigg-fun-prod-course.yml + .github\workflows\wigg-fun-staging-course.yml = .github\workflows\wigg-fun-staging-course.yml EndProjectSection EndProject Global diff --git a/ProgramInformationV2/Program.cs b/ProgramInformationV2/Program.cs index 9d4e238..e0f33c9 100644 --- a/ProgramInformationV2/Program.cs +++ b/ProgramInformationV2/Program.cs @@ -51,8 +51,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(b => OpenSearchFactory.CreateClient(builder.Configuration["SearchUrl"], builder.Configuration["SearchAccessKey"], builder.Configuration["SearchSecretAccessKey"], bool.Parse(builder.Configuration["SearchDebug"] ?? "false"))); -builder.Services.AddSingleton(b => OpenSearchFactory.CreateLowLevelClient(builder.Configuration["SearchUrl"], builder.Configuration["SearchAccessKey"], builder.Configuration["SearchSecretAccessKey"], bool.Parse(builder.Configuration["SearchDebug"] ?? "false"))); +builder.Services.AddSingleton(b => OpenSearchFactory.CreateClient(builder.Configuration["SearchUrl"], builder.Configuration["AccessKey"], builder.Configuration["SecretKey"], bool.Parse(builder.Configuration["SearchDebug"] ?? "false"))); +builder.Services.AddSingleton(b => OpenSearchFactory.CreateLowLevelClient(builder.Configuration["SearchUrl"], builder.Configuration["AccessKey"], builder.Configuration["SecretKey"], bool.Parse(builder.Configuration["SearchDebug"] ?? "false"))); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); From e564f3704bf0f1e4b11bb78ce8b6b3ba8ce579e3 Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Thu, 8 Jan 2026 16:57:53 -0600 Subject: [PATCH 2/9] Fix yml --- .github/workflows/wigg-fun-prod-course.yml | 2 +- .github/workflows/wigg-fun-staging-course.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wigg-fun-prod-course.yml b/.github/workflows/wigg-fun-prod-course.yml index 68d23ec..e8366e3 100644 --- a/.github/workflows/wigg-fun-prod-course.yml +++ b/.github/workflows/wigg-fun-prod-course.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: 'uofi-itp-course-function' # set this to the path to your web app project, defaults to the repository root + AZURE_FUNCTIONAPP_PACKAGE_PATH: 'ProgramInformationV2.Function' # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use jobs: diff --git a/.github/workflows/wigg-fun-staging-course.yml b/.github/workflows/wigg-fun-staging-course.yml index 29a6da8..860c382 100644 --- a/.github/workflows/wigg-fun-staging-course.yml +++ b/.github/workflows/wigg-fun-staging-course.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: 'uofi-itp-course-function' # set this to the path to your web app project, defaults to the repository root + AZURE_FUNCTIONAPP_PACKAGE_PATH: 'ProgramInformationV2.Function' # set this to the path to your web app project, defaults to the repository root DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use jobs: From ddfb709cd7186bbefa3e139f98774f5e31f9e047 Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Mon, 12 Jan 2026 13:52:03 -0600 Subject: [PATCH 3/9] Add ability to pull course information via cisapp/explorer/schedule --- .../CourseImport/ScheduleTranslator.cs | 3 ++ .../CourseImport/XmlImporter.cs | 5 ++- .../GetCampusCourse.cs | 40 +++++++++++++++++++ ProgramInformationV2.sln | 2 +- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 ProgramInformationV2.Function/GetCampusCourse.cs diff --git a/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs b/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs index 6448d61..df90795 100644 --- a/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs +++ b/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs @@ -41,6 +41,9 @@ public static Course Translate(ScheduleCourse scheduleCourse, string source, boo Type = s.Type })]; } + course.FacultyNameList = course.Sections == null ? [] : course.Sections.SelectMany(s => s.FacultyNameList).Distinct().ToList(); + course.Faculty = string.Join(", ", course.FacultyNameList.Select(f => f.Name)); + course.Title = string.IsNullOrWhiteSpace(course.Rubric) ? course.CourseTitle : $"{course.Rubric} {course.CourseNumber}: {course.CourseTitle}"; return course; } diff --git a/ProgramInformationV2.Data/CourseImport/XmlImporter.cs b/ProgramInformationV2.Data/CourseImport/XmlImporter.cs index 01be563..75006c2 100644 --- a/ProgramInformationV2.Data/CourseImport/XmlImporter.cs +++ b/ProgramInformationV2.Data/CourseImport/XmlImporter.cs @@ -9,7 +9,7 @@ public static class XmlImporter { private static readonly int _yearsLookBack = 5; private static readonly int _yearsLookForward = 1; - public static IEnumerable GetAllCoursesBySemester(string rubric, string courseNumber) { + public static IEnumerable GetAllCoursesBySemester(string rubric, string courseNumber, bool shortCircuit = false) { var returnValue = new List(); var urls = new List>(); @@ -26,6 +26,9 @@ public static IEnumerable GetAllCoursesBySemester(string rubric, stri var courseUrl = new CourseUrl { CourseNumber = node.Attributes("id").First().Value, Rubric = rubric.ToUpperInvariant(), Semester = url.Item2, Url = FixUrl(node.Attributes("href").First().Value), Year = url.Item3 }; if (courseUrl != null && courseUrl.CourseNumber == courseNumber) { returnValue.Add(courseUrl); + if (shortCircuit) { + return returnValue; + } } } Console.WriteLine($"Found {url.Item1}"); diff --git a/ProgramInformationV2.Function/GetCampusCourse.cs b/ProgramInformationV2.Function/GetCampusCourse.cs new file mode 100644 index 0000000..a8ab416 --- /dev/null +++ b/ProgramInformationV2.Function/GetCampusCourse.cs @@ -0,0 +1,40 @@ +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using ProgramInformationV2.Data.CourseImport; +using ProgramInformationV2.Function.Helper; +using ProgramInformationV2.Search.Models; + +namespace ProgramInformationV2.Function; + +public class GetCampusCourse(ILogger logger) { + private readonly ILogger _logger = logger; + + [Function("GetCampusCourse")] + [OpenApiOperation(operationId: "GetCampusCourse", tags: "Get Course Information", Description = "Get a course from the campus tools. This is using the same course object, but the source and other fields will be blank.")] + [OpenApiParameter(name: "rubric", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The rubric.")] + [OpenApiParameter(name: "coursenumber", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The course number.")] + [OpenApiParameter(name: "singlesemester", In = ParameterLocation.Query, Required = true, Type = typeof(bool), Description = "If set to true, then only query latest semester -- used if you just need the description. Defaults to false.")] + [OpenApiParameter(name: "courseonly", In = ParameterLocation.Query, Required = true, Type = typeof(bool), Description = "If set to true, then do not include section information. Defaults to false.")] + [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(List), Description = "The list of courses")] + public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) { + var requestHelper = RequestHelperFactory.Create(); + requestHelper.Initialize(req); + var rubric = requestHelper.GetRequest(req, "rubric"); + var coursenumber = requestHelper.GetRequest(req, "coursenumber"); + var singlesemester = requestHelper.GetBoolean(req, "singlesemester"); + var courseonly = requestHelper.GetBoolean(req, "courseonly"); + requestHelper.Validate(); + + var itemGroups = XmlImporter.GetAllCoursesBySemester(rubric, coursenumber, singlesemester); + var scheduledCourse = XmlImporter.GetCourse(itemGroups ?? []); + var course = ScheduleTranslator.Translate(scheduledCourse, "", !courseonly); + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteAsJsonAsync(course); + return response; + } +} \ No newline at end of file diff --git a/ProgramInformationV2.sln b/ProgramInformationV2.sln index 8936717..cf672ff 100644 --- a/ProgramInformationV2.sln +++ b/ProgramInformationV2.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.1.11312.151 d18.0 +VisualStudioVersion = 18.1.11312.151 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProgramInformationV2", "ProgramInformationV2\ProgramInformationV2.csproj", "{B547452F-BE87-4536-80C0-230EA18AD030}" EndProject From 48c6af3b1e93ae38dc9a31a887027f7b0ca89c62 Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Thu, 15 Jan 2026 14:12:12 -0600 Subject: [PATCH 4/9] Add skip/take to programs, fix search criteria --- .../DataHelpers/FilterHelper.cs | 22 +++++++++---------- ProgramInformationV2.Function/GetPrograms.cs | 6 ++++- .../Getters/CredentialGetter.cs | 2 +- .../Getters/ProgramGetter.cs | 9 ++++---- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/ProgramInformationV2.Data/DataHelpers/FilterHelper.cs b/ProgramInformationV2.Data/DataHelpers/FilterHelper.cs index 57f312c..d572622 100644 --- a/ProgramInformationV2.Data/DataHelpers/FilterHelper.cs +++ b/ProgramInformationV2.Data/DataHelpers/FilterHelper.cs @@ -1,9 +1,9 @@ -using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; using ProgramInformationV2.Data.DataContext; using ProgramInformationV2.Data.DataModels; using ProgramInformationV2.Search.Helpers; using ProgramInformationV2.Search.JsonThinModels; -using System.Text.Json; namespace ProgramInformationV2.Data.DataHelpers { @@ -16,20 +16,20 @@ public async Task>> GetAllFilters(stri public async Task> GetFilterListForExport(string source) => [.. (await GetAllFilters(source)).Select(t => new TagList { Title = t.Key.ToString(), - List = [.. t.Select(l => l.Title)], + List = [.. t.OrderBy(l => l.Order).Select(l => l.Title)], })]; public async Task GetFilterJsonForExport(string source) => JsonSerializer.Serialize(await GetFilterListForExport(source)); public async Task ImportFiltersFromJson(string source, string json) { - List? importedTags = JsonSerializer.Deserialize>(json); - Source? sourceEntity = await _programRepository.ReadAsync(c => c.Sources.FirstOrDefault(s => s.Code == source)); + var importedTags = JsonSerializer.Deserialize>(json); + var sourceEntity = await _programRepository.ReadAsync(c => c.Sources.FirstOrDefault(s => s.Code == source)); if (sourceEntity == null || importedTags == null) { return "No tags loaded"; } - foreach (TagList tagList in importedTags) { + foreach (var tagList in importedTags) { var count = 1; - TagType tagType = Enum.TryParse(tagList.Title, out TagType parsedTagType) ? parsedTagType : TagType.None; + var tagType = Enum.TryParse(tagList.Title, out var parsedTagType) ? parsedTagType : TagType.None; if (tagType != TagType.None) { foreach (var tag in tagList.List) { var newTag = new TagSource { @@ -46,9 +46,9 @@ public async Task ImportFiltersFromJson(string source, string json) { } public async Task<(List TagSources, int SourceId)> GetFilters(string source, TagType tagType) { - List returnValue = await _programRepository.ReadAsync(c => c.TagSources.Include(c => c.Source).Where(ts => ts.Source != null && ts.Source.Code == source && ts.TagType == tagType).OrderBy(ts => ts.Order).ToList()); + var returnValue = await _programRepository.ReadAsync(c => c.TagSources.Include(c => c.Source).Where(ts => ts.Source != null && ts.Source.Code == source && ts.TagType == tagType).OrderBy(ts => ts.Order).ToList()); var sourceId = 0; - foreach (TagSource? item in returnValue) { + foreach (var item in returnValue) { item.OldTitle = item.Title; sourceId = item.SourceId; } @@ -60,7 +60,7 @@ public async Task ImportFiltersFromJson(string source, string json) { public async Task SaveFilters(IEnumerable tags, IEnumerable tagsForDeletion, string sourceName) { var i = 1; - foreach (TagSource tag in tags) { + foreach (var tag in tags) { tag.Order = i++; if (tag.Id == 0) { _ = await _programRepository.CreateAsync(tag); @@ -72,7 +72,7 @@ public async Task SaveFilters(IEnumerable tags, IEnumerable ProgramByCredentialId([HttpTrigger(Authoriza [OpenApiParameter(name: "q", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "A full text search string -- it will search the title and description for the search querystring.")] [OpenApiParameter(name: "formats", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'On-Campus', 'Online', 'Off-Campus', or 'Hybrid'. Can choose multiple by separating them with the characters '[-]'")] [OpenApiParameter(name: "credentials", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "The credential type (BS, MS, EdM, PhD, Certificate, etc.) Can choose multiple by separating them with the characters '[-]'")] + [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 0.")] + [OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 1000.")] [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(IEnumerable), Description = "All programs that meet the search criteria. If you filter by credentials, it will filter the credential list for each program.")] public async Task Search([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) { _logger.LogInformation("Called ProgramSearch to check deployment."); @@ -90,9 +92,11 @@ public async Task Search([HttpTrigger(AuthorizationLevel.Anony var departments = requestHelper.GetArray(req, "departments"); var formats = requestHelper.GetArray(req, "formats"); var credentials = requestHelper.GetArray(req, "credentials"); + var take = requestHelper.GetInteger(req, "take", 1000); + var skip = requestHelper.GetInteger(req, "skip"); requestHelper.Validate(); var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(await _programGetter.GetPrograms(source, query, tags, tags2, tags3, skills, departments, formats, credentials)); + await response.WriteAsJsonAsync(await _programGetter.GetPrograms(source, query, tags, tags2, tags3, skills, departments, formats, credentials, take, skip)); return response; } catch (Exception ex) { _logger.LogInformation(ex.ToString()); diff --git a/ProgramInformationV2.Search/Getters/CredentialGetter.cs b/ProgramInformationV2.Search/Getters/CredentialGetter.cs index d0f54e2..8d566ff 100644 --- a/ProgramInformationV2.Search/Getters/CredentialGetter.cs +++ b/ProgramInformationV2.Search/Getters/CredentialGetter.cs @@ -34,7 +34,7 @@ public async Task> GetAllCredentialsBySource(string source, st public async Task GetCredential(string credentialId) => (await _programGetter.GetProgramByCredential(credentialId)).Credentials?.SingleOrDefault(c => c.Id == credentialId) ?? new(); public async Task> GetCredentials(string source, string search, IEnumerable tags, IEnumerable tags2, IEnumerable tags3, IEnumerable skills, IEnumerable departments, IEnumerable formats, IEnumerable credentials) { - var response = await _programGetter.GetPrograms(source, search, tags, tags2, tags3, skills, departments, formats, credentials); + var response = await _programGetter.GetPrograms(source, search, tags, tags2, tags3, skills, departments, formats, credentials, 1000, 0); var credentialList = response.Items.SelectMany(p => p.Credentials).Where(c => c.IsActive).OrderBy(c => c.Title).ToList(); if (tags.Any()) { credentialList = credentialList.Where(c => c.TagList.Any(t => tags.Contains(t))).ToList(); diff --git a/ProgramInformationV2.Search/Getters/ProgramGetter.cs b/ProgramInformationV2.Search/Getters/ProgramGetter.cs index 9fce747..551b2b7 100644 --- a/ProgramInformationV2.Search/Getters/ProgramGetter.cs +++ b/ProgramInformationV2.Search/Getters/ProgramGetter.cs @@ -53,9 +53,10 @@ public async Task GetProgramByCredential(string credentialId) { return response.IsValid ? response.Documents.FirstOrDefault() ?? new() : new(); } - public async Task> GetPrograms(string source, string search, IEnumerable tags, IEnumerable tags2, IEnumerable tags3, IEnumerable skills, IEnumerable departments, IEnumerable formats, IEnumerable credentials) { + public async Task> GetPrograms(string source, string search, IEnumerable tags, IEnumerable tags2, IEnumerable tags3, IEnumerable skills, IEnumerable departments, IEnumerable formats, IEnumerable credentials, int take, int skip) { var response = await _openSearchClient.SearchAsync(s => s.Index(UrlTypes.Programs.ConvertToUrlString()) - .Size(1000) + .Skip(skip) + .Size(take) .Query(q => q .Bool(b => b .Filter(f => f.Term(m => m.Field(fld => fld.Source).Value(source)), @@ -74,7 +75,7 @@ public async Task> GetPrograms(string source, string searc f => formats.Any() ? f.Terms(m => m.Field(fld => fld.Formats).Terms(formats)) : f.MatchAll(), f => departments.Any() ? f.Terms(m => m.Field(fld => fld.DepartmentList).Terms(departments)) : f.MatchAll(), f => skills.Any() ? f.Terms(m => m.Field(fld => fld.SkillList).Terms(skills)) : f.MatchAll()) - .Must(m => !string.IsNullOrWhiteSpace(search) ? m.Match(m => m.Field(fld => fld.Title).Query(search)) : m.MatchAll()))) + .Must(m => !string.IsNullOrWhiteSpace(search) ? m.MultiMatch(m => m.Fields(fld => fld.Field("title^10").Field("summarytext^5").Field("description^2").Field("whoshouldapply")).Query(search)) : m.MatchAll()))) .Suggest(a => a.Phrase("didyoumean", p => p.Text(search).Field(fld => fld.Title)))); LogDebug(response); @@ -82,7 +83,7 @@ public async Task> GetPrograms(string source, string searc return new SearchObject() { Error = !response.IsValid ? response.ServerError.Error.ToString() : "", DidYouMean = response.Suggest["didyoumean"].FirstOrDefault()?.Options?.FirstOrDefault()?.Text ?? "", - Total = (int) response.Total, + Total = (int)response.Total, Items = documents }; } From dedb562f79ef06e9578c4e34fcb678807a108aca Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Thu, 15 Jan 2026 14:32:09 -0600 Subject: [PATCH 5/9] Update documentation --- ProgramInformationV2.Function/GetCourses.cs | 4 ++-- ProgramInformationV2.Function/GetPrograms.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ProgramInformationV2.Function/GetCourses.cs b/ProgramInformationV2.Function/GetCourses.cs index eb66180..2525aa2 100644 --- a/ProgramInformationV2.Function/GetCourses.cs +++ b/ProgramInformationV2.Function/GetCourses.cs @@ -81,8 +81,8 @@ public async Task Id([HttpTrigger(AuthorizationLevel.Anonymous [OpenApiParameter(name: "formats", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'On-Campus', 'Online', 'Off-Campus', or 'Hybrid'. Can choose multiple by separating them with the characters '[-]'")] [OpenApiParameter(name: "rubric", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "The course rubric.")] [OpenApiParameter(name: "terms", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'Fall', 'Spring', 'Summer', or 'Winter'. Can choose multiple by separating them with the characters '[-]'")] - [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 0.")] - [OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 1000.")] + [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 1000.")] + [OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 0.")] [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(IEnumerable), Description = "All programs that meet the search criteria. If you filter by credentials, it will filter the credential list for each program.")] public async Task Search([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) { _logger.LogInformation("Called CourseSearch."); diff --git a/ProgramInformationV2.Function/GetPrograms.cs b/ProgramInformationV2.Function/GetPrograms.cs index 6fe322f..87f0886 100644 --- a/ProgramInformationV2.Function/GetPrograms.cs +++ b/ProgramInformationV2.Function/GetPrograms.cs @@ -75,8 +75,8 @@ public async Task ProgramByCredentialId([HttpTrigger(Authoriza [OpenApiParameter(name: "q", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "A full text search string -- it will search the title and description for the search querystring.")] [OpenApiParameter(name: "formats", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'On-Campus', 'Online', 'Off-Campus', or 'Hybrid'. Can choose multiple by separating them with the characters '[-]'")] [OpenApiParameter(name: "credentials", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "The credential type (BS, MS, EdM, PhD, Certificate, etc.) Can choose multiple by separating them with the characters '[-]'")] - [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 0.")] - [OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 1000.")] + [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 1000.")] + [OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 0.")] [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(IEnumerable), Description = "All programs that meet the search criteria. If you filter by credentials, it will filter the credential list for each program.")] public async Task Search([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) { _logger.LogInformation("Called ProgramSearch to check deployment."); From 7b049b44942191130e7e8c18e48c8efdd582ac9e Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Fri, 16 Jan 2026 08:34:00 -0600 Subject: [PATCH 6/9] Bug fix -- sort order with pagination --- ProgramInformationV2.Search/Getters/CourseGetter.cs | 7 +++---- ProgramInformationV2.Search/Getters/ProgramGetter.cs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ProgramInformationV2.Search/Getters/CourseGetter.cs b/ProgramInformationV2.Search/Getters/CourseGetter.cs index e43b354..b89dade 100644 --- a/ProgramInformationV2.Search/Getters/CourseGetter.cs +++ b/ProgramInformationV2.Search/Getters/CourseGetter.cs @@ -71,16 +71,15 @@ public async Task> GetCourses(string source, string search, f => isUpcoming ? f.Term(m => m.Field(fld => fld.IsUpcoming).Value(true)) : f.MatchAll(), f => isCurrent ? f.Term(m => m.Field(fld => fld.IsCurrent).Value(true)) : f.MatchAll()) .Must(m => !string.IsNullOrWhiteSpace(search) ? m.Match(m => m.Field(fld => fld.Title).Query(search)) : m.MatchAll()))) - .Sort(srt => srt.Ascending(f => f.TitleSortKeyword)) + .Sort(srt => string.IsNullOrWhiteSpace(search) ? srt.Ascending(f => f.TitleSortKeyword) : srt.Ascending(f => SortSpecialField.Score)) .Suggest(a => a.Phrase("didyoumean", p => p.Text(search).Field(fld => fld.Title)))); LogDebug(response); - List documents = response.IsValid ? [.. response.Documents] : []; return new SearchObject() { Error = !response.IsValid ? response.ServerError.Error.ToString() : "", DidYouMean = response.Suggest["didyoumean"].FirstOrDefault()?.Options?.FirstOrDefault()?.Text ?? "", - Total = (int) response.Total, - Items = documents + Total = (int)response.Total, + Items = response.IsValid ? [.. response.Documents] : [] }; } diff --git a/ProgramInformationV2.Search/Getters/ProgramGetter.cs b/ProgramInformationV2.Search/Getters/ProgramGetter.cs index 551b2b7..d6b6e43 100644 --- a/ProgramInformationV2.Search/Getters/ProgramGetter.cs +++ b/ProgramInformationV2.Search/Getters/ProgramGetter.cs @@ -76,15 +76,15 @@ public async Task> GetPrograms(string source, string searc f => departments.Any() ? f.Terms(m => m.Field(fld => fld.DepartmentList).Terms(departments)) : f.MatchAll(), f => skills.Any() ? f.Terms(m => m.Field(fld => fld.SkillList).Terms(skills)) : f.MatchAll()) .Must(m => !string.IsNullOrWhiteSpace(search) ? m.MultiMatch(m => m.Fields(fld => fld.Field("title^10").Field("summarytext^5").Field("description^2").Field("whoshouldapply")).Query(search)) : m.MatchAll()))) + .Sort(srt => string.IsNullOrWhiteSpace(search) ? srt.Ascending(f => f.TitleSortKeyword) : srt.Ascending(f => SortSpecialField.Score)) .Suggest(a => a.Phrase("didyoumean", p => p.Text(search).Field(fld => fld.Title)))); LogDebug(response); - List documents = response.IsValid ? (string.IsNullOrEmpty(search) ? [.. response.Documents.OrderBy(p => p.Title)] : [.. response.Documents]) : []; return new SearchObject() { Error = !response.IsValid ? response.ServerError.Error.ToString() : "", DidYouMean = response.Suggest["didyoumean"].FirstOrDefault()?.Options?.FirstOrDefault()?.Text ?? "", Total = (int)response.Total, - Items = documents + Items = response.IsValid ? [.. response.Documents] : [] }; } } From 8f9583c6c45623f848a7064aa2efefa5bf68aaff Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Fri, 16 Jan 2026 08:38:19 -0600 Subject: [PATCH 7/9] Fix based on copilot --- ProgramInformationV2.Search/Getters/CourseGetter.cs | 2 +- ProgramInformationV2.Search/Getters/ProgramGetter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProgramInformationV2.Search/Getters/CourseGetter.cs b/ProgramInformationV2.Search/Getters/CourseGetter.cs index b89dade..c28d87f 100644 --- a/ProgramInformationV2.Search/Getters/CourseGetter.cs +++ b/ProgramInformationV2.Search/Getters/CourseGetter.cs @@ -71,7 +71,7 @@ public async Task> GetCourses(string source, string search, f => isUpcoming ? f.Term(m => m.Field(fld => fld.IsUpcoming).Value(true)) : f.MatchAll(), f => isCurrent ? f.Term(m => m.Field(fld => fld.IsCurrent).Value(true)) : f.MatchAll()) .Must(m => !string.IsNullOrWhiteSpace(search) ? m.Match(m => m.Field(fld => fld.Title).Query(search)) : m.MatchAll()))) - .Sort(srt => string.IsNullOrWhiteSpace(search) ? srt.Ascending(f => f.TitleSortKeyword) : srt.Ascending(f => SortSpecialField.Score)) + .Sort(srt => string.IsNullOrWhiteSpace(search) ? srt.Ascending(f => f.TitleSortKeyword) : srt.Descending(f => SortSpecialField.Score)) .Suggest(a => a.Phrase("didyoumean", p => p.Text(search).Field(fld => fld.Title)))); LogDebug(response); diff --git a/ProgramInformationV2.Search/Getters/ProgramGetter.cs b/ProgramInformationV2.Search/Getters/ProgramGetter.cs index d6b6e43..16f72b9 100644 --- a/ProgramInformationV2.Search/Getters/ProgramGetter.cs +++ b/ProgramInformationV2.Search/Getters/ProgramGetter.cs @@ -76,7 +76,7 @@ public async Task> GetPrograms(string source, string searc f => departments.Any() ? f.Terms(m => m.Field(fld => fld.DepartmentList).Terms(departments)) : f.MatchAll(), f => skills.Any() ? f.Terms(m => m.Field(fld => fld.SkillList).Terms(skills)) : f.MatchAll()) .Must(m => !string.IsNullOrWhiteSpace(search) ? m.MultiMatch(m => m.Fields(fld => fld.Field("title^10").Field("summarytext^5").Field("description^2").Field("whoshouldapply")).Query(search)) : m.MatchAll()))) - .Sort(srt => string.IsNullOrWhiteSpace(search) ? srt.Ascending(f => f.TitleSortKeyword) : srt.Ascending(f => SortSpecialField.Score)) + .Sort(srt => string.IsNullOrWhiteSpace(search) ? srt.Ascending(f => f.TitleSortKeyword) : srt.Descending(f => SortSpecialField.Score)) .Suggest(a => a.Phrase("didyoumean", p => p.Text(search).Field(fld => fld.Title)))); LogDebug(response); From b5d4b05b8d388f8d6d3a67c4a825082db411b81d Mon Sep 17 00:00:00 2001 From: Bryan Jonker Date: Tue, 20 Jan 2026 08:30:29 -0600 Subject: [PATCH 8/9] Consolidate --- .../CourseImport/ScheduleTranslator.cs | 2 ++ ProgramInformationV2.Function/GetCampusCourse.cs | 7 ++++--- ProgramInformationV2.Function/GetPrograms.cs | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs b/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs index df90795..deceb0f 100644 --- a/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs +++ b/ProgramInformationV2.Data/CourseImport/ScheduleTranslator.cs @@ -12,6 +12,8 @@ public static Course Translate(ScheduleCourse scheduleCourse, string source, boo Rubric = scheduleCourse.Rubric, CourseTitle = scheduleCourse.Title, ExternalUrl = $"https://courses.illinois.edu/schedule/terms/{scheduleCourse.Rubric}/{scheduleCourse.CourseNumber}", + Url = $"https://courses.illinois.edu/schedule/terms/{scheduleCourse.Rubric}/{scheduleCourse.CourseNumber}", + UrlFull = $"https://courses.illinois.edu/schedule/terms/{scheduleCourse.Rubric}/{scheduleCourse.CourseNumber}", IsActive = true, CreditHours = scheduleCourse.CreditHours, Description = string.Empty, diff --git a/ProgramInformationV2.Function/GetCampusCourse.cs b/ProgramInformationV2.Function/GetCampusCourse.cs index a8ab416..74aac63 100644 --- a/ProgramInformationV2.Function/GetCampusCourse.cs +++ b/ProgramInformationV2.Function/GetCampusCourse.cs @@ -1,4 +1,3 @@ -using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -8,20 +7,22 @@ using ProgramInformationV2.Data.CourseImport; using ProgramInformationV2.Function.Helper; using ProgramInformationV2.Search.Models; +using System.Net; namespace ProgramInformationV2.Function; public class GetCampusCourse(ILogger logger) { private readonly ILogger _logger = logger; - [Function("GetCampusCourse")] - [OpenApiOperation(operationId: "GetCampusCourse", tags: "Get Course Information", Description = "Get a course from the campus tools. This is using the same course object, but the source and other fields will be blank.")] + [Function("CampusCourse")] + [OpenApiOperation(operationId: "CampusCourse", tags: "Get Course Information", Description = "Get a course from the campus tools. This is using the same course object, but the source and many other fields not supported by the campus tools will be blank. Note that this is doing a lot of calls to see if the course exists in a particular semester, so it may take some time to run as the only way to check is to validate timeout.")] [OpenApiParameter(name: "rubric", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The rubric.")] [OpenApiParameter(name: "coursenumber", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The course number.")] [OpenApiParameter(name: "singlesemester", In = ParameterLocation.Query, Required = true, Type = typeof(bool), Description = "If set to true, then only query latest semester -- used if you just need the description. Defaults to false.")] [OpenApiParameter(name: "courseonly", In = ParameterLocation.Query, Required = true, Type = typeof(bool), Description = "If set to true, then do not include section information. Defaults to false.")] [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(List), Description = "The list of courses")] public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) { + _logger.LogInformation("Called CampusCourse."); var requestHelper = RequestHelperFactory.Create(); requestHelper.Initialize(req); var rubric = requestHelper.GetRequest(req, "rubric"); diff --git a/ProgramInformationV2.Function/GetPrograms.cs b/ProgramInformationV2.Function/GetPrograms.cs index 87f0886..2486ddd 100644 --- a/ProgramInformationV2.Function/GetPrograms.cs +++ b/ProgramInformationV2.Function/GetPrograms.cs @@ -1,4 +1,3 @@ -using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -7,6 +6,7 @@ using Microsoft.OpenApi.Models; using ProgramInformationV2.Function.Helper; using ProgramInformationV2.Search.Getters; +using System.Net; namespace ProgramInformationV2.Function { @@ -75,7 +75,7 @@ public async Task ProgramByCredentialId([HttpTrigger(Authoriza [OpenApiParameter(name: "q", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "A full text search string -- it will search the title and description for the search querystring.")] [OpenApiParameter(name: "formats", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "Either 'On-Campus', 'Online', 'Off-Campus', or 'Hybrid'. Can choose multiple by separating them with the characters '[-]'")] [OpenApiParameter(name: "credentials", In = ParameterLocation.Query, Required = false, Type = typeof(string), Description = "The credential type (BS, MS, EdM, PhD, Certificate, etc.) Can choose multiple by separating them with the characters '[-]'")] - [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many courses do you want? Defaults to 1000.")] + [OpenApiParameter(name: "take", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "How many programs do you want? Defaults to 1000.")] [OpenApiParameter(name: "skip", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "A skip value to help with pagination. Defaults to 0.")] [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(IEnumerable), Description = "All programs that meet the search criteria. If you filter by credentials, it will filter the credential list for each program.")] public async Task Search([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) { From 8c52e3bfadf483235ba1f522b9fb21298554ce69 Mon Sep 17 00:00:00 2001 From: Bryan Jonker <65776851+bryanjonker-illinois@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:53:22 -0600 Subject: [PATCH 9/9] Update configuration keys for database and OpenSearch --- ProgramInformationV2.Function/Program.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ProgramInformationV2.Function/Program.cs b/ProgramInformationV2.Function/Program.cs index 43bd5c4..2853053 100644 --- a/ProgramInformationV2.Function/Program.cs +++ b/ProgramInformationV2.Function/Program.cs @@ -24,10 +24,10 @@ services.ConfigureFunctionsApplicationInsights(); _ = services.AddApplicationInsightsTelemetryWorkerService(); _ = services.ConfigureFunctionsApplicationInsights(); - _ = services.AddDbContextFactory(options => options.UseSqlServer(hostContext.Configuration["AppConnection"]).EnableSensitiveDataLogging(true)); + _ = services.AddDbContextFactory(options => options.UseSqlServer(hostContext.Configuration["Values:AppConnection"]).EnableSensitiveDataLogging(true)); _ = services.AddScoped(); - _ = services.AddSingleton(c => OpenSearchFactory.CreateLowLevelClient(hostContext.Configuration["SearchUrl"], hostContext.Configuration["AccessKey"], hostContext.Configuration["SecretKey"], hostContext.Configuration["SearchDebug"] == "true")); - _ = services.AddSingleton(c => OpenSearchFactory.CreateClient(hostContext.Configuration["SearchUrl"], hostContext.Configuration["AccessKey"], hostContext.Configuration["SecretKey"], hostContext.Configuration["SearchDebug"] == "true")); + _ = services.AddSingleton(c => OpenSearchFactory.CreateLowLevelClient(hostContext.Configuration["Values:SearchUrl"], hostContext.Configuration["Values:AccessKey"], hostContext.Configuration["Values:SecretKey"], hostContext.Configuration["Values:SearchDebug"] == "true")); + _ = services.AddSingleton(c => OpenSearchFactory.CreateClient(hostContext.Configuration["Values:SearchUrl"], hostContext.Configuration["Values:AccessKey"], hostContext.Configuration["Values:SecretKey"], hostContext.Configuration["Values:SearchDebug"] == "true")); _ = services.AddScoped(); _ = services.AddScoped(); _ = services.AddScoped(); @@ -42,4 +42,4 @@ }) .Build(); -host.Run(); \ No newline at end of file +host.Run();