Skip to content

Commit

Permalink
Merge branch 'development' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
felixriehm committed Mar 16, 2024
2 parents 1971bc1 + 00d06b4 commit d2a9068
Show file tree
Hide file tree
Showing 64 changed files with 2,122 additions and 536 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Bloxberg.org certification

on:
pull_request:
push:
branches:
- development
- staging
- production

permissions:
contents: read

jobs:
certify-code:
name: Certify code on bloxberg.org
runs-on: ubuntu-latest

steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4

- name: Bloxberg certifier
id: bloxberg-certifier
uses: bloxberg-org/[email protected]
with:
authorName: Max Planck Digital Library
researchTitle: ${{github.repository}} ${{github.ref_type}} ${{github.ref}} ${{github.sha}}

- name: Print certification
id: output
run: echo -e "\nYour git branch has been successfully certified by bloxberg. To verify it you can copy paste the following into a json file and verify it on certify.bloxberg.org:\n\n##########\n" && echo "${{ steps.bloxberg-certifier.outputs.certificateVerification }}" && echo -e "\n##########"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ web/.env.development.local
api/.env*
api/docker-compose-postgres.yml
api/hatnoteapp.log
api/geo-mpg-institutes.json
api/geo-bloxberg-validators.json
api/mock-geo-mpg-institutes.json
api/mock-geo-bloxberg-validators.json
api/institute-data-mock.json
api/institute-data-mock-2.json
api/keeperx.json
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@ information. Based on [Hatnote](https://github.com/hatnote/listen-to-wikipedia).

## Build

### api

Build
`go build -o ./api`

Run
`./api -logAbsPath=$LOG_ABS_PATH -appEnvironment=$APP_ENVIRONMENT -appEnvironmentFileDir=$APP_ENVIRONMENT_FILE_DIR`

### docker
Inside the development/staging/production folder run
`docker compose down && docker compose build && docker compose up -d`

### web
For development run `npm run build-dev`. Staging: `npm run build-staging`. Production: `npm run build-prod`.

Then run `npm run start` to start the web server.

Now you can open the website on http://localhost:3000.

## Geographic information
Country data comes from [TopoJSON World Atlas](https://github.com/topojson/world-atlas) (by [Natural Earth](https://www.naturalearthdata.com/)).
Country IDs are expected to be [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) numeric.
State IDs are expected to be [https://de.wikipedia.org/wiki/Amtlicher_Gemeindeschl%C3%BCssel](AGS).
German state geo information comes from [https://github.com/AliceWi/TopoJSON-Germany](github.com/AliceWi/TopoJSON-Germany).
You can also use [this one](https://github.com/m-ad/geofeatures-ags-germany/tree/master/topojson) if the other source is unavailable.
There is a separate CRUD website project that serves and creates the mapping between the service data and the geo information.

## Deployment
See on Keeper.

Expand Down
5 changes: 4 additions & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ WORKDIR /app
# Add source code
ADD . /app

# Add public ssl certificate
ADD hatnote.crt /etc/ssl/certs/hatnote.crt

# Download Go modules
RUN go mod download

Expand All @@ -22,4 +25,4 @@ RUN go build -o ./api
EXPOSE 8181

# Run
CMD ./api -logAbsPath=$LOG_ABS_PATH -appEnvironment=$APP_ENVIRONMENT -appEnvironmentFileDir=$APP_ENVIRONMENT_FILE_DIR
CMD ./api -logAbsPath=$LOG_ABS_PATH -logLevel=$LOG_LEVEL -appEnvironment=$APP_ENVIRONMENT -appEnvironmentFileDir=$APP_ENVIRONMENT_FILE_DIR
7 changes: 7 additions & 0 deletions api/config/environment_config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"api/geo"
"api/institutes"
"api/service"
"api/utils/mail"
Expand All @@ -12,6 +13,7 @@ type EnvironmentConfig struct {
Services []service.ServiceConfig `yaml:"services"`
InstituteData institutes.Config `yaml:"instituteData"`
Email mail.Config `yaml:"email"`
Geographic geo.Config `yaml:"geographic"`
}

func (c EnvironmentConfig) ConfigToString() string {
Expand Down Expand Up @@ -41,5 +43,10 @@ func (c EnvironmentConfig) ConfigToString() string {
sb.WriteString(fmt.Sprintln(" SmtpPort: ", c.Email.SmtpPort))
sb.WriteString(fmt.Sprintln(" FromAddress: ", c.Email.FromAddress))
sb.WriteString(fmt.Sprintln(" ToAddress: ", c.Email.ToAddress))
sb.WriteString(" Geographic:\n")
sb.WriteString(fmt.Sprintln(" BloxbergValidatorsSourceUrl: ", c.Geographic.BloxbergValidatorsSourceUrl))
sb.WriteString(fmt.Sprintln(" MpgInstitutesSourceUrl: ", c.Geographic.MpgInstitutesSourceUrl))
sb.WriteString(fmt.Sprintln(" PeriodicSync: ", c.Geographic.PeriodicSync))
sb.WriteString(fmt.Sprintln(" ApiPassword: ", c.Geographic.ApiPassword))
return sb.String()
}
5 changes: 5 additions & 0 deletions api/config/hatnote_environment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"api/geo"
"api/institutes"
"api/service"
"api/service/bloxberg"
Expand All @@ -16,6 +17,7 @@ import (

type Dependencies struct {
InstitutesDataController institutes.Controller
GeoController geo.Controller
HatnoteServiceController []service.ServiceInterface
}

Expand Down Expand Up @@ -96,6 +98,7 @@ func loadConfigFromFile(fileName string) (config EnvironmentConfig, loadError er
func hatnoteDependencies(services []service.ServiceConfig) *Dependencies {
dependencies := &Dependencies{
InstitutesDataController: institutes.Controller{},
GeoController: geo.Controller{},
HatnoteServiceController: make([]service.ServiceInterface, len(services)),
}

Expand Down Expand Up @@ -127,6 +130,7 @@ func hatnoteDependencies(services []service.ServiceConfig) *Dependencies {
func hatnoteMockDbDependencies(services []service.ServiceConfig) *Dependencies {
dependencies := &Dependencies{
InstitutesDataController: institutes.Controller{},
GeoController: geo.Controller{},
HatnoteServiceController: make([]service.ServiceInterface, len(services)),
}

Expand Down Expand Up @@ -158,6 +162,7 @@ func hatnoteMockDbDependencies(services []service.ServiceConfig) *Dependencies {
func hatnoteMockWsDbDependencies(services []service.ServiceConfig) *Dependencies {
dependencies := &Dependencies{
InstitutesDataController: institutes.Controller{},
GeoController: geo.Controller{},
HatnoteServiceController: make([]service.ServiceInterface, len(services)),
}

Expand Down
100 changes: 100 additions & 0 deletions api/geo/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package geo

import (
"api/utils/file_download"
"api/utils/log"
"api/utils/observer"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
)

type Controller struct {
config Config
ticker *time.Ticker
done chan bool
}

func (idc *Controller) Init(config Config) {
idc.config = config
}

func (idc *Controller) Load(geoInformationType string) (geoInformationMap map[string]Location, e error) {
var sourceUrl = ""
if geoInformationType == "bloxberg-validators" {
sourceUrl = idc.config.BloxbergValidatorsSourceUrl
} else {
sourceUrl = idc.config.MpgInstitutesSourceUrl
}
jsonString, err := file_download.GetJsonStringFromFile(sourceUrl, map[string]string{"hatnote-gis-api-password": idc.config.ApiPassword})
if err != nil {
log.Error("Error while loading geo information data.", err, log.Geo)
e = errors.New("could not load geo information data")
return
}

geoInformationMap = make(map[string]Location)
var geoInformation []Information
err = json.Unmarshal([]byte(jsonString), &geoInformation)
if err != nil {
return geoInformationMap, errors.New(fmt.Sprint("Failed to unmarshal json from geoInformation json: ", sourceUrl, "error: ", err))
}

log.Info(Top3ToString(geoInformation), log.Geo)

// generate map from array and use this in bloxberg service
for _, geoItem := range geoInformation {
geoInformationMap[strings.ToLower(geoItem.Id)] = Location{
Coordinate: geoItem.Coordinate,
CountryId: geoItem.CountryId,
StateId: geoItem.StateId,
}
}

if log.LogLevel >= 5 {
log.Debug(fmt.Sprintf("geo information map:"), log.Geo)
for k, v := range geoInformationMap {
log.Debug(fmt.Sprintf("id: %s, lat,long: %f,%f", k, v.Coordinate.Lat, v.Coordinate.Long), log.Geo)
}
}

return
}

func (idc *Controller) StartPeriodicSync(updatableControllers ...observer.UpdatableGeoInformation) *chan bool {
if idc.config.PeriodicSync <= 0 {
log.Warn("Periodic geo information data sync disabled.", log.Geo)
return nil
}
oneDayInHours := 24
idc.ticker = time.NewTicker(time.Duration(idc.config.PeriodicSync*oneDayInHours) * time.Hour)
idc.done = make(chan bool)

go func() {
for {
select {
case <-idc.done:
return
case <-idc.ticker.C:
log.Info("Syncing geo information data ...", log.Geo)
for _, controller := range updatableControllers {
controller.UpdateGeoInformation()
}
}
}
}()

return &idc.done
}

func (idc *Controller) StopPeriodicSync() {
if idc.ticker != nil {
log.Info("Stopping periodic geo information data sync.", log.Geo)
idc.ticker.Stop()
}
if idc.done != nil {
idc.done <- true
}
}
54 changes: 54 additions & 0 deletions api/geo/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package geo

import (
"fmt"
"strings"
)

type Config struct {
BloxbergValidatorsSourceUrl string `yaml:"bloxbergValidatorsSourceUrl"` // can be a http resource or a local file
MpgInstitutesSourceUrl string `yaml:"mpgInstitutesSourceUrl"` // can be a http resource or a local file
PeriodicSync int `yaml:"periodicSync"` // days
ApiPassword string `yaml:"apiPassword"`
}

type Information struct {
Name string `json:"name"`
Id string `json:"id"`
Coordinate Coordinate `json:"coordinate"`
CountryId string `json:"countryId"`
StateId string `json:"stateId"`
}

type Location struct {
Coordinate Coordinate `json:"coordinate"`
CountryId string `json:"countryId"`
StateId string `json:"stateId"`
}

type Coordinate struct {
Lat float64 `json:"lat"`
Long float64 `json:"long"`
}

func Top3ToString(information []Information) string {
var sb strings.Builder
counter := 0
sb.WriteString("\n")
sb.WriteString("Top 3 geo information data:\n\n")
for _, informationItem := range information {
sb.WriteString(fmt.Sprintln(" Name: ", informationItem.Name))
sb.WriteString(fmt.Sprintln(" Id: ", informationItem.Id))
sb.WriteString(fmt.Sprintln(" CountryId: ", informationItem.CountryId))
sb.WriteString(fmt.Sprintln(" StateId: ", informationItem.StateId))
sb.WriteString(fmt.Sprintln(" Coordinate: "))
sb.WriteString(fmt.Sprintln(" Latitude: ", informationItem.Coordinate.Lat))
sb.WriteString(fmt.Sprintln(" Longitude: ", informationItem.Coordinate.Long))
sb.WriteString(" ----------\n")
counter++
if counter == 3 {
break
}
}
return sb.String()
}
48 changes: 48 additions & 0 deletions api/hatnote.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-----BEGIN CERTIFICATE-----
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
nhVX1X3oyxGCK54sZp0huw==
-----END CERTIFICATE-----
Loading

0 comments on commit d2a9068

Please sign in to comment.