Commit e3b43f74 authored by Andreas Müller's avatar Andreas Müller
Browse files

Implemented the alarm section. Missing sections: messages, navigation, states, test

parent 4221fe33
image: mcr.microsoft.com/dotnet/sdk
variables:
TZ: "Europe/Berlin"
stages:
- build
- deploy
......@@ -10,14 +13,24 @@ build:
- docker
script:
- bash scripts/build.sh
artifacts:
paths:
- artifacts/*.nupkg
- artifacts/*.snupkg
expire_in: 1 day
deploy-baget:
stage: deploy
tags:
- docker
script:
- dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg
deploy:
deploy-nuget:
stage: deploy
tags:
- docker
only:
- tags
script:
- bash scripts/build.sh
- dotnet nuget push -k $NUGET_APIKEY -s https://api.nuget.org/v3/index.json --skip-duplicate artifacts/*.nupkg
......@@ -22,7 +22,7 @@
<PackageProjectUrl>https://am-wd.de/</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl></RepositoryUrl>
<RepositoryUrl>https://git.am-wd.de/andreasmueller/divera-24-7-api.git</RepositoryUrl>
<Product>AM.WD API for Divera 24/7</Product>
<Description>API implementation for Divera 24/7 (divera247.com)</Description>
......@@ -39,7 +39,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
......
using System;
using System.Runtime.Serialization;
namespace AMWD.Net.Api.Divera
{
/// <summary>
/// Represents an error with the access key.
/// </summary>
public class AccessKeyException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="AccessKeyException"/> class.
/// </summary>
public AccessKeyException()
: base()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="AccessKeyException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public AccessKeyException(string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="AccessKeyException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a <c>null</c> reference if no inner exception is specified.</param>
public AccessKeyException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="AccessKeyException"/> class with serialized data.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The info parameter is null.</exception>
/// <exception cref="SerializationException">The class name is null or <see cref="Exception.HResult"/> is zero (0).</exception>
protected AccessKeyException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}
......@@ -24,49 +24,107 @@ namespace AMWD.Net.Api.Divera
/// <summary>
/// Creates a new alarm.
/// [Methode zum Alarmieren von Einheiten]
/// </summary>
/// <param name="request">The alarm request.</param>
/// <returns></returns>
public async Task<bool> CreateAlarm(CreateAlarmRequest request)
/// <returns><c>true</c> on success, otherwise <c>false</c>.</returns>
/// <exception cref="AccessKeyException">The access key is missing or invalid.</exception>
/// <exception cref="ArgumentNullException">The request is missing.</exception>
public Task<bool> CreateAlarm(CreateAlarmRequest request)
{
AssertAccessKeyAndNotDisposed();
CheckAccessKeyOrDisposed();
var requestContent = request.ConvertToHttpContent();
var response = await httpClient.PostAsync($"{DiveraApiUrls.Alarm}?accesskey={AccessKey}", requestContent);
return SendRequestWithDefaultResponse(DiveraApiUrls.Alarm, requestContent);
}
/// <summary>
/// Gets the information of the latest alarm.
/// [Abfrage von Informationen zum letzten Einsatz]
/// </summary>
/// <returns>The alarm information or <c>null</c>.</returns>
/// <exception cref="AccessKeyException">The access key is missing or invalid.</exception>
public async Task<AlarmResultResponse> GetLatestAlarm()
{
CheckAccessKeyOrDisposed();
var response = await httpClient.GetAsync($"{DiveraApiUrls.AlarmLatest}?accesskey={AccessKey}");
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == HttpStatusCode.Forbidden)
throw new ArgumentException("The access key is invalid", nameof(AccessKey));
throw new AccessKeyException("The access key is invalid");
return false;
return null;
}
string responseContent = await response.Content.ReadAsStringAsync();
var successResponse = JsonConvert.DeserializeObject<SuccessResponse>(responseContent);
var successResponse = JsonConvert.DeserializeObject<AlarmResultResponse>(responseContent);
return successResponse;
}
/// <summary>
/// Sends a confirmation to a running alarm.
/// [Rückmeldung eines offenen Einsatzes]
/// </summary>
/// <param name="request">The confirmation request.</param>
/// <returns><c>true</c> on success, otherwise <c>false</c>.</returns>
/// <exception cref="AccessKeyException">The access key is missing or invalid.</exception>
/// <exception cref="ArgumentNullException">The request or required arguments are missing.</exception>
public Task<bool> SetAlarmConfirmation(SetAlarmConfirmationRequest request)
{
CheckAccessKeyOrDisposed();
var requestContent = request.ConvertToHttpContent();
return SendRequestWithDefaultResponse(DiveraApiUrls.AlarmConfirm, requestContent);
}
/// <summary>
/// Closes a running alarm or adds a report.
/// [Alarm schließen / Einsatzbericht eintragen]
/// </summary>
/// <param name="request">The close request.</param>
/// <returns><c>true</c> on success, otherwise <c>false</c>.</returns>
/// <exception cref="AccessKeyException">The access key is missing or invalid.</exception>
/// <exception cref="ArgumentNullException">The request is missing.</exception>
public Task<bool> CloseAlarm(CloseAlarmRequest request)
{
CheckAccessKeyOrDisposed();
return successResponse.Success;
var requestContent = request.ConvertToHttpContent();
return SendRequestWithDefaultResponse(DiveraApiUrls.AlarmClose, requestContent);
}
/// <summary>
/// Gets the information of the latest alarm.
/// Makrs a dataset (alarm, message, appointment) as viewed/read.
/// [Datensatz als gelesen markieren]
/// </summary>
/// <returns></returns>
public async Task<AlarmResultResponse> GetLatestAlarm()
/// <param name="request">The mark request.</param>
/// <returns><c>true</c> on success, otherwise <c>false</c>.</returns>
/// <exception cref="AccessKeyException">The access key is missing or invalid.</exception>
/// <exception cref="ArgumentNullException">The request is missing.</exception>
public Task<bool> MarkViewed(MarkViewedRequest request)
{
AssertAccessKeyAndNotDisposed();
CheckAccessKeyOrDisposed();
var response = await httpClient.GetAsync($"{DiveraApiUrls.AlarmLatest}?accesskey={AccessKey}");
var requestContent = request.ConvertToHttpContent();
return SendRequestWithDefaultResponse(DiveraApiUrls.Viewed, requestContent);
}
private async Task<bool> SendRequestWithDefaultResponse(string url, HttpContent httpContent)
{
var response = await httpClient.PostAsync($"{url}?accesskey={AccessKey}", httpContent);
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == HttpStatusCode.Forbidden)
throw new ArgumentException("The access key is invalid", nameof(AccessKey));
throw new AccessKeyException("The access key is invalid");
return null;
return false;
}
string responseContent = await response.Content.ReadAsStringAsync();
var successResponse = JsonConvert.DeserializeObject<AlarmResultResponse>(responseContent);
return successResponse;
var successResponse = JsonConvert.DeserializeObject<SuccessResponse>(responseContent);
return successResponse.IsSuccessful;
}
#region IDisposable implementation
......@@ -84,13 +142,13 @@ namespace AMWD.Net.Api.Divera
httpClient.Dispose();
}
private void AssertAccessKeyAndNotDisposed()
private void CheckAccessKeyOrDisposed()
{
if (isDisposed)
throw new ObjectDisposedException(GetType().FullName);
if (string.IsNullOrWhiteSpace(AccessKey))
throw new ArgumentNullException(nameof(AccessKey));
throw new AccessKeyException("The access key is missing");
}
#endregion IDisposable implementation
......
......@@ -23,7 +23,7 @@
/// </summary>
public static readonly string AlarmConfirm = $"{Base}/confirm-alarm";
/// <summary>
/// The url to close an alarm
/// The url to close an alarm.
/// </summary>
public static readonly string AlarmClose = $"{Base}/close-alarm";
......@@ -31,5 +31,10 @@
/// The url to create a news entry.
/// </summary>
public static readonly string News = $"{Base}/news";
/// <summary>
/// The url to mark a dataset as viewed.
/// </summary>
public static readonly string Viewed = $"{Base}/viewed";
}
}
namespace AMWD.Net.Api.Divera.Utils
namespace AMWD.Net.Api.Divera.Enums
{
/// <summary>
/// Listing of notification types.
......
using System;
namespace AMWD.Net.Api.Divera.Requests
{
/// <summary>
/// The request model to close an alarm.
/// </summary>
public class CloseAlarmRequest
{
/// <summary>
/// Gets or sets the alarm id.
/// [ID des Einsatzes]
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the alarm should be closed.
/// [Zustand geschlossen oder geöffnet]
/// </summary>
public bool? IsClosed { get; set; }
/// <summary>
/// Gets or sets a report.
/// [Einsatzbericht]
/// </summary>
public string Report { get; set; }
/// <summary>
/// Gets or sets the timestamp.
/// [Datum/Uhrzeit]
/// </summary>
public DateTime? Timestamp { get; set; }
}
}
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace AMWD.Net.Api.Divera.Requests
{
......@@ -27,7 +26,7 @@ namespace AMWD.Net.Api.Divera.Requests
/// Gets or sets a value indicating whether to use priority signals.
/// [Priorität/Sonderrechte]
/// </summary>
public bool Priority { get; set; }
public bool HasPriority { get; set; }
/// <summary>
/// Gets or sets the alarm message.
......@@ -69,7 +68,7 @@ namespace AMWD.Net.Api.Divera.Requests
public string Caller { get; set; }
/// <summary>
/// Gets or sets information about the patient.
/// Gets or sets information about the sick person.
/// [Angaben zum Patienten]
/// </summary>
public string Patient { get; set; }
......@@ -90,7 +89,7 @@ namespace AMWD.Net.Api.Divera.Requests
/// Gets or sets a value indicating whether a destination is defined.
/// [Gibt es ein Fahrtziel?]
/// </summary>
public bool Destination { get; set; }
public bool HasDestination { get; set; }
/// <summary>
/// Gets or sets the destination address.
......@@ -177,7 +176,7 @@ namespace AMWD.Net.Api.Divera.Requests
/// Gets or sets a value indicating whether to create a news instead of an alarm.
/// [Mitteilung statt einer Alarmierung erstellen]
/// </summary>
public bool News { get; set; }
public bool IsNews { get; set; }
/// <summary>
/// Gets or sets the ID of an already existing alarm.
......
namespace AMWD.Net.Api.Divera.Requests
{
/// <summary>
/// The request model to mark a dataset (alarm, message, appointment) as viewed/read.
/// </summary>
public class MarkViewedRequest
{
/// <summary>
/// Gets or sets the id of the dataset.
/// [ID des Datensatzes]
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the type of the dataset.
/// [Typ des Datensatzes (alarm/news/event)]
/// </summary>
public int Type { get; set; }
}
}
namespace AMWD.Net.Api.Divera.Requests
{
/// <summary>
/// The request model to create a status confirm on an alarm.
/// </summary>
public class SetAlarmConfirmationRequest
{
/// <summary>
/// The alarm id.
/// [ID des Einsatzes]
/// </summary>
public string AlarmId { get; set; }
/// <summary>
/// The status id.
/// [ID des Status, der gesetzt werden soll]
/// </summary>
public string StatusId { get; set; }
/// <summary>
/// A response note.
/// [Freitext Rückmeldung]
/// </summary>
public string Note { get; set; }
}
}
using System;
using System.Collections.Generic;
using AMWD.Net.Api.Divera.Utils;
using AMWD.Net.Api.Divera.Enums;
using Newtonsoft.Json;
namespace AMWD.Net.Api.Divera.Responses
......@@ -62,7 +62,7 @@ namespace AMWD.Net.Api.Divera.Responses
/// [Priorität/Sonderrechte]
/// </summary>
[JsonProperty("priority")]
public bool IsPriority { get; set; }
public bool HasPriority { get; set; }
/// <summary>
/// Gets or sets the title.
......@@ -114,7 +114,7 @@ namespace AMWD.Net.Api.Divera.Responses
public string Caller { get; set; }
/// <summary>
/// Gets or sets the patient.
/// Gets or sets the sick person.
/// [Angaben zum Patienten]
/// </summary>
[JsonProperty("patient")]
......@@ -237,21 +237,21 @@ namespace AMWD.Net.Api.Divera.Responses
/// [Nur Personen alarmieren, die sich auf einem zugeteilten Fahrzeuge befinden]
/// </summary>
[JsonProperty("notification_filter_vehicle")]
public bool IsNotificationFilterVehicle { get; set; }
public bool HasNotificationFilterVehicle { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to alarm persons with a specific state.
/// [Nur Personen alarmieren, die sich in einem bestimmten Status befinden]
/// </summary>
[JsonProperty("notification_filter_status")]
public bool IsNotificationFilterStatus { get; set; }
public bool HasNotificationFilterStatus { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to display this alarm for other persons than only alarmed.
/// [Einsatz auch für nicht benachrichtigte Empfänger sichtbar machen]
/// </summary>
[JsonProperty("notification_filter_access")]
public bool IsNotificationFilterAccess { get; set; }
public bool HasNotificationFilterAccess { get; set; }
/// <summary>
/// Gets or sets the notification filter status ids.
......@@ -477,5 +477,12 @@ namespace AMWD.Net.Api.Divera.Responses
get => unixTime.AddSeconds(UpdateTimestampUnix);
set { UpdateTimestampUnix = (int)value.Subtract(unixTime).TotalSeconds; }
}
/// <summary>
/// Deprecated, not used anymore. Use <see cref="HasNotificationFilterAccess"/> instead.
/// </summary>
[Obsolete("Use '" + nameof(HasNotificationFilterAccess) + "' instead")]
[JsonProperty("notification_filter_status_access")]
public bool IsNotificationFilterStatusAccess { get; set; }
}
}
......@@ -11,6 +11,6 @@ namespace AMWD.Net.Api.Divera.Responses
/// Gets or sets a value indicating whether request was successful.
/// </summary>
[JsonProperty("success")]
public bool Success { get; set; }
public bool IsSuccessful { get; set; }
}
}
using System.Linq;
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using AMWD.Net.Api.Divera.Requests;
......@@ -9,9 +10,21 @@ namespace AMWD.Net.Api.Divera.Utils
{
internal static class DiveraApiRequestConverter
{
private static readonly DateTime unixTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
private static readonly string jsonMimeType = "application/json";
public static JObject ConvertToJObject(this CreateAlarmRequest request)
#region Create alarm
public static HttpContent ConvertToHttpContent(this CreateAlarmRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request), "The request is missing");
var contentObject = request.ConvertToJObject();
return new StringContent(JsonConvert.SerializeObject(contentObject), Encoding.UTF8, jsonMimeType);
}
private static JObject ConvertToJObject(this CreateAlarmRequest request)
{
var jObj = new JObject();
......@@ -21,7 +34,7 @@ namespace AMWD.Net.Api.Divera.Utils
if (!string.IsNullOrWhiteSpace(request.Title))
jObj["title"] = request.Title.Trim();
jObj["priority"] = request.Priority ? 1 : 0;
jObj["priority"] = request.HasPriority ? 1 : 0;
if (!string.IsNullOrWhiteSpace(request.Text))
jObj["text"] = request.Text.Trim();
......@@ -50,7 +63,7 @@ namespace AMWD.Net.Api.Divera.Utils
if (!string.IsNullOrWhiteSpace(request.Units))
jObj["units"] = request.Units.Trim();
jObj["destination"] = request.Destination ? 1 : 0;
jObj["destination"] = request.HasDestination ? 1 : 0;
if (!string.IsNullOrWhiteSpace(request.DestinationAddress))
jObj["destination_address"] = request.DestinationAddress.Trim();
......@@ -91,7 +104,7 @@ namespace AMWD.Net.Api.Divera.Utils
if (request.CoordinatesY.HasValue)
jObj["coordinates-y"] = request.CoordinatesY.Value;
jObj["news"] = request.News ? 1 : 0;
jObj["news"] = request.IsNews ? 1 : 0;
if (request.Id.HasValue)
jObj["id"] = request.Id.Value;
......@@ -114,10 +127,101 @@ namespace AMWD.Net.Api.Divera.Utils
return jObj;
}
public static HttpContent ConvertToHttpContent(this CreateAlarmRequest request)
#endregion Create alarm
#region Set alarm confirmation
public static HttpContent ConvertToHttpContent(this SetAlarmConfirmationRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request), "The request is missing");
var contentObject = request.ConvertToJObject();
return new StringContent(JsonConvert.SerializeObject(contentObject), Encoding.UTF8, jsonMimeType);
}
private static JObject ConvertToJObject(this SetAlarmConfirmationRequest request)
{
if (string.IsNullOrWhiteSpace(request.AlarmId))
throw new ArgumentNullException(nameof(request.AlarmId), "The alarm id is required");
if (string.IsNullOrWhiteSpace(request.StatusId))
throw new ArgumentNullException(nameof(request.StatusId), "The status id is required");
var jObj = new JObject
{
["alarm_id"] = request.AlarmId.Trim(),
["status_id"] = request.StatusId.Trim()
};
if (!string.IsNullOrWhiteSpace(request.Note))
jObj["note"] = request.Note.Trim();
return null;
}
#endregion Set alarm confirmation
#region Close alarm
public static HttpContent ConvertToHttpContent(this CloseAlarmRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request), "The request is missing");
var contentObject = request.ConvertToJObject();
return new StringContent(JsonConvert.SerializeObject(contentObject), Encoding.UTF8, jsonMimeType);
}
private static JObject ConvertToJObject(this CloseAlarmRequest request)
{
var jObj = new JObject
{
["id"] = request.Id
};
if (request.IsClosed.HasValue)
jObj["closed"] = request.IsClosed.Value ? 1 : 0;
if (!string.IsNullOrWhiteSpace(request.Report))
jObj["report"] = request.Report.Trim();
if (request.Timestamp.HasValue)
{
var ts = request.Timestamp.Value.Kind == DateTimeKind.Utc
? request.Timestamp.Value
: request.Timestamp.Value.ToUniversalTime();
jObj["ts"] = (int)ts.Subtract(unixTime).TotalSeconds;
}
return jObj;
}
#endregion Close alarm
#region Mark viewed
public static HttpContent ConvertToHttpContent(this MarkViewedRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request), "The request is missing");
var contentObject = request.ConvertToJObject();
return new StringContent(JsonConvert.SerializeObject(contentObject), Encoding.UTF8, jsonMimeType);
}
private static JObject ConvertToJObject(this MarkViewedRequest request)
{
var jObj = new JObject
{