Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add resource-connections endpoint #363

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/Altinn.ResourceRegistry.Core/AccessLists/AccessListData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#nullable enable

namespace Altinn.ResourceRegistry.Core.AccessLists;

/// <summary>
/// Utils for <see cref="AccessListData{T}"/>.
/// </summary>
public static class AccessListData
{
/// <summary>
/// Creates a new instance of <see cref="AccessListData{T}"/>.
/// </summary>
/// <typeparam name="T">The data type.</typeparam>
/// <param name="id">The access list id.</param>
/// <param name="updatedAt">When the access list was last updated.</param>
/// <param name="version">The access list version.</param>
/// <param name="value">The data.</param>
/// <returns>A new <see cref="AccessListData{T}"/>.</returns>
public static AccessListData<T> Create<T>(
Guid id,
DateTimeOffset updatedAt,
ulong version,
T value)
=> new(id, updatedAt, version, value);

/// <summary>
/// Creates a new instance of <see cref="AccessListData{T}"/> from a <see cref="AccessListMetadata"/> and the data.
/// </summary>
/// <typeparam name="T">The data type.</typeparam>
/// <param name="metadata">The access list metadata.</param>
/// <param name="value">The data.</param>
/// <returns>A new <see cref="AccessListData{T}"/>.</returns>
public static AccessListData<T> Create<T>(
AccessListMetadata metadata,
T value)
=> new(metadata, value);
}

/// <summary>
/// Data from an access list along with metadata.
/// </summary>
/// <typeparam name="T">The data type.</typeparam>
/// <param name="Id"><inheritdoc cref="AccessListMetadata(Guid, DateTimeOffset, ulong)" path="/param[@name='Id']/node()"/></param>
/// <param name="UpdatedAt"><inheritdoc cref="AccessListMetadata(Guid, DateTimeOffset, ulong)" path="/param[@name='UpdatedAt']/node()"/></param>
/// <param name="Version"><inheritdoc cref="AccessListMetadata(Guid, DateTimeOffset, ulong)" path="/param[@name='Version']/node()"/></param>
/// <param name="Value">The data value.</param>
public sealed record AccessListData<T>(
Guid Id,
DateTimeOffset UpdatedAt,
ulong Version,
T Value)
: AccessListMetadata(Id, UpdatedAt, Version)
{
/// <summary>
/// Initializes a new instance of the <see cref="AccessListData{T}"/> class
/// from a <see cref="AccessListMetadata"/> and the data.
/// </summary>
/// <param name="metadata">The metadata.</param>
/// <param name="value">The value.</param>
public AccessListData(AccessListMetadata metadata, T value)
: this(metadata.Id, metadata.UpdatedAt, metadata.Version, value)
{
}
}
24 changes: 4 additions & 20 deletions src/Altinn.ResourceRegistry.Core/AccessLists/AccessListInfo.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
#nullable enable

using Altinn.ResourceRegistry.Core.Models;
using Altinn.ResourceRegistry.Core.Models.Versioned;

namespace Altinn.ResourceRegistry.Core.AccessLists;

