Skip to content

Commit

Permalink
Merge pull request #202 from OpenBracketsCH/development
Browse files Browse the repository at this point in the history
Fix #163, #199
  • Loading branch information
elektrolytmangel authored Dec 2, 2024
2 parents 798fbad + d9fb67c commit 25a7cb5
Show file tree
Hide file tree
Showing 43 changed files with 517 additions and 140 deletions.
6 changes: 3 additions & 3 deletions app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Defikarte.ch",
"slug": "Defikarte-ch",
"owner": "defikarte",
"version": "1.0.44",
"version": "1.0.45",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
Expand All @@ -16,14 +16,14 @@
"ios": {
"supportsTablet": true,
"bundleIdentifier": "ch.defikarte.app",
"buildNumber": "1.0.44",
"buildNumber": "1.0.45",
"infoPlist": {
"NSLocationWhenInUseUsageDescription": true
}
},
"android": {
"package": "ch.defikarte.app",
"versionCode": 44,
"versionCode": 45,
"permissions": ["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"],
"config": {
"googleMaps": {
Expand Down
2 changes: 0 additions & 2 deletions app/src/config/createForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ const openingHoursErrorsAndWarnings = (value) => {
msg = 'error_openinghours';
}

console.log(msg);

return msg === '' || value === null || value === '' ? true : msg;
};

Expand Down
25 changes: 14 additions & 11 deletions app/src/context/DefibrillatorContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const getDefibrillators = (dispatch) => {
const response = await defikarteBackend.get('/defibrillator');
dispatch({ type: 'update_all', payload: response.data });
} catch (error) {
dispatch({ type: 'update_error', payload: 'Defibrillatoren konnten nicht geladen werden.' });
dispatch({ type: 'update_error', payload: { code: 'error_aed_not_loaded', additionalMessage: '' } });
} finally {
dispatch({ type: 'update_loading', payload: false });
}
Expand Down Expand Up @@ -77,23 +77,26 @@ const addDefibrillator = (dispatch) => {
if (callback) {
callback();
}

dispatch({ type: 'update_error', payload: { code: '', additionalMessage: '' } });
} catch (error) {
console.log(error.message);
dispatch({ type: 'update_error', payload: 'Defibrillator konnte nicht hinzugefügt werden.' });
let errorMessage = '';
if (error.response?.data) {
errorMessage = error.response.data.map((x) => `${x.field}: ${x.error}`).join('\r\n');
}

dispatch({
type: 'update_error',
payload: { code: 'error_aed_create_failed', additionalMessage: errorMessage },
});
} finally {
dispatch({ type: 'update_creating', payload: false });
}
};
};

const resetError = (dispatch) => {
return () => {
dispatch({ type: 'update_error', payload: '' });
};
};

export const { Context, Provider } = createDataContext(
reducer,
{ getDefibrillators, addDefibrillator, setDefisNearLocation, resetError },
{ defibrillators: [], defisNearLocation: [], loading: false, creating: false, error: '' }
{ getDefibrillators, addDefibrillator, setDefisNearLocation },
{ defibrillators: [], defisNearLocation: [], loading: false, creating: false, error: { additionalMessage: '', code: '' } }
);
16 changes: 16 additions & 0 deletions app/src/helpers/stringHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const trimStringValues = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

const result = {};
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') {
result[key] = obj[key].trim();
} else {
result[key] = trimStringValues(obj[key]);
}
});

