diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1ade888129707fa0cd8bd18bc1c0ba23ac113ce2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +## Ignore .openapi-generator Folder +## +.openapi-generator/ +server/src/Org.OpenAPITools/.gitignore + +# Visual Studio cache/options directory +.vs/ + +# Visual Studio solution +*.sln + +# build-jobs +build.* + +# database data directory +server/data/ + +# database directory +server/programs/MongoDB/ +!server/programs/MongoDB/kill_MongoDB.bat +!server/programs/MongoDB/startMongoDB.bat +!server/programs/MongoDB/readme.md + +#generated readme +server/README.md + +# all generated directories +Attributes/ +Authentication/ +Controllers/ +Converters/ +Filters/ +Formatters/ +Models/ +OpenAPI/ +Properties/ +wwwroot/ + +# generated project-files +*.csproj + +# generated Program.cs +Program.cs + +# backup-files +*.bak diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..120fcda39cfee4122d0a409175f301b4a5405dda --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "arf005"] + path = arf005 + url = git@forge.etsi.org:arf/arf005.git + branch = develop \ No newline at end of file diff --git a/arf005 b/arf005 new file mode 160000 index 0000000000000000000000000000000000000000..533b9d3198b772c7b628b0ab0e0a144b89966b46 --- /dev/null +++ b/arf005 @@ -0,0 +1 @@ +Subproject commit 533b9d3198b772c7b628b0ab0e0a144b89966b46 diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 0000000000000000000000000000000000000000..3b40e47a45fd988f38e0b885958ceb2cac4a6201 --- /dev/null +++ b/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "5.3.0" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..bc84c31f94b24bf5d6c345bfe0c5b9b1505bf356 --- /dev/null +++ b/readme.md @@ -0,0 +1,128 @@ +# Description of version 1.0.0 + +This project should be used to construct a complete ASP-Net REST server compliant to the ARF World Storage API. It uses auto-generated ASP.NET server code. We propose to use the open source OpenAPI-Generator for this. + +It includes description and code for a fully functional server with MongoDB integration. + +# Prerequisites +What you need: + +1. Installed npm: https://phoenixnap.com/kb/install-node-js-npm-on-windows +2. Installed openapi generator with npm: https://openapi-generator.tech/docs/installation/ +3. Installed docker (if you want to use it): https://www.docker.com/get-started + +# Generate or update the server + +We provided the file `.openapi-generator-ignore` in `server`, which prevents openapi-generator to override some adapted files. + +## Auto-generate server code +Open a command shell and execute: +``` + openapi-generator-cli generate -i arf005\API\openapi.yaml -g aspnetcore -o server +``` + +Open the solution `Org.OpenAPITools.sln` (folder `server`) in Visual Studio: + +## In Visual Studio: +Open `NuGet Package Manager` and add `MongoDB.Driver`. + +### File adaptations: +Change version number in all files if a new version is provided. + +### In the folder `Controllers`: +Change "`public class`" to "`public abstract class`". + +Compare files folder in "`ControllersImpl`" with the corresponding files in "`Controllers`" and adapt if necessary. + +Methods should be the same with "`override`" instead of "`virtual`". + +--- + +#### - if files are missing (and only then): +Copy them from folder `Controllers`, rename them (append `Impl`) and handle them like the already existing files, i.e.: +Change classnames by appending `Impl` to the original classnames (and change filenames accordingly) and inherit from original class in `Controllers` (instead of `ControllerBase`) + +..and replace `virtual` by `override` with all methods. + +Add +``` + using Org.OpenAPITools.Services; + using MongoDB.Driver; +``` + +Add a private readonly service class variable like in the already existing files. + +Add a constructor with this service class variable like in the already existing files. + +Remove sample code and replace it by using the appropriate methods of the corresponding classes in the folder `Services` (which you may be have to create). + +--- + +### In the folder `Models`: +Add to the classes to be stored in the database (i.e. `Trackable.cs`, `WorldAnchor.cs`, `WorldLink.cs`) : +``` + using MongoDB.Bson; + using MongoDB.Bson.Serialization.Attributes; +``` + +and at the value that is to become the MongoDB ID, add: +``` + [BsonId] + [BsonRepresentation(BsonType.String)] +``` + +### Folder `Services` +The folder `Services` should contain one common class with the DatabaseSettings (`DatabaseSettings.cs`) and one with the database-access-methods (create, get, update, remove) for each API. If some are missing create them like the ones you find there. Be aware to add the reference to these in the file `startup.cs` in this case. + +The naming in the DatabaseSettings is the same as defined in `appsettings.json`, which you have to extend when creating new classes in this folder. Change `appsettings.json` in the folder `docker` accordingly. Make sure that the ConnectionString for the database contains the correct IP address as specified in `docker-compose.yml`. + +### In the folder `wwwroot` +Add in `openapi-original.json` in section `servers` the urls of the servers you want to use with swagger-ui + +# MongoDB +If you don't have a MongoDB, follow the instructions in `readme.md` in `server/programs/MongoDB` + +...and put MongoDB in folder `server/programs/MongoDB` (download MongoDB as zip-file from https://www.mongodb.com/try/download/community and unzip the file into this directory, so that the bin-directory is in this folder). + + +# Use in Visual Studio +Make sure, that an instance of MongoDB is running. + +Start application with IIS Express. + + +# Use within a Docker +Remove the substring `src/Org.OpenAPITools/` in Dockerfile (if not already done) + +open a command shell and generate docker by executing in `server/src/Org.OpenAPITools`: +``` + docker build -t org.openapitools . +``` + +## How to start: +The easiest way is to use docker-compose: + +Open a command shell and use docker-compose (if necessary adapt docker-compose.yml) by executing in `server/src/Org.OpenAPITools`: +``` + docker-compose up --force-recreate --remove-orphan --detach +``` + +Open http://localhost:8080/openapi/index.html in a web-browser, if you want to check the functionalities using SwaggerUI + +## How to stop: +Open a command shell by executing in `server/src/Org.OpenAPITools`: +``` + docker-compose down +``` + +## How to dump database +Execute the following command in docker: +``` + mongodump --db **insert database_name** --out /data-dump/`date +"%Y-%m-%d"` +``` + +## How to import database: +Execute the following command in docker: +``` + mongorestore --db **insert database_name** **insert path_to_bson_file** +``` \ No newline at end of file diff --git a/server/.openapi-generator-ignore b/server/.openapi-generator-ignore new file mode 100644 index 0000000000000000000000000000000000000000..cbd23cf64baafbfa18079cf451ee244ab18af239 --- /dev/null +++ b/server/.openapi-generator-ignore @@ -0,0 +1,37 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + + +**/Startup.cs +**/appsettings.json +**/Dockerfile +**/docker-compose.yml +**/.openapi-generator-ignore + + +# ARF +# Implementation of REST request and database functionalities +# +**/ControllersImpl +**/Services diff --git a/server/programs/MongoDB/kill_MongoDB.bat b/server/programs/MongoDB/kill_MongoDB.bat new file mode 100644 index 0000000000000000000000000000000000000000..8aae66b413c380ef488b0f50cd36d8115538ff6e --- /dev/null +++ b/server/programs/MongoDB/kill_MongoDB.bat @@ -0,0 +1 @@ +taskkill -im mongod.exe \ No newline at end of file diff --git a/server/programs/MongoDB/readme.md b/server/programs/MongoDB/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..b93d699990880de497daa38a66021f5813a7f494 --- /dev/null +++ b/server/programs/MongoDB/readme.md @@ -0,0 +1,4 @@ +# Installation of MongoDB +1. download MongoDB as zip-file from https://www.mongodb.com/try/download/community +2. unzip the file into this directory, so that the bin-directory is in this folder (where you found this readme.md) +3. before starting `startMongoDB.bat` create data-directory and adjust - if necessary - data path in startMongoDB.bat \ No newline at end of file diff --git a/server/programs/MongoDB/startMongoDB.bat b/server/programs/MongoDB/startMongoDB.bat new file mode 100644 index 0000000000000000000000000000000000000000..1202cf18b83cebfab758a08cab5853c3703a29cd --- /dev/null +++ b/server/programs/MongoDB/startMongoDB.bat @@ -0,0 +1,3 @@ +cd bin +call "cmd /c start mongod.exe --dbpath ../../../data" +cd .. \ No newline at end of file diff --git a/server/src/Org.OpenAPITools/ControllersImpl/DefaultApiImpl.cs b/server/src/Org.OpenAPITools/ControllersImpl/DefaultApiImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..b3d314ff0f22c2f1804ed5d050e20d151cd512c1 --- /dev/null +++ b/server/src/Org.OpenAPITools/ControllersImpl/DefaultApiImpl.cs @@ -0,0 +1,102 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +/* + * World Storage API + * + * API ensuring interoperability between an authoring tool and a World Storage service + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using Org.OpenAPITools.Attributes; +using Org.OpenAPITools.Models; +using Microsoft.OpenApi.Models; + +namespace Org.OpenAPITools.Controllers +{ + /// + /// + /// + [ApiController] + public class DefaultApiControllerImpl : DefaultApiController + { + + /// + /// Get the state of the server. + /// + /// OK, world storage server ready. + [HttpGet] + [Route("/admin")] + [ValidateModelState] + [SwaggerOperation("GetAdmin")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, world storage server ready.")] + public override IActionResult GetAdmin() + { + string version = "OK world storage server ready"; + return new ObjectResult(version); + //return StatusCode(200, new ObjectResult(version)); + } + + /// + /// Test the server availability. + /// + /// Ok, returns a string message. + [HttpGet] + [Route("/ping")] + [ValidateModelState] + [SwaggerOperation("GetPing")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "Ok, returns a string message.")] + public override IActionResult GetPing() + { + string answer = "OK, world storage alive."; + return new ObjectResult(answer); + // return StatusCode(200, new ObjectResult(answer)); + } + + /// + /// Get the version of the ARF API. + /// + /// Current version. + [HttpGet] + [Route("/version")] + [ValidateModelState] + [SwaggerOperation("GetVersion")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "Current version.")] + public override IActionResult GetVersion() + { + string version = "1.0.0"; + return new ObjectResult(version); + //return StatusCode(200, new ObjectResult(version)); + } + + } +} diff --git a/server/src/Org.OpenAPITools/ControllersImpl/TrackablesApiImpl.cs b/server/src/Org.OpenAPITools/ControllersImpl/TrackablesApiImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..39ded8d709184b320509977b0372846c83db7979 --- /dev/null +++ b/server/src/Org.OpenAPITools/ControllersImpl/TrackablesApiImpl.cs @@ -0,0 +1,220 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +/* + * World Storage API + * + * API ensuring interoperability between an authoring tool and a World Storage service + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using Org.OpenAPITools.Attributes; +using Org.OpenAPITools.Models; +using Org.OpenAPITools.Services; +using MongoDB.Driver; + +namespace Org.OpenAPITools.Controllers +{ + /// + /// + /// + [ApiController] + public class TrackablesApiControllerImpl : TrackablesApiController + { + + private readonly TrackableService _trackableService; + + /// + /// + /// + public TrackablesApiControllerImpl(TrackableService trackableService) + { + _trackableService = trackableService; + } + + + /// + /// Create a Trackable. + /// + /// Create a new Trackable from a json object containing all the required informations and add it to the world storage. <br>As a result you will get the ID of the newly created Trackable. + /// The Trackable to be added to the world storage. + /// OK, return the UUID of the Trackable defined by the world storage. + /// Null response. + /// Bad request. + /// Invalid UUID, id must be a Nil value. + /// Unexpected error. + [HttpPost] + [Route("/trackables")] + [Consumes("application/json")] + [ValidateModelState] + [SwaggerOperation("AddTrackable")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, return the UUID of the Trackable defined by the world storage.")] + [SwaggerResponse(statusCode: 201, type: typeof(string), description: "Null response.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Bad request.")] + [SwaggerResponse(statusCode: 409, type: typeof(string), description: "Invalid UUID, id must be a Nil value.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult AddTrackable([FromBody] Trackable trackable) + { + if (String.IsNullOrEmpty(trackable.UUID.ToString())) + { + trackable.UUID = Guid.NewGuid(); + } + try + { + Trackable mytrackable = _trackableService.Create(trackable); + return StatusCode(200, mytrackable.UUID.ToString()); + } + catch (Exception e) + { + return StatusCode(400, e.Message); + } + } + + /// + /// Delete a Trackable. + /// + /// Delete a single Trackable stored in the world storage from its ID. + /// Trackable UUID to delete. + /// OK, delete successful. + /// Invalid UUID supplied. + /// Not found, could not find UUID in database. + [HttpDelete] + [Route("/trackables/{trackableUUID}")] + [ValidateModelState] + [SwaggerOperation("DeleteTrackable")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, delete successful.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Invalid UUID supplied.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + public override IActionResult DeleteTrackable([FromRoute(Name = "trackableUUID")][Required] Guid trackableUUID) + { + DeleteResult answer = _trackableService.Remove(trackableUUID); + // check, if used in WorldLink + string result = "ok"; + string worldlinkinfo = ""; + List worldlinklistfrom = _trackableService.GetWorldLinkUUIDFrom(trackableUUID); + foreach (WorldLink worldlink in worldlinklistfrom) + { + worldlinkinfo += worldlink.UUID.ToString() + "; "; + worldlink.UUIDFrom = Guid.Empty; + worldlink.TypeFrom = ObjectType.NotIdentifiedEnum; + _trackableService.UpdateWorldLink(worldlink.UUID, worldlink); + } + List worldlinklistto = _trackableService.GetWorldLinkUUIDTo(trackableUUID); + foreach (WorldLink worldlink in worldlinklistto) + { + worldlinkinfo += worldlink.UUID.ToString() + "; "; + worldlink.UUIDTo = Guid.Empty; + worldlink.TypeTo = ObjectType.NotIdentifiedEnum; + _trackableService.UpdateWorldLink(worldlink.UUID, worldlink); + } + if (worldlinkinfo.Length > 1) + { + result += ", removed object was referenced in " + worldlinkinfo + " and removed there as well"; + } + return (answer.IsAcknowledged && answer.DeletedCount > 0) ? new ObjectResult(result) : StatusCode(404, "Not found, could not find UUID in database."); + } + + /// + /// Find a Trackable by its UUID. + /// + /// Get a single Trackable stored in the world storage from its ID. + /// UUID of the Trackable to retrieve. + /// Successful operation. + /// Invalid UUID supplied. + /// Not found, could not find UUID in database. + [HttpGet] + [Route("/trackables/{trackableUUID}")] + [ValidateModelState] + [SwaggerOperation("GetTrackableById")] + [SwaggerResponse(statusCode: 200, type: typeof(Trackable), description: "Successful operation.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Invalid UUID supplied.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + public override IActionResult GetTrackableById([FromRoute(Name = "trackableUUID")][Required] Guid trackableUUID) + { + Trackable trackable = _trackableService.Get(trackableUUID); + return (null != trackable) ? new ObjectResult(trackable) : StatusCode(404, "Not found, could not find UUID in database."); + } + + /// + /// Return all the Trackables. + /// + /// Get all the Trackables currently being stored in the world storage. + /// OK, return all the Trackables defined by the world storage. + /// Null response. + /// Unexpected error. + [HttpGet] + [Route("/trackables")] + [ValidateModelState] + [SwaggerOperation("GetTrackables")] + [SwaggerResponse(statusCode: 200, type: typeof(List), description: "OK, return all the Trackables defined by the world storage.")] + [SwaggerResponse(statusCode: 201, type: typeof(string), description: "Null response.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult GetTrackables() + { + List trackablelist = _trackableService.Get(); + return new ObjectResult(trackablelist); + } + + + + /// + /// Modify a Trackable. + /// + /// Modify an existing Trackable given a json object containing all the required informations. <br> **Please note that ID of the object is required in the JSON** + /// The Trackable to be modified in the world storage. + /// OK, return the UUID of the modified Trackable. + /// Bad request. + /// Not found, could not find UUID in database. + /// Unexpected error. + [HttpPut] + [Route("/trackables")] + [Consumes("application/json")] + [ValidateModelState] + [SwaggerOperation("ModifyTrackable")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, return the UUID of the modified Trackable.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Bad request.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult ModifyTrackable([FromBody] Trackable trackable) + { + ReplaceOneResult result = _trackableService.Update(trackable.UUID, trackable); + if (result.MatchedCount == 0) + { + return StatusCode(404, "Not found, could not find UUID in database."); + } + else + { + return StatusCode(200, trackable.UUID.ToString()); + } + } + } +} diff --git a/server/src/Org.OpenAPITools/ControllersImpl/WorldAnchorsApiImpl.cs b/server/src/Org.OpenAPITools/ControllersImpl/WorldAnchorsApiImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..9bb4100ea89d2d443e1234692ceebda2d78de752 --- /dev/null +++ b/server/src/Org.OpenAPITools/ControllersImpl/WorldAnchorsApiImpl.cs @@ -0,0 +1,222 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +/* + * World Storage API + * + * API ensuring interoperability between an authoring tool and a World Storage service + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using Org.OpenAPITools.Attributes; +using Org.OpenAPITools.Models; +using Org.OpenAPITools.Services; +using MongoDB.Driver; + +namespace Org.OpenAPITools.Controllers +{ + /// + /// + /// + [ApiController] + public class WorldAnchorsApiControllerImpl : WorldAnchorsApiController + { + + private readonly WorldAnchorService _worldAnchorService; + + /// + /// + /// + public WorldAnchorsApiControllerImpl(WorldAnchorService worldAnchorService) + { + _worldAnchorService = worldAnchorService; + } + + + /// + /// Create a World Anchor. + /// + /// Create a new World Anchor from a json object containing all the required informations and add it to the world storage. <br>As a result you will get the ID of the newly created World Anchor. + /// The World Anchor to be added to the world storage. + /// OK, return the UUID of the World Anchor defined by the world storage. + /// Null response. + /// Bad request. + /// Invalid UUID, id must be a Nil value. + /// Unexpected error. + [HttpPost] + [Route("/worldAnchors")] + [Consumes("application/json")] + [ValidateModelState] + [SwaggerOperation("AddWorldAnchor")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, return the UUID of the World Anchor defined by the world storage.")] + [SwaggerResponse(statusCode: 201, type: typeof(string), description: "Null response.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Bad request.")] + [SwaggerResponse(statusCode: 409, type: typeof(string), description: "Invalid UUID, id must be a Nil value.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult AddWorldAnchor([FromBody] WorldAnchor worldAnchor) + { + if (String.IsNullOrEmpty(worldAnchor.UUID.ToString())) + { + worldAnchor.UUID = Guid.NewGuid(); + } + try + { + WorldAnchor myworldanchor = _worldAnchorService.Create(worldAnchor); + return StatusCode(200, myworldanchor.UUID.ToString()); + } + catch (Exception e) + { + return StatusCode(400, e.Message); + } + } + + /// + /// Delete a World Anchor. + /// + /// Delete a single World Anchor stored in the world storage from its ID. + /// World Anchor UUID to delete. + /// OK, delete successful. + /// Invalid UUID supplied. + /// Not found, could not find UUID in database. + [HttpDelete] + [Route("/worldAnchors/{worldAnchorUUID}")] + [ValidateModelState] + [SwaggerOperation("DeleteWorldAnchor")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, delete successful.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Invalid UUID supplied.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + public override IActionResult DeleteWorldAnchor([FromRoute(Name = "worldAnchorUUID")][Required] Guid worldAnchorUUID) + { + DeleteResult answer = _worldAnchorService.Remove((worldAnchorUUID)); + // check, if used in WorldLink + string result = "ok"; + string worldlinkinfo = ""; + List worldlinklistfrom = _worldAnchorService.GetWorldLinkUUIDFrom(worldAnchorUUID); + foreach (WorldLink worldlink in worldlinklistfrom) + { + worldlinkinfo += worldlink.UUID.ToString() + "; "; + worldlink.UUIDFrom = Guid.Empty; + worldlink.TypeFrom = ObjectType.NotIdentifiedEnum; + _worldAnchorService.UpdateWorldLink(worldlink.UUID, worldlink); + } + List worldlinklistto = _worldAnchorService.GetWorldLinkUUIDTo(worldAnchorUUID); + foreach (WorldLink worldlink in worldlinklistto) + { + worldlinkinfo += worldlink.UUID.ToString() + "; "; + worldlink.UUIDTo = Guid.Empty; + worldlink.TypeTo = ObjectType.NotIdentifiedEnum; + _worldAnchorService.UpdateWorldLink(worldlink.UUID, worldlink); + } + if (worldlinkinfo.Length > 1) + { + result += ", but removed object was referenced in " + worldlinkinfo + " and removed there as well"; + } + return (answer.IsAcknowledged && answer.DeletedCount > 0) ? new ObjectResult(result) : StatusCode(404, "Not found, could not find UUID in database."); + } + + /// + /// Find a World Anchor by its UUID. + /// + /// Get a single World Anchor stored in the world storage from its ID. + /// UUID of the World Anchor to retrieve. + /// Successful operation. + /// Invalid UUID supplied. + /// Not found, could not find UUID in database. + [HttpGet] + [Route("/worldAnchors/{worldAnchorUUID}")] + [ValidateModelState] + [SwaggerOperation("GetWorldAnchorById")] + [SwaggerResponse(statusCode: 200, type: typeof(WorldAnchor), description: "Successful operation.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Invalid UUID supplied.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + public override IActionResult GetWorldAnchorById([FromRoute(Name = "worldAnchorUUID")][Required] Guid worldAnchorUUID) + { + WorldAnchor myworldanchor = _worldAnchorService.Get(worldAnchorUUID); + return (null != myworldanchor) ? new ObjectResult(myworldanchor) : StatusCode(404, "Not found, could not find UUID in database."); + } + + /// + /// Return all the World Anchors. + /// + /// Get all the World Anchors currently being stored in the world storage. + /// OK, return all the World Anchors defined by the world storage. + /// Null response. + /// Unexpected error. + [HttpGet] + [Route("/worldAnchors")] + [ValidateModelState] + [SwaggerOperation("GetWorldAnchors")] + [SwaggerResponse(statusCode: 200, type: typeof(List), description: "OK, return all the World Anchors defined by the world storage.")] + [SwaggerResponse(statusCode: 201, type: typeof(string), description: "Null response.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult GetWorldAnchors() + { + List worldanchorlist = _worldAnchorService.Get(); + return new ObjectResult(worldanchorlist); + } + + + + /// + /// Modify a World Anchor. + /// + /// Modify an existing World Anchor given a json object containing all the required informations. <br> **Please note that ID of the object is required in the JSON** + /// The World Anchor to be modified in the world storage. + /// OK, return the UUID of the modified World Anchor. + /// Bad request. + /// Not found, could not find UUID in database. + /// Unexpected error. + [HttpPut] + [Route("/worldAnchors")] + [Consumes("application/json")] + [ValidateModelState] + [SwaggerOperation("ModifyWorldAnchor")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, return the UUID of the modified World Anchor.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Bad request.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult ModifyWorldAnchor([FromBody] WorldAnchor worldAnchor) + { + ReplaceOneResult result = _worldAnchorService.Update(worldAnchor.UUID, worldAnchor); + if (result.MatchedCount == 0) + { + return StatusCode(404, "Not found, could not find UUID in database."); + } + else + { + return StatusCode(200, worldAnchor.UUID.ToString()); + } + } + + + } +} diff --git a/server/src/Org.OpenAPITools/ControllersImpl/WorldLinksApiImpl.cs b/server/src/Org.OpenAPITools/ControllersImpl/WorldLinksApiImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..0cb2b03232c04e7710169afe697b7b9a994733c3 --- /dev/null +++ b/server/src/Org.OpenAPITools/ControllersImpl/WorldLinksApiImpl.cs @@ -0,0 +1,271 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +/* + * World Storage API + * + * API ensuring interoperability between an authoring tool and a World Storage service + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using Org.OpenAPITools.Attributes; +using Org.OpenAPITools.Models; +using Org.OpenAPITools.Services; +using MongoDB.Driver; + +namespace Org.OpenAPITools.Controllers +{ + /// + /// + /// + [ApiController] + public class WorldLinksApiControllerImpl : WorldLinksApiController + { + + private readonly WorldLinkService _worldLinkService; + + /// + /// + /// + public WorldLinksApiControllerImpl(WorldLinkService worldLinkService) + { + _worldLinkService = worldLinkService; + } + + /// + /// Create a World Link between elements (world anchors and/or trackables). + /// + /// Create a new World Link from a json object containing all the required informations and add it to the world storage. <br>As a result you will get the ID of the newly created World Link. + /// The link to be added to the world storage. + /// OK, return the UUID of the World Link defined by the world storage. + /// Null response. + /// Bad request. + /// Invalid UUID, id must be a Nil value. + /// Unexpected error. + [HttpPost] + [Route("/worldLinks")] + [Consumes("application/json")] + [ValidateModelState] + [SwaggerOperation("AddWorldLink")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, return the UUID of the World Link defined by the world storage.")] + [SwaggerResponse(statusCode: 201, type: typeof(string), description: "Null response.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Bad request.")] + [SwaggerResponse(statusCode: 409, type: typeof(string), description: "Invalid UUID, id must be a Nil value.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult AddWorldLink([FromBody] WorldLink worldLink) + { + if (String.IsNullOrEmpty(worldLink.UUID.ToString())) + { + worldLink.UUID = Guid.NewGuid(); + } + try + { + WorldLink myworldlink = _worldLinkService.Create(worldLink); + return StatusCode(200, myworldlink.UUID.ToString()); + } + catch (Exception e) + { + return StatusCode(400, e.Message); + } + } + + /// + /// Delete a World Link. + /// + /// Delete a single World Link stored in the world storage from its ID. + /// World Link id to delete. + /// OK, delete successful. + /// Invalid UUID supplied. + /// Not found, could not find UUID in database. + [HttpDelete] + [Route("/worldLinks/{worldLinkUUID}")] + [ValidateModelState] + [SwaggerOperation("DeleteWorldLink")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, delete successful.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Invalid UUID supplied.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + public override IActionResult DeleteWorldLink([FromRoute(Name = "worldLinkUUID")][Required] Guid worldLinkUUID) + { + DeleteResult answer = _worldLinkService.Remove((worldLinkUUID)); + return (answer.IsAcknowledged && answer.DeletedCount > 0) ? new ObjectResult("ok") : StatusCode(404, "Not found, could not find UUID in database."); + } + + + /// + /// Find a World Link by its UUID. + /// + /// Get a single World Link stored in the world storage from its ID. + /// UUID of the World Link to retrieve. + /// Successful operation. + /// Invalid UUID supplied. + /// Not found, could not find UUID in database. + [HttpGet] + [Route("/worldLinks/{worldLinkUUID}")] + [ValidateModelState] + [SwaggerOperation("GetWorldLinkById")] + [SwaggerResponse(statusCode: 200, type: typeof(WorldLink), description: "Successful operation.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Invalid UUID supplied.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + public override IActionResult GetWorldLinkById([FromRoute(Name = "worldLinkUUID")][Required] Guid worldLinkUUID) + { + WorldLink myworldlink = _worldLinkService.Get(worldLinkUUID); + if (null != myworldlink) + { + // check TypeFrom + if (myworldlink.TypeFrom == ObjectType.TrackableEnum) + { + if (null == _worldLinkService.GetTrackable(myworldlink.UUIDFrom)) + { + myworldlink.TypeFrom = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDFrom = Guid.Empty; + } + } + else if (myworldlink.TypeFrom == ObjectType.WorldAnchorEnum) + { + if (null == _worldLinkService.GetAnchor(myworldlink.UUIDFrom)) + { + myworldlink.TypeFrom = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDFrom = Guid.Empty; + } + } + // check TypeTo + if (myworldlink.TypeTo == ObjectType.TrackableEnum) + { + if (null == _worldLinkService.GetTrackable(myworldlink.UUIDTo)) + { + myworldlink.TypeTo = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDTo = Guid.Empty; + } + } + else if (myworldlink.TypeTo == ObjectType.WorldAnchorEnum) + { + if (null == _worldLinkService.GetAnchor(myworldlink.UUIDTo)) + { + myworldlink.TypeTo = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDTo = Guid.Empty; + } + } + } + return (null != myworldlink) ? new ObjectResult(myworldlink) : StatusCode(404, "Not found, could not find UUID in database."); + } + + /// + /// Return all World Links. + /// + /// Get all the World Links currently being stored in the world storage. + /// OK return all the World Links defined by the world storage. + /// Null response. + /// Unexpected error. + [HttpGet] + [Route("/worldLinks")] + [ValidateModelState] + [SwaggerOperation("GetWorldLinks")] + [SwaggerResponse(statusCode: 200, type: typeof(List), description: "OK return all the World Links defined by the world storage.")] + [SwaggerResponse(statusCode: 201, type: typeof(string), description: "Null response.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult GetWorldLinks() + { + List worldlinklist = _worldLinkService.Get(); + foreach (WorldLink myworldlink in worldlinklist) + { + // check TypeFrom + if (myworldlink.TypeFrom == ObjectType.TrackableEnum) + { + if (null == _worldLinkService.GetTrackable(myworldlink.UUIDFrom)) + { + myworldlink.TypeFrom = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDFrom = Guid.Empty; + } + } + else if (myworldlink.TypeFrom == ObjectType.WorldAnchorEnum) + { + if (null == _worldLinkService.GetAnchor(myworldlink.UUIDFrom)) + { + myworldlink.TypeFrom = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDFrom = Guid.Empty; + } + } + // check TypeTo + if (myworldlink.TypeTo == ObjectType.TrackableEnum) + { + if (null == _worldLinkService.GetTrackable(myworldlink.UUIDTo)) + { + myworldlink.TypeTo = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDTo = Guid.Empty; + } + } + else if (myworldlink.TypeTo == ObjectType.WorldAnchorEnum) + { + if (null == _worldLinkService.GetAnchor(myworldlink.UUIDTo)) + { + myworldlink.TypeTo = ObjectType.NotIdentifiedEnum; + myworldlink.UUIDTo = Guid.Empty; + } + } + } + return new ObjectResult(worldlinklist); + } + + + + /// + /// Modify a World Link. + /// + /// Modify an existing World Link given a json object containing all the required informations. <br> **Please note that ID of the object is required in the JSON** + /// The World Link to be modified in the world storage. + /// OK, return the UUID of the modified World Link. + /// Bad request. + /// Not found, could not find UUID in database. + /// Unexpected error. + [HttpPut] + [Route("/worldLinks")] + [Consumes("application/json")] + [ValidateModelState] + [SwaggerOperation("ModifyWorldLink")] + [SwaggerResponse(statusCode: 200, type: typeof(string), description: "OK, return the UUID of the modified World Link.")] + [SwaggerResponse(statusCode: 400, type: typeof(string), description: "Bad request.")] + [SwaggerResponse(statusCode: 404, type: typeof(string), description: "Not found, could not find UUID in database.")] + [SwaggerResponse(statusCode: 0, type: typeof(Error), description: "Unexpected error.")] + public override IActionResult ModifyWorldLink([FromBody] WorldLink worldLink) + { + ReplaceOneResult result = _worldLinkService.Update(worldLink.UUID, worldLink); + if (result.MatchedCount == 0) + { + return StatusCode(404, "Not found, could not find UUID in database."); + } + else + { + return StatusCode(200, worldLink.UUID.ToString()); + } + } + } +} diff --git a/server/src/Org.OpenAPITools/Dockerfile b/server/src/Org.OpenAPITools/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..267d1dfaaaba2df754d93ddc9cf2e2171f82d899 --- /dev/null +++ b/server/src/Org.OpenAPITools/Dockerfile @@ -0,0 +1,32 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# Container we use for final publish +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +# Build container +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build + +# Copy the code into the container +WORKDIR /src +COPY ["Org.OpenAPITools.csproj", "Org.OpenAPITools/"] + +# NuGet restore +RUN dotnet restore "Org.OpenAPITools/Org.OpenAPITools.csproj" +COPY [".", "Org.OpenAPITools/"] + +# Build the API +WORKDIR "Org.OpenAPITools" +RUN dotnet build "Org.OpenAPITools.csproj" -c Release -o /app/build + +# Publish it +FROM build AS publish +RUN dotnet publish "Org.OpenAPITools.csproj" -c Release -o /app/publish + +# Make the final image for publishing +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Org.OpenAPITools.dll"] diff --git a/server/src/Org.OpenAPITools/Services/DatabaseSettings.cs b/server/src/Org.OpenAPITools/Services/DatabaseSettings.cs new file mode 100644 index 0000000000000000000000000000000000000000..93f177d61574831e98d067472c7bf25a98e79e4d --- /dev/null +++ b/server/src/Org.OpenAPITools/Services/DatabaseSettings.cs @@ -0,0 +1,45 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Org.OpenAPITools.Services +{ + public class DatabaseSettings : IDatabaseSettings + { + public string CollectionNameWorldLink { get; set; } + public string CollectionNameTrackables { get; set; } + public string CollectionNameWorldAnchor { get; set; } + public string ConnectionString { get; set; } + public string DatabaseName { get; set; } + } + + public interface IDatabaseSettings + { + string CollectionNameWorldLink { get; set; } + string CollectionNameTrackables { get; set; } + string CollectionNameWorldAnchor { get; set; } + string ConnectionString { get; set; } + string DatabaseName { get; set; } + } +} diff --git a/server/src/Org.OpenAPITools/Services/TrackableService.cs b/server/src/Org.OpenAPITools/Services/TrackableService.cs new file mode 100644 index 0000000000000000000000000000000000000000..a115449919516309400d5ee19284e926f920c512 --- /dev/null +++ b/server/src/Org.OpenAPITools/Services/TrackableService.cs @@ -0,0 +1,86 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Org.OpenAPITools.Models; +using MongoDB.Driver; + +namespace Org.OpenAPITools.Services +{ + public class TrackableService + { + private readonly IMongoCollection _trackablecollection; + private readonly IMongoCollection _worldlinkcollection; + + /// + /// + /// + public TrackableService(IDatabaseSettings settings) + { + var client = new MongoClient(settings.ConnectionString); + var database = client.GetDatabase(settings.DatabaseName); + + _trackablecollection = database.GetCollection(settings.CollectionNameTrackables); + _worldlinkcollection = database.GetCollection(settings.CollectionNameWorldLink); + } + + public List Get() => + _trackablecollection.Find(trackable => true).ToList(); + + public List Get(int limit) => + _trackablecollection.Find(trackable => true).Limit(limit).ToList(); + + public Trackable Get(Guid UUID) => + _trackablecollection.Find(trackable => trackable.UUID == UUID).FirstOrDefault(); + + public Trackable Create(Trackable trackable) + { + _trackablecollection.InsertOne(trackable); + return trackable; + } + + public ReplaceOneResult Update(Guid UUID, Trackable trackableIn) => + _trackablecollection.ReplaceOne(trackable => trackable.UUID == UUID, trackableIn); + + public DeleteResult Remove(Trackable trackableIn) => + _trackablecollection.DeleteOne(trackable => trackable.UUID == trackableIn.UUID); + + public DeleteResult Remove(Guid UUID) => + _trackablecollection.DeleteOne(trackable => trackable.UUID == UUID); + + + + /* WorldLink */ + public List GetWorldLinkUUIDFrom(Guid UUID) => + _worldlinkcollection.Find(worldlink => worldlink.UUIDFrom == UUID).ToList(); + + public List GetWorldLinkUUIDTo(Guid UUID) => + _worldlinkcollection.Find(worldlink => worldlink.UUIDTo == UUID).ToList(); + + public ReplaceOneResult UpdateWorldLink(Guid UUID, WorldLink worldlinkIn) => + _worldlinkcollection.ReplaceOne(worldlink => worldlink.UUID == UUID, worldlinkIn); + + } + +} + diff --git a/server/src/Org.OpenAPITools/Services/WorldAnchorService.cs b/server/src/Org.OpenAPITools/Services/WorldAnchorService.cs new file mode 100644 index 0000000000000000000000000000000000000000..8a6e7c6974526a0ef332c02d0965470ecca7206c --- /dev/null +++ b/server/src/Org.OpenAPITools/Services/WorldAnchorService.cs @@ -0,0 +1,83 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Org.OpenAPITools.Models; +using MongoDB.Driver; + +namespace Org.OpenAPITools.Services +{ + public class WorldAnchorService + { + private readonly IMongoCollection _worldanchorcollection; + private readonly IMongoCollection _worldlinkcollection; + + /// + /// + /// + public WorldAnchorService(IDatabaseSettings settings) + { + var client = new MongoClient(settings.ConnectionString); + var database = client.GetDatabase(settings.DatabaseName); + + _worldanchorcollection = database.GetCollection(settings.CollectionNameWorldAnchor); + _worldlinkcollection = database.GetCollection(settings.CollectionNameWorldLink); + } + + public List Get() => + _worldanchorcollection.Find(worldanchor => true).ToList(); + + public List Get(int limit) => + _worldanchorcollection.Find(worldanchor => true).Limit(limit).ToList(); + + public WorldAnchor Get(Guid UUID) => + _worldanchorcollection.Find(worldanchor => worldanchor.UUID == UUID).FirstOrDefault(); + + public WorldAnchor Create(WorldAnchor worldanchor) + { + _worldanchorcollection.InsertOne(worldanchor); + return worldanchor; + } + + public ReplaceOneResult Update(Guid UUID, WorldAnchor worldanchorIn) => + _worldanchorcollection.ReplaceOne(worldanchor => worldanchor.UUID == UUID, worldanchorIn); + + public DeleteResult Remove(WorldAnchor worldanchorIn) => + _worldanchorcollection.DeleteOne(worldanchor => worldanchor.UUID == worldanchorIn.UUID); + + public DeleteResult Remove(Guid UUID) => + _worldanchorcollection.DeleteOne(worldanchor => worldanchor.UUID == UUID); + + + /* WorldLink */ + public List GetWorldLinkUUIDFrom(Guid UUID) => + _worldlinkcollection.Find(worldlink => worldlink.UUIDFrom == UUID).ToList(); + + public List GetWorldLinkUUIDTo(Guid UUID) => + _worldlinkcollection.Find(worldlink => worldlink.UUIDTo == UUID).ToList(); + public ReplaceOneResult UpdateWorldLink(Guid UUID, WorldLink worldlinkIn) => + _worldlinkcollection.ReplaceOne(worldlink => worldlink.UUID == UUID, worldlinkIn); + } + +} + diff --git a/server/src/Org.OpenAPITools/Services/WorldLinkService.cs b/server/src/Org.OpenAPITools/Services/WorldLinkService.cs new file mode 100644 index 0000000000000000000000000000000000000000..073c59724eb6cb8c966ab2a1a3f003b31e768f7f --- /dev/null +++ b/server/src/Org.OpenAPITools/Services/WorldLinkService.cs @@ -0,0 +1,83 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Org.OpenAPITools.Models; +using MongoDB.Driver; + +namespace Org.OpenAPITools.Services +{ + public class WorldLinkService + { + private readonly IMongoCollection _worldlinkcollection; + private readonly IMongoCollection _worldanchorcollection; + private readonly IMongoCollection _trackablecollection; + + /// + /// + /// + public WorldLinkService(IDatabaseSettings settings) + { + var client = new MongoClient(settings.ConnectionString); + var database = client.GetDatabase(settings.DatabaseName); + + _worldlinkcollection = database.GetCollection(settings.CollectionNameWorldLink); + _worldanchorcollection = database.GetCollection(settings.CollectionNameWorldAnchor); + _trackablecollection = database.GetCollection(settings.CollectionNameTrackables); + } + + public List Get() => + _worldlinkcollection.Find(worldlink => true).ToList(); + + public List Get(int limit) => + _worldlinkcollection.Find(worldlink => true).Limit(limit).ToList(); + + public WorldLink Get(Guid UUID) => + _worldlinkcollection.Find(worldlink => worldlink.UUID == UUID).FirstOrDefault(); + + public WorldLink Create(WorldLink worldlink) + { + _worldlinkcollection.InsertOne(worldlink); + return worldlink; + } + + public ReplaceOneResult Update(Guid UUID, WorldLink worldlinkIn) => + _worldlinkcollection.ReplaceOne(worldlink => worldlink.UUID == UUID, worldlinkIn); + + public DeleteResult Remove(WorldLink worldlinkIn) => + _worldlinkcollection.DeleteOne(worldlink => worldlink.UUID == worldlinkIn.UUID); + + public DeleteResult Remove(Guid UUID) => + _worldlinkcollection.DeleteOne(worldlink => worldlink.UUID == UUID); + + + /*********************/ + public WorldAnchor GetAnchor(Guid UUID) => + _worldanchorcollection.Find(worldanchor => worldanchor.UUID == UUID).FirstOrDefault(); + + public Trackable GetTrackable(Guid UUID) => + _trackablecollection.Find(trackable => trackable.UUID == UUID).FirstOrDefault(); + + } +} + diff --git a/server/src/Org.OpenAPITools/Startup.cs b/server/src/Org.OpenAPITools/Startup.cs new file mode 100644 index 0000000000000000000000000000000000000000..a3ee3471a19b486cbdfbefaa341ce4933f51744b --- /dev/null +++ b/server/src/Org.OpenAPITools/Startup.cs @@ -0,0 +1,180 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2022 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2022 +// + +/* + * World Storage API + * + * API ensuring interoperability between an authoring tool and a World Storage service + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using Org.OpenAPITools.Authentication; +using Org.OpenAPITools.Filters; +using Org.OpenAPITools.OpenApi; +using Org.OpenAPITools.Formatters; +using Org.OpenAPITools.Services; +using MongoDB.Bson.Serialization; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Serializers; +using Microsoft.Extensions.Options; + +namespace Org.OpenAPITools +{ + /// + /// Startup + /// + public class Startup + { + /// + /// Constructor + /// + /// + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + /// + /// The application configuration. + /// + public IConfiguration Configuration { get; } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// + public void ConfigureServices(IServiceCollection services) + { + BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard)); + BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3; + // requires using Microsoft.Extensions.Options + services.Configure( + Configuration.GetSection(nameof(DatabaseSettings))); + services.AddSingleton(sp => + sp.GetRequiredService>().Value); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // Add framework services. + services + // Don't need the full MVC stack for an API, see https://andrewlock.net/comparing-startup-between-the-asp-net-core-3-templates/ + .AddControllers(options => + { + options.InputFormatters.Insert(0, new InputFormatterStream()); + }) + .AddNewtonsoftJson(opts => + { + opts.SerializerSettings.ContractResolver = new DefaultContractResolver(); + /* opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + opts.SerializerSettings.Converters.Add(new StringEnumConverter + { + NamingStrategy = new CamelCaseNamingStrategy() + });*/ + }); + + services + .AddSwaggerGen(c => + { + c.SwaggerDoc("1.0.0", new OpenApiInfo + { + Title = "World Storage API", + Description = "World Storage API (ASP.NET Core 3.1)", + TermsOfService = new Uri("https://github.com/openapitools/openapi-generator"), + Contact = new OpenApiContact + { + Name = "OpenAPI-Generator Contributors", + Url = new Uri("https://github.com/openapitools/openapi-generator"), + Email = "" + }, + License = new OpenApiLicense + { + Name = "NoLicense", + Url = new Uri("https://opensource.org/licenses/BSD-3-Clause") + }, + Version = "1.0.0", + }); + c.CustomSchemaIds(type => type.FriendlyId(true)); + c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{Assembly.GetEntryAssembly().GetName().Name}.xml"); + + // Include DataAnnotation attributes on Controller Action parameters as OpenAPI validation rules (e.g required, pattern, ..) + // Use [ValidateModelState] on Actions to actually validate it in C# as well! + c.OperationFilter(); + }); + services + .AddSwaggerGenNewtonsoftSupport(); + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// + /// + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseSwagger(c => + { + c.RouteTemplate = "openapi/{documentName}/openapi.json"; + }) + .UseSwaggerUI(c => + { + // set route prefix to openapi, e.g. http://localhost:8080/openapi/index.html + c.RoutePrefix = "openapi"; + //TODO: Either use the SwaggerGen generated OpenAPI contract (generated from C# classes) + //c.SwaggerEndpoint("/openapi/1.0.0/openapi.json", "World Storage API"); + + //TODO: Or alternatively use the original OpenAPI contract that's included in the static files + c.SwaggerEndpoint("/openapi-original.json", "World Storage API Original"); + }); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/server/src/Org.OpenAPITools/appsettings.json b/server/src/Org.OpenAPITools/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..aebd1bbadc56a6c987a8359d0c6ffc87d5760f89 --- /dev/null +++ b/server/src/Org.OpenAPITools/appsettings.json @@ -0,0 +1,15 @@ +{ + "DatabaseSettings": { + "CollectionNameWorldLink": "WorldLink", + "CollectionNameTrackables": "Trackables", + "CollectionNameWorldAnchor": "WorldAnchor", + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "WorldStorageAPI" + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/server/src/Org.OpenAPITools/docker-compose.yml b/server/src/Org.OpenAPITools/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..4bf996377154112c6c94313165f9d8123b2a1395 --- /dev/null +++ b/server/src/Org.OpenAPITools/docker-compose.yml @@ -0,0 +1,41 @@ +# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP .NET Core service. + +version: '3.4' + +networks: + vpcbr: + ipam: + config: + - subnet: 10.10.20.0/24 + +services: + worldstorageapi: + image: org.openapitools:latest + volumes: + - ./docker/appsettings.json:/app/appsettings.json:rw + ports: + - 8080:8080 + networks: + vpcbr: + ipv4_address: 10.10.20.101 + restart: unless-stopped + + mongodatabase: + image: mongo:latest + container_name: mongodatabase + environment: + - PUID=1000 + - PGID=1000 + volumes: + - mongodbdata:/data/db:rw + - ./docker/data-dump:/data-dump + ports: + - 27017:27017 + - 28017:28017 + networks: + vpcbr: + ipv4_address: 10.10.20.100 + restart: unless-stopped + +volumes: + mongodbdata: \ No newline at end of file diff --git a/server/src/Org.OpenAPITools/docker/appsettings.json b/server/src/Org.OpenAPITools/docker/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..28066ba66cf25710db88eac58e4066184f67bd0c --- /dev/null +++ b/server/src/Org.OpenAPITools/docker/appsettings.json @@ -0,0 +1,15 @@ +{ + "DatabaseSettings": { + "CollectionNameWorldLink": "WorldLink", + "CollectionNameTrackables": "Trackables", + "CollectionNameWorldAnchor": "WorldAnchor", + "ConnectionString": "mongodb://10.10.20.100:27017", + "DatabaseName": "WorldStorageAPI" + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +}