-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create controller and service for options/code lists (#13046)
* Add GET endpoint for fetching an options list * Add PUT endpoint for creating or overwriting an options list * Implement 'Option' model * Add DELETE endpoint * Add GET endpoint for fetching list of options * Add tests for all return codes * Add tests for new methods in AltinnAppGitRepository
- Loading branch information
1 parent
99069a0
commit e069007
Showing
14 changed files
with
1,042 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Altinn.Studio.Designer.Helpers; | ||
using Altinn.Studio.Designer.Models; | ||
using Altinn.Studio.Designer.Services.Interfaces; | ||
using LibGit2Sharp; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
namespace Altinn.Studio.Designer.Controllers; | ||
|
||
/// <summary> | ||
/// Controller containing actions related to options (code lists). | ||
/// </summary> | ||
[ApiController] | ||
[Authorize] | ||
[AutoValidateAntiforgeryToken] | ||
[Route("designer/api/{org}/{repo:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/options")] | ||
public class OptionsController : ControllerBase | ||
{ | ||
private readonly IOptionsService _optionsService; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="OptionsController"/> class. | ||
/// </summary> | ||
/// <param name="optionsService">The options service.</param> | ||
public OptionsController(IOptionsService optionsService) | ||
{ | ||
_optionsService = optionsService; | ||
} | ||
|
||
/// <summary> | ||
/// Fetches the IDs of the options lists belonging to the app. | ||
/// </summary> | ||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||
/// <param name="repo">Application identifier which is unique within an organisation.</param> | ||
/// <returns>Array of options list's IDs. Empty array if none are found</returns> | ||
[HttpGet] | ||
[Produces("application/json")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
public ActionResult<string[]> GetOptionsListIds(string org, string repo) | ||
{ | ||
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); | ||
|
||
string[] optionsListIds = _optionsService.GetOptionsListIds(org, repo, developer); | ||
|
||
return Ok(optionsListIds); | ||
} | ||
|
||
/// <summary> | ||
/// Fetches a specific option list. | ||
/// </summary> | ||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||
/// <param name="repo">Application identifier which is unique within an organisation.</param> | ||
/// <param name="optionsListId">Name of the option list.</param> | ||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> | ||
[HttpGet] | ||
[Produces("application/json")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[ProducesResponseType(StatusCodes.Status404NotFound)] | ||
[Route("{optionsListId}")] | ||
public async Task<ActionResult<List<Option>>> GetOptionsList(string org, string repo, [FromRoute] string optionsListId, CancellationToken cancellationToken = default) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); | ||
|
||
try | ||
{ | ||
List<Option> optionsList = await _optionsService.GetOptionsList(org, repo, developer, optionsListId, cancellationToken); | ||
return Ok(optionsList); | ||
} | ||
catch (NotFoundException ex) | ||
{ | ||
return NotFound(ex.Message); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Creates or overwrites an options list. | ||
/// </summary> | ||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||
/// <param name="repo">Application identifier which is unique within an organisation.</param> | ||
/// <param name="optionsListId">Name of the options list.</param> | ||
/// <param name="payload">Contents of the options list.</param> | ||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> | ||
[HttpPut] | ||
[Produces("application/json")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[ProducesResponseType(StatusCodes.Status400BadRequest)] | ||
[Route("{optionsListId}")] | ||
public async Task<ActionResult> CreateOrOverwriteOptionsList(string org, string repo, [FromRoute] string optionsListId, [FromBody] List<Option> payload, CancellationToken cancellationToken = default) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); | ||
|
||
var newOptionsList = await _optionsService.CreateOrOverwriteOptionsList(org, repo, developer, optionsListId, payload, cancellationToken); | ||
|
||
return Ok(newOptionsList); | ||
} | ||
|
||
/// <summary> | ||
/// Deletes an option list. | ||
/// </summary> | ||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||
/// <param name="repo">Application identifier which is unique within an organisation.</param> | ||
/// <param name="optionsListId">Name of the option list.</param> | ||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> | ||
[HttpDelete] | ||
[Produces("application/json")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[Route("{optionsListId}")] | ||
public async Task<ActionResult> DeleteOptionsList(string org, string repo, [FromRoute] string optionsListId, CancellationToken cancellationToken = default) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); | ||
|
||
bool optionsListExists = await _optionsService.OptionsListExists(org, repo, developer, optionsListId, cancellationToken); | ||
if (optionsListExists) | ||
{ | ||
_optionsService.DeleteOptionsList(org, repo, developer, optionsListId); | ||
} | ||
|
||
return Ok($"The options file {optionsListId}.json has been deleted."); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Text.Json.Serialization; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Altinn.Studio.Designer.Models; | ||
|
||
/// <summary> | ||
/// Options used in checkboxes, radio buttons and dropdowns. | ||
/// </summary> | ||
public class Option | ||
{ | ||
/// <summary> | ||
/// Value that connects the option to the data model. | ||
/// </summary> | ||
[Required] | ||
[JsonPropertyName("value")] | ||
public string Value { get; set; } | ||
|
||
/// <summary> | ||
/// Label to present to the user. | ||
/// </summary> | ||
[Required] | ||
[JsonPropertyName("label")] | ||
public string Label { get; set; } | ||
|
||
/// <summary> | ||
/// Description, typically displayed below the label. | ||
/// </summary> | ||
[JsonPropertyName("description")] | ||
public Optional<string> Description { get; set; } | ||
|
||
/// <summary> | ||
/// Help text, typically wrapped inside a popover. | ||
/// </summary> | ||
[JsonPropertyName("helpText")] | ||
public Optional<string> HelpText { get; set; } | ||
} |
93 changes: 93 additions & 0 deletions
93
backend/src/Designer/Services/Implementation/OptionsService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
using System.Collections.Generic; | ||
using System.Text.Json; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Altinn.Studio.Designer.Models; | ||
using Altinn.Studio.Designer.Services.Interfaces; | ||
using LibGit2Sharp; | ||
|
||
namespace Altinn.Studio.Designer.Services.Implementation; | ||
|
||
/// <summary> | ||
/// Service for handling options lists (code lists). | ||
/// </summary> | ||
public class OptionsService : IOptionsService | ||
{ | ||
private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory; | ||
|
||
/// <summary> | ||
/// Constructor | ||
/// </summary> | ||
/// <param name="altinnGitRepositoryFactory">IAltinnGitRepository</param> | ||
public OptionsService(IAltinnGitRepositoryFactory altinnGitRepositoryFactory) | ||
{ | ||
_altinnGitRepositoryFactory = altinnGitRepositoryFactory; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public string[] GetOptionsListIds(string org, string repo, string developer) | ||
{ | ||
var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, repo, developer); | ||
|
||
try | ||
{ | ||
string[] optionsLists = altinnAppGitRepository.GetOptionsListIds(); | ||
return optionsLists; | ||
} | ||
catch (NotFoundException) // Is raised if the Options folder does not exist | ||
{ | ||
return []; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public async Task<List<Option>> GetOptionsList(string org, string repo, string developer, string optionsListId, CancellationToken cancellationToken = default) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, repo, developer); | ||
|
||
string optionsListString = await altinnAppGitRepository.GetOptionsList(optionsListId, cancellationToken); | ||
var optionsList = JsonSerializer.Deserialize<List<Option>>(optionsListString); | ||
|
||
return optionsList; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public async Task<List<Option>> CreateOrOverwriteOptionsList(string org, string repo, string developer, string optionsListId, List<Option> payload, CancellationToken cancellationToken = default) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, repo, developer); | ||
|
||
var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; | ||
string payloadString = JsonSerializer.Serialize(payload, jsonOptions); | ||
|
||
string updatedOptionsString = await altinnAppGitRepository.CreateOrOverwriteOptionsList(optionsListId, payloadString, cancellationToken); | ||
var updatedOptions = JsonSerializer.Deserialize<List<Option>>(updatedOptionsString); | ||
|
||
return updatedOptions; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void DeleteOptionsList(string org, string repo, string developer, string optionsListId) | ||
{ | ||
var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, repo, developer); | ||
|
||
altinnAppGitRepository.DeleteOptionsList(optionsListId); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public async Task<bool> OptionsListExists(string org, string repo, string developer, string optionsListId, CancellationToken cancellationToken = default) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
try | ||
{ | ||
await GetOptionsList(org, repo, developer, optionsListId, cancellationToken); | ||
return true; | ||
} | ||
catch (NotFoundException) | ||
{ | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.