return result;
};
8 changes: 8 additions & 0 deletions app/src/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const resources = {
access_info_titel: 'What is meant by "accessible"?',
access_placeholder: 'Is the defibrillator accessible to the public?',
access: 'Accessible',
error_aed_create_failed: 'Error creating defibrillator.',
error_aed_not_loaded: 'Error loading defibrillators.',
error_default: 'Value is invalid',
error_description: 'The maximum length is 200 characters',
error_level: 'The value must be a number',
Expand Down Expand Up @@ -110,6 +112,8 @@ const resources = {
access_info_titel: 'Was wird unter "Zugänglich" verstanden?',
access_placeholder: 'Ist der Defibrillator öffentlich?',
access: 'Zugänglich',
error_aed_create_failed: 'Defibrillator konnte nicht erstellt werden.',
error_aed_not_loaded: 'Defibrillatoren konnten nicht geladen werden.',
error_default: 'Wert ist ungültig',
error_description: 'Die maximale Länge beträgt 200 Zeichen',
error_level: 'Der Wert muss eine Nummer sein',
Expand Down Expand Up @@ -189,6 +193,8 @@ const resources = {
access_info_titel: 'Que signifie "accessible"?',
access_placeholder: 'Le défibrillateur est-il accessible au public?',
access: 'Accessible',
error_aed_create_failed: 'Erreur lors de la création du défibrillateur.',
error_aed_not_loaded: 'Erreur lors du chargement des défibrillateurs.',
error_default: 'La valeur est invalide',
error_description: 'La longueur maximale est de 200 caractères',
error_level: 'La valeur doit être un nombre',
Expand Down Expand Up @@ -268,6 +274,8 @@ const resources = {
access_info_titel: 'Cosa si intende per "accessibile"?',
access_placeholder: 'Il defibrillatore è accessibile al pubblico?',
access: 'Accessibile',
error_aed_create_failed: 'Errore nella creazione del defibrillatore.',
error_aed_not_loaded: 'Errore nel caricamento dei defibrillatori.',
error_default: 'Il valore non è valido',
error_description: 'La lunghezza massima è di 200 caratteri',
error_level: 'Il valore deve essere un numero',
Expand Down
35 changes: 14 additions & 21 deletions app/src/screens/CreateScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,28 @@ import SwitchForm from '../components/SwitchForm';
import TextForm from '../components/TextForm';
import createForm from '../config/createForm';
import { Context as DefibrillatorContext } from '../context/DefibrillatorContext';
import { trimStringValues } from '../helpers/stringHelpers';

const CreateScreen = ({ navigation }) => {
const insets = useSafeAreaInsets();
const { state: defiState, addDefibrillator, resetError } = useContext(DefibrillatorContext);
const { state: defiState, addDefibrillator } = useContext(DefibrillatorContext);
const [state, setState] = useState({ latitude: 0, longitude: 0, emergencyPhone: '144' });
const [isSubmitted, setIsSubmitted] = useState(false);
const [localError, setLocalError] = useState('');
const {
control,
handleSubmit,
formState: { errors },
} = useForm();

const onSubmit = (formValues) => {
setState({ ...state, ...formValues, indoor: formValues.indoor ? 'yes' : 'no' });
setIsSubmitted(true);
};

const add = async () => {
await addDefibrillator(state, () => navigation.navigate('Main'));
const onSubmit = async (formValues) => {
try {
const completeData = { ...state, ...formValues, indoor: formValues.indoor ? 'yes' : 'no' };
const formData = trimStringValues(completeData);
await addDefibrillator(formData, () => navigation.navigate('Main'));
} catch (error) {
// should never occure
setLocalError(error.message);
}
};

useEffect(() => {
Expand All @@ -37,17 +40,6 @@ const CreateScreen = ({ navigation }) => {
}
}, []);

useEffect(() => {
if (isSubmitted) {
add();
setIsSubmitted(false);
} else {
resetError();
}
}, [isSubmitted]);

useEffect(() => {}, [state]);

const renderFormComponent = () => {
return createForm.map((formComp, index) => {
if (formComp.type === 'Text') {
Expand Down Expand Up @@ -127,7 +119,8 @@ const CreateScreen = ({ navigation }) => {
>
<ScrollView keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false}>
{renderFormComponent()}
<Text style={styles.errorTextStyle}>{defiState.error}</Text>
<Text style={styles.errorTextStyle}>{t(defiState.error.code) || localError}</Text>
<Text style={styles.errorTextStyle}>{t(defiState.error.additionalMessage)}</Text>
</ScrollView>
</KeyboardAvoidingView>
<View style={bottomBar}>
Expand Down
42 changes: 42 additions & 0 deletions backend/DefikarteBackend.Tests/DefikarteBackend.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<None Remove="Resources\swissboundaries3d_2024-01_4326.geojson" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Resources\swissboundaries3d_2024-01_4326.geojson">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DefikarteBackend\DefikarteBackend.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

</Project>
81 changes: 81 additions & 0 deletions backend/DefikarteBackend.Tests/LocalisationServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using DefikarteBackend.Interfaces;
using DefikarteBackend.Services;
using Microsoft.Extensions.Logging;
using Moq;
using NetTopologySuite;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Implementation;
using System.Reflection;

namespace DefikarteBackend.Tests
{
[TestFixture]
public class LocalisationServiceTests
{
private Mock<IBlobStorageDataRepository> _blobRepositoryMock;
private Mock<IServiceConfiguration> _configuratonMock;
private Mock<ILogger> _loggerMock;

[SetUp]
public void Setup()
{
NtsGeometryServices.Instance = new NtsGeometryServices(
CoordinateArraySequenceFactory.Instance,
new PrecisionModel(1000d),
4326,
GeometryOverlay.NG,
new CoordinateEqualityComparer());

_blobRepositoryMock = new Mock<IBlobStorageDataRepository>();
var geoJson = GetFileContents("swissboundaries3d_2024-01_4326.geojson");
_blobRepositoryMock.Setup(x => x.ReadAsync(It.IsAny<string>())).ReturnsAsync(geoJson);

_configuratonMock = new Mock<IServiceConfiguration>();
_configuratonMock.Setup(Setup => Setup.BlobStorageSwissBoundariesName).Returns("swissboundaries3d_2024-01_4326.geojson");

_loggerMock = new Mock<ILogger>();
}

[Test]
public async Task IsSwitzerlandAsync_GivenCooridnatesInSwitzerland_ShouldReturnTrueAsync()
{
// Arrange
var localisationService = new LocalisationService(NtsGeometryServices.Instance, _blobRepositoryMock.Object, _configuratonMock.Object, _loggerMock.Object);

// Act
var result = await localisationService.IsSwitzerlandAsync(47.0, 7.0).ConfigureAwait(false);

// Assert
Assert.That(result, Is.True);
}

[Test]
public async Task IsSwitzerlandAsync_GivenCooridnatesOutsideSwitzerland_ShouldReturnFalseAsync()
{
// Arrange
var localisationService = new LocalisationService(NtsGeometryServices.Instance, _blobRepositoryMock.Object, _configuratonMock.Object, _loggerMock.Object);

// Act
var result = await localisationService.IsSwitzerlandAsync(47.73921, 8.04199).ConfigureAwait(false);

// Assert
Assert.That(result, Is.False);
}

private string GetFileContents(string resourceFile)
{
var asm = Assembly.GetExecutingAssembly();
var resource = string.Format("DefikarteBackend.Tests.Resources.{0}", resourceFile);
using (var stream = asm.GetManifestResourceStream(resource))
{
if (stream != null)
{
var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}

return string.Empty;
}
}
}

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions backend/DefikarteBackend.sln
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
# Visual Studio Version 17
VisualStudioVersion = 17.11.35327.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DefikarteBackend", "DefikarteBackend.csproj", "{5391DED1-23DC-47A1-9DDA-485DB0396FB6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DefikarteBackend", ".\DefikarteBackend\DefikarteBackend.csproj", "{5391DED1-23DC-47A1-9DDA-485DB0396FB6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DefikarteBackend.Tests", ".\DefikarteBackend.Tests\DefikarteBackend.Tests.csproj", "{9E45FF69-D535-4679-BBD6-844BE39CE8F1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,6 +17,10 @@ Global
{5391DED1-23DC-47A1-9DDA-485DB0396FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5391DED1-23DC-47A1-9DDA-485DB0396FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5391DED1-23DC-47A1-9DDA-485DB0396FB6}.Release|Any CPU.Build.0 = Release|Any CPU
{9E45FF69-D535-4679-BBD6-844BE39CE8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E45FF69-D535-4679-BBD6-844BE39CE8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E45FF69-D535-4679-BBD6-844BE39CE8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E45FF69-D535-4679-BBD6-844BE39CE8F1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
using Azure.Storage.Blobs;
using DefikarteBackend.Interfaces;
using DefikarteBackend.Model;
using DefikarteBackend.Repository;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;

namespace DefikarteBackend.Cache
{
public class BlobStorageCacheRepository : IBlobStorageCacheRepository, ICacheRepository<OsmNode>
public class BlobStorageCacheRepository : BlobStorageDataRepository, IBlobStorageCacheRepository, ICacheRepository<OsmNode>
{
private readonly BlobContainerClient _containerClient;
private readonly string _blobName;

public BlobStorageCacheRepository(BlobContainerClient containerClient, string blobName)
: base(containerClient)
{
_containerClient = containerClient;
_blobName = blobName;
}

public async Task CreateAsync(string jsonData, string blobName)
{
BlobClient blobClient = _containerClient.GetBlobClient(blobName);
await blobClient.UploadAsync(BinaryData.FromString(jsonData));
}

public async Task<string> ReadAsync(string blobName)
{
BlobClient blobClient = _containerClient.GetBlobClient(blobName);
var response = await blobClient.DownloadContentAsync();
var content = response.Value.Content;
return Encoding.UTF8.GetString(content);
}

public async Task UpdateAsync(string jsonData, string blobName)
{
BlobClient blobClient = _containerClient.GetBlobClient(blobName);
Expand Down
Loading

0 comments on commit 25a7cb5

Please sign in to comment.