- This Project is a web application that allows users to create, read, update, and delete dashboard configurations.
- It retrieves populated dashboards from external APIs (REST Countries, Open-Meteo, Currency API) and supports registering, deleting, and viewing webhooks for events. The webhooks are triggered automatically when a dashboard is created, updated, or deleted.
- The service is deployed using Docker and OpenStack, and persists data using Firestore.
| Member | Responsibilities |
|---|---|
| Sofiya Yasim Ibrahim Ali | notifications_handler and corresponding structs, and connecting trigger webhooks to registration_handler as well as testing. |
| Katerina Radchenko | Docker, dashboards_handler and corresponding structs, as well as integration with external APIs, registration_handler. |
| Katharina Kivle | status_handler and corresponding structs and registration_handler as well as testing. |
- Register, update, delete, and view dashboard configurations
- Retrieve populated dashboards with external data
- Webhook registration for:
REGISTER(new dashboard)CHANGE(dashboard update)DELETE(dashboard removal)INVOKE(dashboard viewed)
Method: GET
Path: /
Method: POST
Path: /dashboard/v1/registrations/
{
"country": "Norway",
"isoCode": "NO",
"features": {
"temperature": true,
"precipitation": true,
"capital": true,
"coordinates": true,
"population": true,
"area": true,
"targetCurrencies": ["EUR", "USD", "SEK"]
}
}
{
"id": "ZaAJF0ywF7XFDJRPsfwi",
"lastChange": "20250407 13:02"
}
Method: GET
Path: /dashboard/v1/registrations/
[
{
"id": "0xrM5VxUqt9QAkUlM2Az",
"country": "Norway",
"isoCode": "no",
"features": {
"temperature": true,
"precipitation": true,
"capital": true,
"coordinates": true,
"population": true,
"area": true,
"targetCurrencies": [
"USD",
"SEK"
]
},
"lastChange": "20250408 19:41"
},
{
"id": "MUGKLhX4aX8R8tsVXk3i",
"country": "Sweden",
"isoCode": "se",
"features": {
"temperature": true,
"precipitation": true,
"capital": true,
"coordinates": true,
"population": true,
"area": true,
"targetCurrencies": [
"EUR",
"NOK",
"SEK"
]
},
"lastChange": "20250403 14:02"
},
{
"id": "ZaAJF0ywF7XFDJRPsfwi",
"country": "Norway",
"isoCode": "no",
"features": {
"temperature": false,
"precipitation": true,
"capital": true,
"coordinates": true,
"population": true,
"area": true,
"targetCurrencies": [
"EUR",
"USD",
"SEK"
]
},
"lastChange": "20250407 23:02"
},
{
"id": "b3RiUziT0eCMohDRPxJH",
"country": "Belgia",
"isoCode": "be",
"features": {
"temperature": false,
"precipitation": true,
"capital": true,
"coordinates": false,
"population": true,
"area": true,
"targetCurrencies": [
"EUR",
"USD",
"SEK"
]
},
"lastChange": "20250408 20:08"
}...
]
Method: GET
Path: /dashboard/v1/registrations/{id}
id: the ID associated with the specific configuration.
Method: GET
Path: /dashboard/v1/registrations/b3RiUziT0eCMohDRPxJH
{
"id": "b3RiUziT0eCMohDRPxJH",
"country": "Belgia",
"isoCode": "be",
"features": {
"temperature": false,
"precipitation": true,
"capital": true,
"coordinates": false,
"population": true,
"area": true,
"targetCurrencies": [
"EUR",
"USD",
"SEK"
]
},
"lastChange": "20250408 20:08"
}
Method: PUT
Path: /dashboard/v1/registrations/{id}
id: the ID associated with the specific configuration.
Path: /dashboard/v1/registrations/b3RiUziT0eCMohDRPxJH
{
"country": "Belgia",
"isoCode": "be",
"features": {
"temperature": true, // Changed value
"precipitation": true,
"capital": true,
"coordinates": false,
"population": true,
"area": true,
"targetCurrencies": [
"EUR",
"USD",
"SEK"
]
}
}
Method: DELETE
Path: /dashboard/v1/registrations/{id}
id: the ID associated with the specific configuration.
/dashboard/v1/registrations/b3RiUziT0eCMohDRPxJH
Method: GET
Path: /dashboard/v1/dashboards/{id}
id: the ID associated with the specific configuration.
/dashboard/v1/dashboards/ZaAJF0ywF7XFDJRPsfwi
{
"country": "Norway",
"isoCode": "no",
"features": {
"temperature": null,
"precipitation": 0.02261904761904763,
"capital": "Oslo",
"coordinates": {
"latitude": 62,
"longitude": 10
},
"population": 5379475,
"area": 323802,
"targetCurrencies": {
"EUR": 0.083447,
"SEK": 0.916623,
"USD": 0.091509
}
},
"lastRetrieval": "20250410 00:19"
}
Method: POST
Path: /dashboard/v1/notifications/
{
"url": "https://webhook.site/b5e5e555-f590-4ecc-93ac-bf12990665c1",
"country": "NO",
"event": "INVOKE"
}
{
"id":"cFgY08RFKv1mBGhMLLRN"
}
Method: DELETE
Path: /dashboard/v1/notifications/{id}
id: the ID for the webhook registration.
/dashboard/v1/notifications/cFgY08RFKv1mBGhMLLRN
{
"message": "Webhook deleted successfully"
}
Method: GET
Path: /dashboard/v1/notifications/{id}
id: the ID for the webhook registration.
/dashboard/v1/notifications/8MFqluTvqj9DM9xg4uGY
{
"id": "8MFqluTvqj9DM9xg4uGY",
"url": "https://webhook.site/b5e5e555-f590-4ecc-93ac-bf12990665c1",
"country": "NO",
"event": "CHANGE"
}
Method: GET
Path: /dashboard/v1/notifications/
/dashboard/v1/notifications/
[
{
"id": "8MFqluTvqj9DM9xg4uGY",
"url": "https://webhook.site/b5e5e555-f590-4ecc-93ac-bf12990665c1",
"country": "NO",
"event": "CHANGE"
},
{
"id": "9b80WsAjAavjXb3FgXsm",
"url": "https://webhook.site/b5e5e555-f590-4ecc-93ac-bf12990665c1",
"country": "NO",
"event": "DELETE"
},
{
"id": "XETAPbr3CSRcklkK6LBe",
"url": "https://webhook.site/b5e5e555-f590-4ecc-93ac-bf12990665c1",
"country": "NO",
"event": "INVOKE"
}
]
Method: POST
Path: https://webhook.site/b5e5e555-f590-4ecc-93ac-bf12990665c1
/dashboard/v1/notifications/
{
"country": "NO",
"event": "INVOKE",
"id": "XETAPbr3CSRcklkK6LBe",
"time": "20250409 11:32"
}
Method: GET
Path: dashboard/v1/status/
{
"countries_api": 200,
"meteo_api": 200,
"currency_api": 200,
"notification_db": 200,
"webhooks": 7,
"version": "v1",
"uptime": 8801
}
- Language: Go (Golang)
- Database: Firestore (Firebase)
- Hosting: Docker + OpenStack (SkyHigh)
- External APIs:
- REST Countries
- Open-Meteo
- Currency API
HTTP errors handled in the code:
200 - OK
204 - No Content
404 - Not Found
405 - Method Not Allowed
500 - Internal Server Error
Set environment variable to your service account JSON. Do NOT commit this file.
$env:FIREBASE_CREDENTIALS = "C:\\path\\to\\your\\service-account.json"
# optional
$env:PORT = "8080"
cd "C:\\Users\\Bruker\\OneDrive - NTNU\\semester 4\\Cloud Technologies\\assignment-2"
go run .\main.goRun the Firestore emulator locally and set its host; the service will connect without keys.
# Example host; adapt to your emulator
$env:FIRESTORE_EMULATOR_HOST = "localhost:8081"
cd "C:\\Users\\Bruker\\OneDrive - NTNU\\semester 4\\Cloud Technologies\\assignment-2"
go run .\main.goOpen in browser:
- Homepage: http://localhost:8080/
- Status: http://localhost:8080/dashboard/v1/status/
Use a REST client (or Invoke-RestMethod) for registrations/webhooks.
This project is dockerized and deployed on OpenStack (SkyHigh).
The first step was setting up OpenStack (and the network, subnet and router), adding new rules to security groups (allowing ingress from 8080 and 22) and finally creating an instance. After connecting to the instance and installing Docker, it was time to create the Dockerfile that will be used to set up the image. Next we build the image and run it:
docker build -t assignment-2 .
docker run -d -p 8080:8080 --name assignment2 assignment-2
Use the following URL to run the service:
A range of tests have been implemented to ensure that the core functionality of the system works as expected. These tests cover handling of webhooks, creation and retrieval of dashboards, as well as system status verification.
The focus of the tests has been to:
- Validate the use of correct HTTP methods (such as GET, POST, and DELETE).
- Ensure the system correctly handles invalid requests, such as malformed JSON or missing IDs.
- Check response codes and error handling to ensure the correct status is returned in various scenarios.
The tests are written using Go’s built-in testing package, along with assert for clear and expressive validation.
go testThe tests are located in the testing_Handler folder and cover the following components:
notification_Handlerregistrations_Handlerstatus_Handler
These tests help ensure the reliability of the system by catching errors early and supporting robust, stable operation.
- Service account JSON files are sensitive. Store them outside the repository and reference via env vars.
- In the portfolio ZIP, credentials are intentionally excluded.
- Prefer the Firestore emulator for development to avoid key management.
- All time formats are in
"YYYYMMDD HH:MM"format - Country/ISO code must be provided for valid webhook matching
- Each webhook registration is persistent and survives service restarts
Futher work with the assignemtnt would include more testing as well as comparing our implementation with working on JSON documents that are then transferred to the database (instead of working on the firebase "directly").