/// <summary>
/// Information about an access list.
/// </summary>
/// <param name="Id">The database id for the access list.</param>
/// <param name="Id"><inheritdoc cref="AccessListMetadata(Guid, DateTimeOffset, ulong)" path="/param[@name='Id']/node()"/></param>
/// <param name="ResourceOwner">The resource owner (a org.nr.).</param>
/// <param name="Identifier">The resource owner-unique identifier. Limited to 'a'-'z' and '-' characters.</param>
/// <param name="Name">The access list name. Does not have to be unique, and can contain any characters.</param>
/// <param name="Description">A access list description.</param>
/// <param name="CreatedAt">When this access list was created.</param>
/// <param name="UpdatedAt">When this access list was last updated.</param>
/// <param name="UpdatedAt"><inheritdoc cref="AccessListMetadata(Guid, DateTimeOffset, ulong)" path="/param[@name='UpdatedAt']/node()"/></param>
/// <param name="ResourceConnections">The resource connections for the access list.</param>
/// <param name="Version">The version of this access list.</param>
/// <param name="Version"><inheritdoc cref="AccessListMetadata(Guid, DateTimeOffset, ulong)" path="/param[@name='Version']/node()"/></param>
public record AccessListInfo(
Guid Id,
string ResourceOwner,
Expand All @@ -27,17 +24,4 @@ public record AccessListInfo(
DateTimeOffset UpdatedAt,
IReadOnlyList<AccessListResourceConnection>? ResourceConnections,
ulong Version)
: IVersionEquatable<ulong>
{
/// <inheritdoc/>
bool IVersionEquatable<ulong>.ModifiedSince(HttpDateTimeHeaderValue other)
{
return UpdatedAt > other;
}

/// <inheritdoc/>
bool IVersionEquatable<ulong>.VersionEquals(ulong other)
{
return Version == other;
}
}
: AccessListMetadata(Id, UpdatedAt, Version);
31 changes: 31 additions & 0 deletions src/Altinn.ResourceRegistry.Core/AccessLists/AccessListMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#nullable enable

using Altinn.ResourceRegistry.Core.Models;
using Altinn.ResourceRegistry.Core.Models.Versioned;

namespace Altinn.ResourceRegistry.Core.AccessLists;

/// <summary>
/// Metadata about an access list.
/// </summary>
/// <param name="Id">The database id for the access list.</param>
/// <param name="UpdatedAt">When this access list was last updated.</param>
/// <param name="Version">The version of this access list.</param>
public record AccessListMetadata(
Guid Id,
DateTimeOffset UpdatedAt,
ulong Version)
: IVersionEquatable<ulong>
{
/// <inheritdoc/>
bool IVersionEquatable<ulong>.ModifiedSince(HttpDateTimeHeaderValue other)
{
return UpdatedAt > other;
}

/// <inheritdoc/>
bool IVersionEquatable<ulong>.VersionEquals(ulong other)
{
return Version == other;
}
}
51 changes: 48 additions & 3 deletions src/Altinn.ResourceRegistry.Core/AccessLists/AccessListService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ namespace Altinn.ResourceRegistry.Core.AccessLists;
internal class AccessListService
: IAccessListService
{
private const int LISTS_PAGE_SIZE = 20;
private const int SMALL_PAGE_SIZE = 20;
private const int LARGE_PAGE_SIZE = 100;

private readonly IAccessListsRepository _repository;

Expand All @@ -41,11 +42,11 @@ public async Task<Page<AccessListInfo, string>> GetAccessListsByOwner(
var accessLists = await _repository.GetAccessListsByOwner(
owner,
continueFrom: request.ContinuationToken,
count: LISTS_PAGE_SIZE + 1,
count: SMALL_PAGE_SIZE + 1,
includes,
cancellationToken);

return Page.Create(accessLists, LISTS_PAGE_SIZE, static list => list.Identifier);
return Page.Create(accessLists, SMALL_PAGE_SIZE, static list => list.Identifier);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -191,4 +192,48 @@ public async Task<Conditional<AccessListInfo, ulong>> CreateOrUpdateAccessList(
// Return the maybe updated list.
return aggregate.AsAccessListInfo();
}

/// <inheritdoc/>
public async Task<Conditional<VersionedPage<AccessListResourceConnection, string, ulong>, ulong>> GetAccessListResourceConnections(
string owner,
string identifier,
Page<string>.Request request,
IVersionedEntityCondition<ulong>? condition = null,
CancellationToken cancellationToken = default)
{
Guard.IsNotNull(owner);
Guard.IsNotNull(identifier);
Guard.IsNotNull(request);

var data = await _repository.GetAccessListResourceConnections(
owner,
identifier,
continueFrom: request.ContinuationToken,
count: LARGE_PAGE_SIZE + 1,
includeActions: true,
cancellationToken);

if (data is null)
{
return Conditional.NotFound();
}

if (condition is not null)
{
var result = condition.Validate(data);

if (result == VersionedEntityConditionResult.Failed)
{
return Conditional.ConditionFailed();
}

if (result == VersionedEntityConditionResult.Unmodified)
{
return Conditional.Unmodified(data.Version, data.UpdatedAt);
}
}

return Page.Create(data.Value, LARGE_PAGE_SIZE, static resource => resource.ResourceIdentifier)
.WithVersion(data.UpdatedAt, data.Version);
}
}
16 changes: 16 additions & 0 deletions src/Altinn.ResourceRegistry.Core/AccessLists/IAccessListService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,20 @@ Task<Conditional<AccessListInfo, ulong>> CreateOrUpdateAccessList(
string description,
IVersionedEntityCondition<ulong>? condition = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Gets a page of access list resource-connections by owner and identifier.
/// </summary>
/// <param name="owner">The resource owner (org.nr.).</param>
/// <param name="identifier">The access list identifier (unique per owner).</param>
/// <param name="request">The page request.</param>
/// <param name="condition">Optional condition on the access list</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns></returns>
Task<Conditional<VersionedPage<AccessListResourceConnection, string, ulong>, ulong>> GetAccessListResourceConnections(
string owner,
string identifier,
Page<string>.Request request,
IVersionedEntityCondition<ulong>? condition = null,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ Task<IReadOnlyList<AccessListInfo>> GetAccessListsByOwner(
/// Lookup resource connections for an access list by it's id.
/// </summary>
/// <param name="id">The id</param>
/// <param name="continueFrom">An optional value to continue iterating from. This value is an <see cref="AccessListResourceConnection.ResourceIdentifier"/> to start from, using greater than or equals comparison.</param>
/// <param name="count">The total number of entries to return.</param>
/// <param name="includeActions">Whether to include actions in the response.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A list of <see cref="AccessListResourceConnection"/> for the given resource.</returns>
Task<IReadOnlyList<AccessListResourceConnection>?> GetAccessListResourceConnections(
Task<AccessListData<IReadOnlyList<AccessListResourceConnection>>?> GetAccessListResourceConnections(
Guid id,
string? continueFrom,
int count,
bool includeActions,
CancellationToken cancellationToken = default);

Expand All @@ -59,12 +63,16 @@ Task<IReadOnlyList<AccessListInfo>> GetAccessListsByOwner(
/// </summary>
/// <param name="resourceOwner">The resource owner</param>
/// <param name="identifier">The access list identifier (unique per owner).</param>
/// <param name="continueFrom">An optional value to continue iterating from. This value is an <see cref="AccessListResourceConnection.ResourceIdentifier"/> to start from, using greater than or equals comparison.</param>
/// <param name="count">The total number of entries to return.</param>
/// <param name="includeActions">Whether to include actions in the response.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A list of <see cref="AccessListResourceConnection"/> for the given resource.</returns>
Task<IReadOnlyList<AccessListResourceConnection>?> GetAccessListResourceConnections(
Task<AccessListData<IReadOnlyList<AccessListResourceConnection>>?> GetAccessListResourceConnections(
string resourceOwner,
string identifier,
string? continueFrom,
int count,
bool includeActions,
CancellationToken cancellationToken = default);

Expand All @@ -74,7 +82,7 @@ Task<IReadOnlyList<AccessListInfo>> GetAccessListsByOwner(
/// <param name="id">The access list id</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A list of <see cref="AccessListMembership"/></returns>
Task<IReadOnlyList<AccessListMembership>?> GetAccessListMemberships(
Task<AccessListData<IReadOnlyList<AccessListMembership>>?> GetAccessListMemberships(
Guid id,
CancellationToken cancellationToken = default);

Expand All @@ -85,7 +93,7 @@ Task<IReadOnlyList<AccessListInfo>> GetAccessListsByOwner(
/// <param name="identifier">The access list identifier (unique per owner).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>A list of <see cref="AccessListMembership"/></returns>
Task<IReadOnlyList<AccessListMembership>?> GetAccessListMemberships(
Task<AccessListData<IReadOnlyList<AccessListMembership>>?> GetAccessListMemberships(
string resourceOwner,
string identifier,
CancellationToken cancellationToken = default);
Expand Down
1 change: 1 addition & 0 deletions src/Altinn.ResourceRegistry.Core/Models/Conditional.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.ComponentModel;
using System.Diagnostics;
using Altinn.ResourceRegistry.Core.Models.Versioned;
using CommunityToolkit.Diagnostics;

namespace Altinn.ResourceRegistry.Core.Models;
Expand Down
51 changes: 51 additions & 0 deletions src/Altinn.ResourceRegistry.Core/Models/Optional.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#nullable enable

using CommunityToolkit.Diagnostics;

namespace Altinn.ResourceRegistry.Core.Models;

/// <summary>
/// An optional value (similar to <see cref="Nullable{T}"/>, but supports reference types and value types).
/// </summary>
/// <typeparam name="T">The inner type.</typeparam>
public readonly record struct Optional<T>
{
private readonly T? _value;

/// <summary>
/// Gets a value indicating whether this instance has a value.
/// </summary>
public bool HasValue { get; }

/// <summary>
/// Gets the value if it exists.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
public T Value
{
get
{
if (!HasValue)
{
ThrowHelper.ThrowInvalidOperationException();
}

return _value!;
}
}

/// <summary>
/// Constructs a new instance of <see cref="Optional{T}"/> with a value.
/// </summary>
/// <param name="value">The value.</param>
public Optional(T value)
{
_value = value;
HasValue = true;
}

public static implicit operator Optional<T>(T value)
{
return new(value);
}
}
8 changes: 4 additions & 4 deletions src/Altinn.ResourceRegistry.Core/Models/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ public static Page<TItem, TToken> Create<TItem, TToken>(
/// </summary>
/// <typeparam name="TToken">The token type used to request the next page</typeparam>
public abstract class Page<TToken>(
TToken? continuationToken)
Optional<TToken> continuationToken)
: Page()
{
/// <summary>
/// Gets the continuation token, if any.
/// </summary>
public TToken? ContinuationToken => continuationToken;
public Optional<TToken> ContinuationToken => continuationToken;

/// <summary>
/// A request for the next page of items.
Expand All @@ -71,9 +71,9 @@ public sealed class Request(TToken? continuationToken)
/// </summary>
/// <typeparam name="TItem">The item type</typeparam>
/// <typeparam name="TToken">The token type used to request the next page</typeparam>
public sealed class Page<TItem, TToken>(
public class Page<TItem, TToken>(
IReadOnlyList<TItem> items,
TToken? continuationToken)
Optional<TToken> continuationToken)
: Page<TToken>(continuationToken)
{
/// <summary>
Expand Down
Loading
Loading