diff --git a/data-collectors/parking-offstreet-sta/.env.example b/data-collectors/parking-offstreet-sta/.env.example new file mode 100644 index 000000000..f46a5cca6 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/.env.example @@ -0,0 +1,21 @@ +API_OAUTH_TOKEN_URI= +API_OAUTH_USERNAME= +API_OAUTH_PASSWORD= +API_OAUTH_CLIENT_SECRET= +API_OAUTH_CLIENT_ID= + + +SCHEDULER_CRON="*/10 * * * * *" + +BASE_URI=https://mobility.share.opendatahub.testingmachine.eu/json +OAUTH_AUTH_URI=https://auth.opendatahub.testingmachine.eu/auth +OAUTH_TOKEN_URI=https://auth.opendatahub.testingmachine.eu/auth/realms/noi/protocol/openid-connect/token +OAUTH_CLIENT_ID=odh-mobility-datacollector-development +OAUTH_CLIENT_NAME=odh-mobility-datacollector-development +OAUTH_CLIENT_SECRET=7bd46f8f-c296-416d-a13d-dc81e68d0830 +OAUTH_CLIENT_SCOPE=openid + +PROVENANCE_VERSION=0.0.0-local +PROVENANCE_NAME=odh-mobility-golang-dc-parking-offstreet-sta + +LOG_LEVEL=INFO \ No newline at end of file diff --git a/data-collectors/parking-offstreet-sta/.gitignore b/data-collectors/parking-offstreet-sta/.gitignore new file mode 100644 index 000000000..0a2bb2e39 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/.gitignore @@ -0,0 +1,25 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +.env + +main \ No newline at end of file diff --git a/data-collectors/parking-offstreet-sta/README.md b/data-collectors/parking-offstreet-sta/README.md new file mode 100644 index 000000000..da1e1f295 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/README.md @@ -0,0 +1,38 @@ + + +# Parking off street STA + +[![CI](https://github.com/noi-techpark/bdp-commons/actions/workflows/ci-parking-offstreet-sta.yml/badge.svg)](https://github.com/noi-techpark/bdp-commons/actions/workflows/ci-parking-offstreet-sta.yml) + +This data collector gathers data about STA's parking facilities provided by Skidata. +The job runs as a scheduled cron job that periodically interrogates the onecenter REST API. + +See the [requirements](./documentation/230214_SpecificheIntegrazione_NOI_v1.1.pdf) and [API specification](./documentation/230728_SpecificaSkidata.pdf) for more details + +See [calls.http](./calls.http) for example API calls to the onecenter REST API. + +## Getting started + +General instructions can be found inside the [Open Data Hub Mobility - Data +Collectors README](../../README.md). Please read that first. The following +chapters will only contain specific configuration and setup steps. + +These instructions will get you a copy of the project up and running on your +local machine for development and testing purposes. + +### Prerequisites + +To build the project, the following prerequisites must be met: +- Everything inside [Open Data Hub Mobility - Data Collectors README](../../README.md#prerequisites) + +### Configuration + +Create a local .env file by copying the [example .env](.env.example) + +Credentials both for ODH and ONECENTER endpoints have to be obtained separately + +After plugging in the relevant credentials, docker-compose up should fire up a local instance. diff --git a/data-collectors/parking-offstreet-sta/calls.http b/data-collectors/parking-offstreet-sta/calls.http new file mode 100644 index 000000000..0d9fd74e1 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/calls.http @@ -0,0 +1,34 @@ +##### VSCODE / REST Client +# Create a .env file and set the corresponding variables +# See all $dotenv fields below + +@username={{$dotenv API_OAUTH_USERNAME}} +@password={{$dotenv API_OAUTH_PASSWORD}} +@secret={{$dotenv API_OAUTH_CLIENT_SECRET}} +@clientId={{$dotenv API_OAUTH_CLIENT_ID}} + + + +### Get access token for the writer (LOCAL DEV) +# @name login +GET https://www.onecenter.info/oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=password +&username={{username}} +&client_id={{clientId}} +&client_secret={{secret}} +&password={{password}} + +### save token + +@token = {{login.response.body.access_token}} + +### GetFacilities +GET https://www.onecenter.info/api/DAZ/GetFacilities +Authorization: Bearer {{token}} + +### FacilityID +GET https://www.onecenter.info/api/DAZ/FacilityFreePlaces +?FacilityID=608612 +Authorization: Bearer {{login.response.body.access_token}} \ No newline at end of file diff --git a/data-collectors/parking-offstreet-sta/docker-compose.yml b/data-collectors/parking-offstreet-sta/docker-compose.yml new file mode 100644 index 000000000..3916268f6 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" + +services: + app: + image: golang:1.21.4-alpine + env_file: + - .env + entrypoint: > + go run main.go + volumes: + - ./src:/code + - ~/.go/pkg/mod:/go/pkg/mod + working_dir: /code \ No newline at end of file diff --git a/data-collectors/parking-offstreet-sta/documentation/230728_SpecificaSkidata.pdf b/data-collectors/parking-offstreet-sta/documentation/230728_SpecificaSkidata.pdf new file mode 100644 index 000000000..e3bd7f115 Binary files /dev/null and b/data-collectors/parking-offstreet-sta/documentation/230728_SpecificaSkidata.pdf differ diff --git a/data-collectors/parking-offstreet-sta/infrastructure/ansible/ansible.cfg b/data-collectors/parking-offstreet-sta/infrastructure/ansible/ansible.cfg new file mode 100644 index 000000000..bec5ce050 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/ansible/ansible.cfg @@ -0,0 +1,4 @@ +[defaults] +inventory = ./hosts +roles_path = ./roles +retry_files_enabled = False diff --git a/data-collectors/parking-offstreet-sta/infrastructure/ansible/deploy.yml b/data-collectors/parking-offstreet-sta/infrastructure/ansible/deploy.yml new file mode 100644 index 000000000..905feb993 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/ansible/deploy.yml @@ -0,0 +1,19 @@ +--- +- hosts: all + vars: + ansible_python_interpreter: /usr/bin/python3 + tasks: + - name: Login to GitHub Container Registry + ansible.builtin.shell: + cmd: echo "{{ docker_password }}" | docker login "{{ docker_host }}" --username "{{ docker_username }}" --password-stdin + - name: Execute Docker deployment + ansible.builtin.include_role: + name: ansible-docker-deployment + vars: + docker_deployment_project_name: '{{ project_name }}' + docker_deployment_release_name: '{{ release_name }}' + docker_deployment_release_files: + - local: ../docker-compose.run.yml + remote: docker-compose.yml + - local: ../../.env + remote: .env \ No newline at end of file diff --git a/data-collectors/parking-offstreet-sta/infrastructure/ansible/hosts b/data-collectors/parking-offstreet-sta/infrastructure/ansible/hosts new file mode 100644 index 000000000..b77317073 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/ansible/hosts @@ -0,0 +1,5 @@ +[test] +docker03.testingmachine.eu ansible_user='noi-techpark-bot' ansible_ssh_common_args='-o StrictHostKeyChecking=no' + +[prod] +docker03.opendatahub.bz.it ansible_user='noi-techpark-bot' ansible_ssh_common_args='-o StrictHostKeyChecking=no' diff --git a/data-collectors/parking-offstreet-sta/infrastructure/ansible/requirements.yml b/data-collectors/parking-offstreet-sta/infrastructure/ansible/requirements.yml new file mode 100644 index 000000000..cb9e81bb8 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/ansible/requirements.yml @@ -0,0 +1,2 @@ +- src: git+https://github.com/noi-techpark/ansible-docker-deployment.git + version: "2.0" diff --git a/data-collectors/parking-offstreet-sta/infrastructure/ansible/roles/.gitignore b/data-collectors/parking-offstreet-sta/infrastructure/ansible/roles/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/ansible/roles/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/data-collectors/parking-offstreet-sta/infrastructure/docker-compose.build.yml b/data-collectors/parking-offstreet-sta/infrastructure/docker-compose.build.yml new file mode 100644 index 000000000..5a97148fb --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/docker-compose.build.yml @@ -0,0 +1,10 @@ +version: '3.4' + +services: + app: + image: ${DOCKER_IMAGE}:${DOCKER_TAG} + build: + context: ../ + dockerfile: infrastructure/docker/Dockerfile + env_file: ../.env + restart: unless-stopped diff --git a/data-collectors/parking-offstreet-sta/infrastructure/docker-compose.run.yml b/data-collectors/parking-offstreet-sta/infrastructure/docker-compose.run.yml new file mode 100644 index 000000000..4aee574ea --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/docker-compose.run.yml @@ -0,0 +1,7 @@ +version: '3.4' + +services: + app: + image: ${DOCKER_IMAGE}:${DOCKER_TAG} + restart: unless-stopped + env_file: .env diff --git a/data-collectors/parking-offstreet-sta/infrastructure/docker/Dockerfile b/data-collectors/parking-offstreet-sta/infrastructure/docker/Dockerfile new file mode 100644 index 000000000..187aa6242 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/infrastructure/docker/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:1.21.4-bookworm + +# install if docker host doesn't has libc6 installed +RUN apt-get update && apt-get install -y libc6 + +WORKDIR app + +COPY src/main main + +CMD [ "./main"] \ No newline at end of file diff --git a/data-collectors/parking-offstreet-sta/src/dc/auth.go b/data-collectors/parking-offstreet-sta/src/dc/auth.go new file mode 100644 index 000000000..ff7139d44 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/dc/auth.go @@ -0,0 +1,98 @@ +package dc + +import ( + "encoding/json" + "io" + "log/slog" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" +) + +type Token struct { + AccessToken string `json:"access_token"` + ExpiresIn int64 `json:"expires_in"` + NotBeforePolicy int64 `json:"not-before-policy"` + RefreshExpiresIn int64 `json:"refresh_expires_in"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + Scope string +} + +var tokenUri string = os.Getenv("API_OAUTH_TOKEN_URI") +var clientId string = os.Getenv("API_OAUTH_CLIENT_ID") +var clientSecret string = os.Getenv("API_OAUTH_CLIENT_SECRET") +var username string = os.Getenv("API_OAUTH_USERNAME") +var password string = os.Getenv("API_OAUTH_PASSWORD") + +var token Token + +var tokenExpiry int64 + +func GetToken() string { + ts := time.Now().Unix() + + if len(token.AccessToken) == 0 || ts > tokenExpiry { + // if no token is available or refreshToken is expired, get new token + newToken() + } + + return token.AccessToken +} + +func newToken() { + slog.Info("Getting new API token...") + + authRequest() + + slog.Info("Getting new API token done.") +} + +func authRequest() { + params := url.Values{} + params.Add("grant_type", "password") + params.Add("client_id", clientId) + params.Add("client_secret", clientSecret) + params.Add("username", username) + params.Add("password", password) + + body := strings.NewReader(params.Encode()) + + req, err := http.NewRequest("GET", tokenUri, body) + if err != nil { + slog.Error("error", err) + return + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + slog.Error("error", err) + return + } + defer resp.Body.Close() + + slog.Info("Auth response code is: " + strconv.Itoa(resp.StatusCode)) + if resp.StatusCode == http.StatusOK { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + slog.Error("error", err) + return + } + + err = json.Unmarshal(bodyBytes, &token) + if err != nil { + slog.Error("error", err) + return + } + } + + // calculate token expiry timestamp with 600 seconds margin + tokenExpiry = time.Now().Unix() + token.ExpiresIn - 600 + + slog.Info("auth token expires in " + strconv.FormatInt(tokenExpiry, 10)) +} diff --git a/data-collectors/parking-offstreet-sta/src/dc/data.go b/data-collectors/parking-offstreet-sta/src/dc/data.go new file mode 100644 index 000000000..8942be4e1 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/dc/data.go @@ -0,0 +1,72 @@ +package dc + +import ( + "encoding/json" + "io" + "log/slog" + "net/http" + "strconv" +) + +type ApiResponse struct { + Data Data +} + +type Data struct { + Status string + Facilities []Facility +} + +type Facility struct { + IdCompany int + FacilityId int + Description string + City string + Address string + ZIPCode string + Telephone1 string + Telephone2 string + PostNumber int + ReceiptMerchant string + Web string +} + +const apiUrl = "https://www.onecenter.info/api/DAZ/GetFacilities" + +func GetData() ApiResponse { + + var apiResponse ApiResponse + + req, err := http.NewRequest("GET", apiUrl, nil) + if err != nil { + slog.Error("error", err) + return apiResponse + } + req.Header = http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"Bearer " + GetToken()}, + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + slog.Error("error", err) + return apiResponse + } + defer resp.Body.Close() + + slog.Info("Auth response code is: " + strconv.Itoa(resp.StatusCode)) + if resp.StatusCode == http.StatusOK { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + slog.Error("error", err) + return apiResponse + } + + err = json.Unmarshal(bodyBytes, &apiResponse) + if err != nil { + slog.Error("error", err) + return apiResponse + } + } + return apiResponse +} diff --git a/data-collectors/parking-offstreet-sta/src/dc/job.go b/data-collectors/parking-offstreet-sta/src/dc/job.go new file mode 100644 index 000000000..3d67c7914 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/dc/job.go @@ -0,0 +1,36 @@ +package dc + +import ( + "parking-offstreet-sta/lib" +) + +const stationTypeParent string = "ParkingFacility" +const stationTypeChild string = "ParkingStation" +const origin string = "skidata" + +func Job() { + var parentStations []lib.Station + var childStations []lib.Station + + // var parentDataMap lib.DataMap + // var childDataMap lib.DataMap + + // get data + + // sync stations + lib.SyncStations(stationTypeParent, parentStations) + lib.SyncStations(stationTypeParent, childStations) + + // push data + // lib.PushData(stationTypeModel, modelDataMap) + +} + +func DataTypes() { + var dataTypes []lib.DataType + + dataTypes = append(dataTypes, lib.CreateDataType("free", "", "Free parking slots", "Instantaneous")) + dataTypes = append(dataTypes, lib.CreateDataType("occupied", "", "Occupied parking slots", "Instantaneous")) + + lib.SyncDataTypes(stationTypeChild, dataTypes) +} diff --git a/data-collectors/parking-offstreet-sta/src/dc/mapping.go b/data-collectors/parking-offstreet-sta/src/dc/mapping.go new file mode 100644 index 000000000..7d8e9431a --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/dc/mapping.go @@ -0,0 +1,151 @@ +package dc + +import ( + "encoding/json" + "log/slog" +) + +type Forecast struct { + Info Info `json:"info"` + Municipalities []Municipality `json:"municipalities"` +} + +type Info struct { + Model string `json:"model"` + CurrentModelRun string `json:"currentModelRun"` + NextModelRun string `json:"nextModelRun"` + FileName string `json:"fileName"` + FileCreationDate string `json:"fileCreationDate"` + AbsTempMin int `json:"absTempMin"` + AbsTempMax int `json:"absTempMax"` + AbsPrecMin int `json:"absPrecMin"` + AbsPrecMax int `json:"absPrecMax"` +} + +type Municipality struct { + Code string `json:"code"` + NameDe string `json:"nameDe"` + NameIt string `json:"nameIt"` + NameEn string `json:"nameEn"` + NameRm string `json:"nameRm"` + TempMin24 ValueSet `json:"tempMin24"` + TempMax24 ValueSet `json:"tempMax24"` + Temp3 ValueSet `json:"temp3"` + Ssd24 ValueSet `json:"ssd24"` + PrecProb3 ValueSet `json:"precProb3"` + PrecProb24 ValueSet `json:"precProb24"` + PrecSum3 ValueSet `json:"precSum3"` + PrecSum24 ValueSet `json:"precSum24"` + Symbols3 ValueSet `json:"symbols3"` + Symbols24 ValueSet `json:"symbols24"` + WindDir3 ValueSet `json:"windDir3"` + WindSpd3 ValueSet `json:"windSpd3"` +} + +type ValueSet struct { + NameDe string `json:"nameDe"` + NameIt string `json:"nameIt"` + NameEn string `json:"nameEn"` + NameRm string `json:"nameRm"` + Unit string `json:"unit"` + Data []Value `json:"data"` +} + +type Value struct { + Date string `json:"date"` + Value interface{} `json:"value"` +} + +func Mapping(data []byte) Forecast { + var forecast Forecast + + err := json.Unmarshal(data, &forecast) + if err != nil { + slog.Error("error", err) + } + + return forecast +} + +func MapQuantitative(value string) string { + switch value { + case "a_n": + case "a_d": + return "sunny" + case "b_n": + case "b_d": + return "partly cloudy" + case "c_n": + case "c_d": + return "cloudy" + case "d_n": + case "d_d": + return "very cloudy" + case "e_n": + case "e_d": + return "overcast" + case "f_n": + case "f_d": + return "cloudy with moderate rain" + case "g_n": + case "g_d": + return "cloudy with intense rain" + case "h_n": + case "h_d": + return "overcast with moderate rain" + case "i_n": + case "i_d": + return "overcast with intense rain" + case "j_n": + case "j_d": + return "overcast with light rain" + case "k_n": + case "k_d": + return "translucent cloudy" + case "l_n": + case "l_d": + return "cloudy with light snow" + case "m_n": + case "m_d": + return "cloudy with heavy snow" + case "n_n": + case "n_d": + return "overcast with light snow" + case "o_n": + case "o_d": + return "overcast with moderate snow" + case "p_n": + case "p_d": + return "overcast with intense snow" + case "q_n": + case "q_d": + return "cloudy with rain and snow" + case "r_n": + case "r_d": + return "overcast with rain and snow" + case "s_n": + case "s_d": + return "low cloudiness" + case "t_n": + case "t_d": + return "fog" + case "u_n": + case "u_d": + return "cloudy, thunderstorms with moderate showers" + case "v_n": + case "v_d": + return "cloudy, thunderstorms with intense showers" + case "w_n": + case "w_d": + return "cloudy, thunderstorms with moderate snowy and rainy showers" + case "x_n": + case "x_d": + return "cloudy, thunderstorms with intense snowy and rainy showers" + case "y_n": + case "y_d": + return "cloudy, thunderstorms with moderate snowy showers" + default: + slog.Error("No mapping configured for value: " + value) + } + return "" +} diff --git a/data-collectors/parking-offstreet-sta/src/go.mod b/data-collectors/parking-offstreet-sta/src/go.mod new file mode 100644 index 000000000..414df62ad --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/go.mod @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: (c) NOI Techpark + +// SPDX-License-Identifier: AGPL-3.0-or-later +module parking-offstreet-sta + +go 1.21.4 + +require github.com/go-co-op/gocron v1.37.0 + +require ( + github.com/google/uuid v1.5.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + go.uber.org/atomic v1.11.0 // indirect +) diff --git a/data-collectors/parking-offstreet-sta/src/go.sum b/data-collectors/parking-offstreet-sta/src/go.sum new file mode 100644 index 000000000..f452661ea --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/go.sum @@ -0,0 +1,40 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/data-collectors/parking-offstreet-sta/src/log/log.go b/data-collectors/parking-offstreet-sta/src/log/log.go new file mode 100644 index 000000000..04de6a055 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/log/log.go @@ -0,0 +1,39 @@ +package log + +import ( + "log/slog" + "os" + "strings" +) + +// read logger level from env and uses INFO as default +func InitLogger() { + + logLevel := os.Getenv("LOG_LEVEL") + + level := new(slog.LevelVar) + + level.Set(parseLogLevel(logLevel)) + + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + })) + slog.SetDefault(logger) + + slog.Info("Start logger with level: " + logLevel) +} + +func parseLogLevel(level string) slog.Level { + switch strings.ToUpper(level) { + case "DEBUG": + return slog.LevelDebug + case "WARNING": + return slog.LevelWarn + case "ERROR": + return slog.LevelError + case "INFO": + return slog.LevelInfo + default: + return slog.LevelInfo + } +} diff --git a/data-collectors/parking-offstreet-sta/src/main.go b/data-collectors/parking-offstreet-sta/src/main.go new file mode 100644 index 000000000..af006b2b7 --- /dev/null +++ b/data-collectors/parking-offstreet-sta/src/main.go @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: (c) NOI Techpark + +// SPDX-License-Identifier: AGPL-3.0-or-later + +package main + +import ( + "fmt" + "log/slog" + "os" + "time" + + "parking-offstreet-sta/dc" + "parking-offstreet-sta/log" + + "github.com/go-co-op/gocron" +) + +func main() { + log.InitLogger() + + // dc.DataTypes() + // dc.DataTypesModel() + + data := dc.GetData() + fmt.Printf("%+v\n", data) + + cron := os.Getenv("SCHEDULER_CRON") + slog.Debug("Cron defined as: " + cron) + + if len(cron) == 0 { + slog.Error("Cron job definition in env missing") + os.Exit(1) + } + + // slog.Info("Start job for first time on startup now.") + // test.TestJob() + + // start cron job + s := gocron.NewScheduler(time.UTC) + s.CronWithSeconds(cron).Do(dc.Job) + s.StartBlocking() +}