From 3f9fb89b6d3ce3ec4d6c377e08b06c8e6ce60fe8 Mon Sep 17 00:00:00 2001 From: raul Date: Fri, 3 May 2024 09:26:49 +0200 Subject: [PATCH 01/33] Configure CI/CD --- .gitea/workflows/go-audit.yaml | 25 +++++++++++++++++ .goreleaser.yaml | 51 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 .gitea/workflows/go-audit.yaml create mode 100644 .goreleaser.yaml diff --git a/.gitea/workflows/go-audit.yaml b/.gitea/workflows/go-audit.yaml new file mode 100644 index 0000000..70c5e4b --- /dev/null +++ b/.gitea/workflows/go-audit.yaml @@ -0,0 +1,25 @@ +name: Go audit +run-name: ${{ gitea.actor }} pulled to main! 🚀 +on: + pull_request: + branches: [main] +jobs: + audit: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '>=1.22.0' + + - name: Verify dependencies + run: go mod verify + + - name: Build + run: go build -v ./... + + - name: Run go vet + run: go vet ./... diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..8e02d12 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,51 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 1 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +gitea_urls: + api: https://git.bulgariu.xyz/api/v1 + download: https://git.bulgariu.xyz + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + goarch: + - amd64 + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" -- 2.30.2 From 75053b0ec5f214653d668a555d27ce4cda058356 Mon Sep 17 00:00:00 2001 From: raul Date: Sun, 5 May 2024 12:46:07 +0200 Subject: [PATCH 02/33] Minor formatting changes --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 099f1ba..135d317 100644 --- a/main.go +++ b/main.go @@ -36,8 +36,8 @@ func main() { fmt.Println(aemetRequest.Base.Nombre) fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha) - fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) - fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) + fmt.Printf("Temperatura máxima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) + fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) } func getJSON() string { -- 2.30.2 From acd0b74fcad038222a34b8821adf90cde9f64dd0 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 08:57:57 +0200 Subject: [PATCH 03/33] Update dependencies --- go.mod | 37 ++++++++++++++++++++--- go.sum | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index dda00b7..24e38f5 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,40 @@ module aemet go 1.22.2 -require github.com/basgys/goxml2json v1.1.0 +require ( + github.com/basgys/goxml2json v1.1.0 + github.com/gin-gonic/gin v1.10.0 + github.com/spf13/cobra v1.8.0 +) require ( github.com/bitly/go-simplejson v0.5.1 // indirect - github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 72bdc9c..01ddef1 100644 --- a/go.sum +++ b/go.sum @@ -2,15 +2,100 @@ github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUK github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -- 2.30.2 From 08fb32b4b221c00f6965e0b19d7c38cb7e566b16 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 09:08:36 +0200 Subject: [PATCH 04/33] Transition project to Cobra --- cmd/clientFunc.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 51 ++++++++++++++++++++++++++++++++++++ cmd/server.go | 42 +++++++++++++++++++++++++++++ cmd/serverFunc.go | 16 +++++++++++ main.go | 67 +++++------------------------------------------ 5 files changed, 181 insertions(+), 61 deletions(-) create mode 100644 cmd/clientFunc.go create mode 100644 cmd/root.go create mode 100644 cmd/server.go create mode 100644 cmd/serverFunc.go diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go new file mode 100644 index 0000000..7754b78 --- /dev/null +++ b/cmd/clientFunc.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "encoding/json" + "fmt" + xj "github.com/basgys/goxml2json" + "io" + "log" + "net/http" + "strings" +) + +type root struct { + Base struct { + Nombre string `json:"nombre"` + Prediccion struct { + Dia []struct { + Fecha string `json:"-fecha"` + Temperatura struct { + Maxima string `json:"maxima"` + Minima string `json:"minima"` + } + } + } + } `json:"root"` +} + +func main() { + jsonData := getJSON() + textBytes := []byte(jsonData) + aemetRequest := root{} + err := json.Unmarshal(textBytes, &aemetRequest) + if err != nil { + log.Fatalf("Error occurred unmarshalling data: %v\n", err) + } + + fmt.Println(aemetRequest.Base.Nombre) + fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha) + fmt.Printf("Temperatura máxima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) + fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) +} + +func getJSON() string { + resp, err := http.Get("https://www.aemet.es/xml/municipios/localidad_46250.xml") + if err != nil { + log.Fatalf("Error occurred pulling data: %v\n", err) + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + log.Fatalf("Error occurred accessing website: %v\n", err) + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error occurred reading data: %v\n", err) + } + + xml := strings.NewReader(string(data)) + json, err := xj.Convert(xml) + if err != nil { + log.Fatalf("Error occurred converting XML to JSON: %v\n", err) + } + + return json.String() +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..05c5819 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,51 @@ +/* +Copyright © 2024 NAME HERE + +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + + + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "aemet", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.aemet.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + + diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..98d74cf --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,42 @@ +/* +Copyright © 2024 raul +*/ + +package cmd + +import ( + "github.com/spf13/cobra" + "log" +) + +var serverCmd = &cobra.Command{ + Use: "server", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + if err := setServerParameters(cmd); err != nil { + log.Fatalf("Error happened trying to set parameters: %v\n", err) + } + }, +} + +func init() { + rootCmd.AddCommand(serverCmd) + serverCmd.PersistentFlags().StringP("port", "p", "1302", "port to use for listening") +} + +func setServerParameters(cmd *cobra.Command) error { + parameterPort, err := cmd.Flags().GetString("port") + if err != nil { + return err + } + if parameterPort != "" { + listenPort = parameterPort + } + return nil +} diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go new file mode 100644 index 0000000..f2c3b3f --- /dev/null +++ b/cmd/serverFunc.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +var ( + listenPort string = "1302" +) + +func server() { + router := gin.Default + getJSON() +} diff --git a/main.go b/main.go index 135d317..991956d 100644 --- a/main.go +++ b/main.go @@ -1,66 +1,11 @@ +/* +Copyright © 2024 raul +*/ + package main -import ( - "encoding/json" - "fmt" - xj "github.com/basgys/goxml2json" - "io" - "log" - "net/http" - "strings" -) - -type root struct { - Base struct { - Nombre string `json:"nombre"` - Prediccion struct { - Dia []struct { - Fecha string `json:"-fecha"` - Temperatura struct { - Maxima string `json:"maxima"` - Minima string `json:"minima"` - } - } - } - } `json:"root"` -} +import "aemet/cmd" func main() { - jsonData := getJSON() - textBytes := []byte(jsonData) - aemetRequest := root{} - err := json.Unmarshal(textBytes, &aemetRequest) - if err != nil { - log.Fatalf("Error occurred unmarshalling data: %v\n", err) - } - - fmt.Println(aemetRequest.Base.Nombre) - fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha) - fmt.Printf("Temperatura máxima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) - fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) -} - -func getJSON() string { - resp, err := http.Get("https://www.aemet.es/xml/municipios/localidad_46250.xml") - if err != nil { - log.Fatalf("Error occurred pulling data: %v\n", err) - } - - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - log.Fatalf("Error occurred accessing website: %v\n", err) - } - - data, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatalf("Error occurred reading data: %v\n", err) - } - - xml := strings.NewReader(string(data)) - json, err := xj.Convert(xml) - if err != nil { - log.Fatalf("Error occurred converting XML to JSON: %v\n", err) - } - - return json.String() + cmd.Execute() } -- 2.30.2 From 8e04491386021e172468c3787e46ccbeac315997 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 09:16:05 +0200 Subject: [PATCH 05/33] Add basic server functionality --- cmd/server.go | 1 + cmd/serverFunc.go | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index 98d74cf..0c7ab27 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -22,6 +22,7 @@ to quickly create a Cobra application.`, if err := setServerParameters(cmd); err != nil { log.Fatalf("Error happened trying to set parameters: %v\n", err) } + server() }, } diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index f2c3b3f..fafda14 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "net/http" "github.com/gin-gonic/gin" ) @@ -11,6 +12,20 @@ var ( ) func server() { - router := gin.Default - getJSON() + router := gin.Default() + router.GET("/api/valencia", returnWeather) + fmt.Printf("Listening on port %v...\n", listenPort) + router.Run("localhost:" + listenPort) +} + +func returnWeather(c *gin.Context) { + jsonData := getJSON() + c.IndentedJSON(http.StatusOK, jsonData) + //textBytes := []byte(jsonData) + // aemetRequest := root{} + // err := json.Unmarshal(textBytes, &aemetRequest) + // if err != nil { + // log.Fatalf("Error occurred unmarshalling data: %v\n", err) + // } + } -- 2.30.2 From 6354a824f9f132bfa91fc24c7fcddd3253987844 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 09:29:58 +0200 Subject: [PATCH 06/33] Properly render JSON --- cmd/serverFunc.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index fafda14..4e6215d 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -1,10 +1,11 @@ package cmd import ( + "encoding/json" "fmt" - "net/http" - "github.com/gin-gonic/gin" + "log" + "net/http" ) var ( @@ -20,12 +21,12 @@ func server() { func returnWeather(c *gin.Context) { jsonData := getJSON() - c.IndentedJSON(http.StatusOK, jsonData) - //textBytes := []byte(jsonData) - // aemetRequest := root{} - // err := json.Unmarshal(textBytes, &aemetRequest) - // if err != nil { - // log.Fatalf("Error occurred unmarshalling data: %v\n", err) - // } + textBytes := []byte(jsonData) + aemetRequest := root{} + err := json.Unmarshal(textBytes, &aemetRequest) + if err != nil { + log.Fatalf("Error occurred unmarshalling data: %v\n", err) + } + c.IndentedJSON(http.StatusOK, aemetRequest) } -- 2.30.2 From 53e4b1acf933b24447eb325b27c590a795e74e4d Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 10:47:41 +0200 Subject: [PATCH 07/33] Fix gin only listening on localhost --- cmd/serverFunc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 4e6215d..3308c4b 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -16,7 +16,7 @@ func server() { router := gin.Default() router.GET("/api/valencia", returnWeather) fmt.Printf("Listening on port %v...\n", listenPort) - router.Run("localhost:" + listenPort) + router.Run(":" + listenPort) } func returnWeather(c *gin.Context) { -- 2.30.2 From cc49c07b1bce8a7917bc7660d3af643b051a2151 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 11:14:11 +0200 Subject: [PATCH 08/33] Improve error handling and include User-Agent --- cmd/clientFunc.go | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index 7754b78..c6ad254 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -25,11 +25,14 @@ type root struct { } `json:"root"` } -func main() { - jsonData := getJSON() +func client() { + jsonData, err := getJSON() + if err != nil { + log.Fatal(err) + } textBytes := []byte(jsonData) aemetRequest := root{} - err := json.Unmarshal(textBytes, &aemetRequest) + err = json.Unmarshal(textBytes, &aemetRequest) if err != nil { log.Fatalf("Error occurred unmarshalling data: %v\n", err) } @@ -40,27 +43,40 @@ func main() { fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) } -func getJSON() string { - resp, err := http.Get("https://www.aemet.es/xml/municipios/localidad_46250.xml") +func getJSON() (s string, err error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://www.aemet.es/xml/municipios/localidad_46250.xml", nil) if err != nil { - log.Fatalf("Error occurred pulling data: %v\n", err) + e := fmt.Errorf("Error occurred pulling data: %v\n", err) + return "", e + } + req.Header.Set("User-Agent", "AEMET-Client/1.0 (https://git.bulgariu.xyz/raul/aemet)") + + resp, err := client.Do(req) + + if err != nil { + e := fmt.Errorf("Error occurred pulling data: %v\n", err) + return "", e } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - log.Fatalf("Error occurred accessing website: %v\n", err) + e := fmt.Errorf("Error occurred accessing upstream website: %v\n", resp.Status) + return "", e } data, err := io.ReadAll(resp.Body) if err != nil { - log.Fatalf("Error occurred reading data: %v\n", err) + e := fmt.Errorf("Error occurred reading data: %v\n", err) + return "", e } xml := strings.NewReader(string(data)) json, err := xj.Convert(xml) if err != nil { - log.Fatalf("Error occurred converting XML to JSON: %v\n", err) + e := fmt.Errorf("Error occurred converting XML to JSON: %v\n", err) + return "", e } - return json.String() + return json.String(), nil } -- 2.30.2 From 212462be3dfe434a448a9e604e4d538558e7ff21 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 8 May 2024 11:14:30 +0200 Subject: [PATCH 09/33] Communicate upstream errors to client --- cmd/serverFunc.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 3308c4b..1e7e73d 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -20,10 +20,15 @@ func server() { } func returnWeather(c *gin.Context) { - jsonData := getJSON() + jsonData, err := getJSON() + if err != nil { + e := fmt.Sprint(err) + c.String(http.StatusInternalServerError, e) + return + } textBytes := []byte(jsonData) aemetRequest := root{} - err := json.Unmarshal(textBytes, &aemetRequest) + err = json.Unmarshal(textBytes, &aemetRequest) if err != nil { log.Fatalf("Error occurred unmarshalling data: %v\n", err) } -- 2.30.2 From 4336c0068be2ffc85833f47eece1ffda540b7f86 Mon Sep 17 00:00:00 2001 From: raul Date: Thu, 9 May 2024 10:07:00 +0200 Subject: [PATCH 10/33] Properly handle unmarshalling errors --- cmd/serverFunc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 1e7e73d..4026fe9 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" - "log" "net/http" ) @@ -30,7 +29,8 @@ func returnWeather(c *gin.Context) { aemetRequest := root{} err = json.Unmarshal(textBytes, &aemetRequest) if err != nil { - log.Fatalf("Error occurred unmarshalling data: %v\n", err) + e := fmt.Sprintf("Error occurred unmarshalling data: %v\n", err) + c.String(http.StatusInternalServerError, e) } c.IndentedJSON(http.StatusOK, aemetRequest) -- 2.30.2 From bef2a46c41b6205b4a603848b4b3014a6f28c638 Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 13 May 2024 07:56:40 +0200 Subject: [PATCH 11/33] Remove workflow file --- .gitea/workflows/go-audit.yaml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .gitea/workflows/go-audit.yaml diff --git a/.gitea/workflows/go-audit.yaml b/.gitea/workflows/go-audit.yaml deleted file mode 100644 index 70c5e4b..0000000 --- a/.gitea/workflows/go-audit.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Go audit -run-name: ${{ gitea.actor }} pulled to main! 🚀 -on: - pull_request: - branches: [main] -jobs: - audit: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: '>=1.22.0' - - - name: Verify dependencies - run: go mod verify - - - name: Build - run: go build -v ./... - - - name: Run go vet - run: go vet ./... -- 2.30.2 From bb2a3adb834cbc8951f8fb0ff61be1cdd8c34563 Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 13 May 2024 08:26:11 +0200 Subject: [PATCH 12/33] Expand JSON struct --- cmd/clientFunc.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index c6ad254..86202e0 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -19,6 +19,18 @@ type root struct { Temperatura struct { Maxima string `json:"maxima"` Minima string `json:"minima"` + Dato []struct { + Valor string `json:"#content"` + Hora string `json:"-hora"` + } + } + Sens_Termica struct { + Maxima string `json:"maxima"` + Minima string `json:"minima"` + Dato []struct { + Valor string `json:"#content"` + Hora string `json:"-hora"` + } } } } -- 2.30.2 From 281e194bc526c323d8074ddb883f060cdb59f2da Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 15 May 2024 08:00:18 +0200 Subject: [PATCH 13/33] Expand struct and remove client() --- cmd/clientFunc.go | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index 86202e0..a6fef06 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -1,11 +1,9 @@ package cmd import ( - "encoding/json" "fmt" xj "github.com/basgys/goxml2json" "io" - "log" "net/http" "strings" ) @@ -16,6 +14,7 @@ type root struct { Prediccion struct { Dia []struct { Fecha string `json:"-fecha"` + UV string `json:"uv_max"` Temperatura struct { Maxima string `json:"maxima"` Minima string `json:"minima"` @@ -32,28 +31,32 @@ type root struct { Hora string `json:"-hora"` } } + Humedad_Relativa struct { + Maxima string `json:"maxima"` + Minima string `json:"minima"` + } } } } `json:"root"` } -func client() { - jsonData, err := getJSON() - if err != nil { - log.Fatal(err) - } - textBytes := []byte(jsonData) - aemetRequest := root{} - err = json.Unmarshal(textBytes, &aemetRequest) - if err != nil { - log.Fatalf("Error occurred unmarshalling data: %v\n", err) - } - - fmt.Println(aemetRequest.Base.Nombre) - fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha) - fmt.Printf("Temperatura máxima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) - fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) -} +// func client() { +// jsonData, err := getJSON() +// if err != nil { +// log.Fatal(err) +// } +// textBytes := []byte(jsonData) +// aemetRequest := root{} +// err = json.Unmarshal(textBytes, &aemetRequest) +// if err != nil { +// log.Fatalf("Error occurred unmarshalling data: %v\n", err) +// } +// +// fmt.Println(aemetRequest.Base.Nombre) +// fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha) +// fmt.Printf("Temperatura máxima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) +// fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) +// } func getJSON() (s string, err error) { client := &http.Client{} -- 2.30.2 From e70106932db021d722a75fc78a8c7064f5bb43e7 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 15 May 2024 08:35:00 +0200 Subject: [PATCH 14/33] Use dictionaries for requesting locality weather This is the first time in my life I've found a practical use for dictionaries myself --- cmd/clientFunc.go | 4 ++-- cmd/serverFunc.go | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index a6fef06..24f231a 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -58,9 +58,9 @@ type root struct { // fmt.Printf("Temperatura mĂ­nima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) // } -func getJSON() (s string, err error) { +func getJSON(codPostal string) (s string, err error) { client := &http.Client{} - req, err := http.NewRequest("GET", "https://www.aemet.es/xml/municipios/localidad_46250.xml", nil) + req, err := http.NewRequest("GET", "https://www.aemet.es/xml/municipios/localidad_"+codPostal+".xml", nil) if err != nil { e := fmt.Errorf("Error occurred pulling data: %v\n", err) return "", e diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 4026fe9..27d96dc 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -8,18 +8,33 @@ import ( ) var ( - listenPort string = "1302" + listenPort string = "1302" + localidades = map[string]string{ + "valencia": "46250", + "madrid": "28079", + } ) func server() { router := gin.Default() - router.GET("/api/valencia", returnWeather) + router.GET("/api/:codigopostal", returnWeather) fmt.Printf("Listening on port %v...\n", listenPort) router.Run(":" + listenPort) } func returnWeather(c *gin.Context) { - jsonData, err := getJSON() + codPostal := c.Param("codigopostal") + isAvailable := false + for k := range localidades { + if codPostal == k { + isAvailable = true + } + } + if isAvailable == false { + c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported\n") + return + } + jsonData, err := getJSON(localidades[codPostal]) if err != nil { e := fmt.Sprint(err) c.String(http.StatusInternalServerError, e) @@ -34,4 +49,5 @@ func returnWeather(c *gin.Context) { } c.IndentedJSON(http.StatusOK, aemetRequest) + //c.String(http.StatusOK, jsonData) } -- 2.30.2 From 8f9239e39258dd81da47268c16cc33ae27efc7fd Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 15 May 2024 12:26:21 +0200 Subject: [PATCH 15/33] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 914891d..cb4250b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # aemet -CLI tool to check Valencian weather \ No newline at end of file +API Server that fetches spanish weather data from the [AEMET](https://www.aemet.es/en/portada) formatted in XML and translates it +to JSON, optionally allowing to render the obtained data in a web page. + +----- + +Credits and thanks to [riccardomanfrin](https://github.com/riccardomanfrin) for his fork of [goxml2json, which allowed +this entire project to work properly](https://github.com/riccardomanfrin/goxml2json) despite the AEMET's inconsistent XML formatting. -- 2.30.2 From e0a8c745d5bd406b2817053263d9921f48e98353 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 15 May 2024 12:27:24 +0200 Subject: [PATCH 16/33] Fix JSON unmarshalling breaking on XML arrays being rendered as objects Due to a problem in the upstream goxml2json repo, I had to switch to a fork that allowed me to specify which children should be forcefully rendered as arrays, as some in some instances the AEMET's XML would contain completely individual values that are usually arrays, resulting in the JSON translator interpreting them as objects instead of arrays. The amount of effort I had to put into fixing this problem was mind-boggling. --- cmd/clientFunc.go | 42 +++++++++++++++++++++++++++++++----------- cmd/serverFunc.go | 10 ++++++---- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index 24f231a..3639c36 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -2,7 +2,7 @@ package cmd import ( "fmt" - xj "github.com/basgys/goxml2json" + xj "github.com/riccardomanfrin/goxml2json" "io" "net/http" "strings" @@ -13,30 +13,45 @@ type root struct { Nombre string `json:"nombre"` Prediccion struct { Dia []struct { - Fecha string `json:"-fecha"` - UV string `json:"uv_max"` + Fecha string `json:"-fecha"` + UV string `json:"uv_max"` + Temperatura struct { Maxima string `json:"maxima"` Minima string `json:"minima"` Dato []struct { Valor string `json:"#content"` Hora string `json:"-hora"` - } - } + } `json:"dato"` + } `json:"temperatura"` + Sens_Termica struct { Maxima string `json:"maxima"` Minima string `json:"minima"` Dato []struct { Valor string `json:"#content"` Hora string `json:"-hora"` - } - } + } `json:"dato"` + } `json:"sens_termica"` + Humedad_Relativa struct { Maxima string `json:"maxima"` Minima string `json:"minima"` - } - } - } + } `json:"humedad_relativa"` + + Estado_Cielo []struct { + Test string `json:"#content"` + Periodo string `json:"-periodo"` + Descripcion string `json:"-descripcion"` + } `json:"estado_cielo"` + + // Prob_Precipitacion []struct { + // Probabilidad string `json:"#content"` + // Periodo string `json:"-periodo"` + // } `json:"prob_precipitacion"` + + } `json:"dia"` + } `json:"prediccion"` } `json:"root"` } @@ -87,7 +102,12 @@ func getJSON(codPostal string) (s string, err error) { } xml := strings.NewReader(string(data)) - json, err := xj.Convert(xml) + + // I am in tremendous pain after what I had to go through to get this damn thing working, + json, err := xj.Convert(xml, xj.WithNodes( + xj.NodePlugin("root.prediccion.dia.estado_cielo", xj.ToArray()), + )) + if err != nil { e := fmt.Errorf("Error occurred converting XML to JSON: %v\n", err) return "", e diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 27d96dc..fc1158e 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -10,20 +10,21 @@ import ( var ( listenPort string = "1302" localidades = map[string]string{ - "valencia": "46250", - "madrid": "28079", + "valencia": "46250", + "madrid": "28079", + "barcelona": "08019", } ) func server() { router := gin.Default() - router.GET("/api/:codigopostal", returnWeather) + router.GET("/api/:localidad", returnWeather) fmt.Printf("Listening on port %v...\n", listenPort) router.Run(":" + listenPort) } func returnWeather(c *gin.Context) { - codPostal := c.Param("codigopostal") + codPostal := c.Param("localidad") isAvailable := false for k := range localidades { if codPostal == k { @@ -46,6 +47,7 @@ func returnWeather(c *gin.Context) { if err != nil { e := fmt.Sprintf("Error occurred unmarshalling data: %v\n", err) c.String(http.StatusInternalServerError, e) + return } c.IndentedJSON(http.StatusOK, aemetRequest) -- 2.30.2 From 421f37ed6218071d88b1f744f56af9f30144cea4 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 15 May 2024 12:37:31 +0200 Subject: [PATCH 17/33] Update dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 24e38f5..954255a 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module aemet go 1.22.2 require ( - github.com/basgys/goxml2json v1.1.0 github.com/gin-gonic/gin v1.10.0 + github.com/riccardomanfrin/goxml2json v1.1.1-0.20190801150129-10f0ac9a1fe4 github.com/spf13/cobra v1.8.0 ) diff --git a/go.sum b/go.sum index 01ddef1..dc8b43e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw= -github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= @@ -54,6 +52,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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/riccardomanfrin/goxml2json v1.1.1-0.20190801150129-10f0ac9a1fe4 h1:rCNXLdPiD69nJU1XiMyJ+ia0A40wEx2RXxKzhnmGGyw= +github.com/riccardomanfrin/goxml2json v1.1.1-0.20190801150129-10f0ac9a1fe4/go.mod h1:RLEQpK1Wk5+A0V1SfATfoPrwDiZWbbVMChJ0+8m1dvY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -- 2.30.2 From a8cb38480054471f57c3dc5f85afb6b6afc77118 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 15 May 2024 12:37:58 +0200 Subject: [PATCH 18/33] Add backup of functional goxml2kjson fork Again, credits to riccardomanfrin for his fork which saved my life, I'm only including this here incase the fork ever goes missing (https://github.com/riccardomanfrin/goxml2json). --- backup-goxml2json.tar.gz | Bin 0 -> 75375 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 backup-goxml2json.tar.gz diff --git a/backup-goxml2json.tar.gz b/backup-goxml2json.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e6817d64e040a1371af6a497830ea96dd614b8b1 GIT binary patch literal 75375 zcmV(vKOsZ_6(zG1aLjiS^OiO>od`zEj=_dd9X z>i?$CbT$8DZu?^%ybHMq&8h$S`oIaUSF3l;f2~rgtj&M5Uafz_il2{!yPyB^`CnM} zr8VU&mI6(4<_kXvSx(#0MHo2VROs-ELILl(2JcfRoJAwk_U8qF;GsPW3RFK?7Azrn z5b9c9)2?7#1#7YQ2j2sBcEtnX_+CpdnMIv(4}=X6w)EG-5u6>2{Ky*%Hg`PN3R`-uY8D%f{aOP$z%uHsY?iBy zz1kj@SvLvXeyOrwRAn(-4IrVcAd!2Z4a^z0^zweA(WsW|`#M`S2NA9UeW^am^-5W1 z5%AyA2UpyC**}F(L{P0a8c=oR2xsK-mL3KX*HZ(cGka!vQ!Y4QukY3C6~G)ia~M-= zz5oJ>#l~)Nf45v3mP^gbUb9;Jd6UX|X|LRy_eun#yWvzg&%}JaaDnH;xzb zKoMH0?^UaxN@%5AY3wz&5L(+UR)(cYv$EeTS2hVPH7cb>qf{O`G71pGiDD;#3j=F1aqM@(nR*0oJK^%3>&%@n=DdKc%h!qTKO0%W zK>=S-;tL@1&)#cVp}>Yf-4WSf&<+s=T(EFvg-qIIHuj;w^Fsz27X==(yd~p7;0J=` zu$W+BFeZ_k*Cvr?rx4~s_C#s0In1+<+K$vy)@(8PX7>1XE(ERJ0ZJ4V|9o`7 zd!e=y_&&iu>OtMdTWtsR8wij@$WI`ZJ&~=U1ixAg5UY}$vM1PApIcOt&ucq~q=juw zMJ;xfk7+b}eg}KV0T|MUa04E;LqIl)LVgzTiF1=93#I3i`a>V&+6&XQWqnI$Z@`!x z!Xn_$B`6ovy}_tSMOQCX{VsxTwe`_Plzs#s6Ie4s(e}oj8{3V>JfT_qAD$3{A^k{d zJa#&iZE|paAzOdcl@jAnCFWYc{p~Xgj2_OoeJMp*_;b#zxWD%x`=F6*W}yY@Upj*w&T?o^n zJPe%qrj&*j7*wR5W>4zk{pa6VxV5@etN;9)G;Y1Ysu*EGh;}A^z(6>K%{?n{d=htD+?kXgl!d7@ULYRx zp6|T~ti{Z+b8!pP@j$*4J^m%K++4UqXvV!8Y)S^@7G(FYcBTK8Pip^1E;$cA>(-B+ z`}cn(*zT9t_J5T+6n)+QeSzoR{oiN_)*p9&j*tIFo`Vunh{8#Oc6hM-wLR96a>K3z z2Uoox2B$sdk3N8Yf&=S=X<*`8m`LVLTVNxF2*&dX{4}l(V7h$=@&Tfe>@Q7jxrN0f!IrI2++(xeWie=#P+LTM@ zs$K4S#eu;VZZvfOiD{bhHP4>J9RTmNran>yAFrG2+F%*(n%Pf-t?}5T0{MQM7~sXq z^2P+9?d9MvEX?+JJoIB?IT_I%wYT7E6l86}=8VhPjW_SipRgVnJwb0ZFJ};maVCh)#2Nlai+PSq6vlMO8X^q<2|;TE3O?ns6C-L-Ll}d(6)JDO zs2t2855TLlLfB(j_DuSVa;c-~bsdkJ5^&}SHn%Pn-%5mC-@gP%*9k*d$3P!YIsy86 z#y#+JN6{1pd1}ylNbcaJ18*=)0u+rCD4-4aFOd^~cLY2kaw${em>P>@Et-!&aHxs! zrMMw%Kz=&A^!#h0417%RGrh+loBM(6VeCvD&^_CqqvIxrg{Clv9s>@<+VIAH-`cdP z0rcdY8O*(Mnd61o$@9#vIP~WWm*3>_Qo2^%eWc!yCJ;6@1{azkbFg_GkDU;FTgyec zB{^m}zy_OIT=(J4#B0S9iRdyX01?I9uO$_ zfJ{eB$$CpewnN$5aZuNa@DfVTMluk)-w1|E=~^T1f^Mtgm42>3KY>-kat9k&dJXPW z*9-l25Lip>V}gD@)+obZ69KE71FC4=GPK6NIyZYgv-LYdOgHq`=|6H?$ax`IP|yD}?ikMA%k zs)1UO$zBj1xlZQJFVN+nsE}NMW?{Gx%|c=7yD;CTA504(unX}5R7g3l;J1D+TD4lE z+N{;fd*7byH5wFyp79%CLob%fm1?cN*Vwm4_Lxs}Md8|Tt!R_KNFrhxMH5E;koi2C zOu+1GJ5VI)evU*x)*s_IpgS2VQ-Ja+&BAvy(I|T&|DD`313H?mbLZkacs*d~R+;_C z;{)zaw~tpJdg}ck_5LjN{^)V6*Ca(djI9894pJtC6d4#*nnr$O^F@g3DtqgZkMI^(c-agV z#%u1n_ohNv0VLa(Xc~ndQBMd3)RYpv`#tKgWh5q($ykns!=ARNpNBYtsd)XgTr4-xRXpkT zI&9E6Yxmp3)4rLi-p6Vzf6;HBz1)BSxmnVbj=-|BklQoxRxQ_EqL=B6@w6qdY!E8} zb1i3D$%7#gfKLVs13H%1esCEBof#lY4(L3FT|pj3Ad={|S{Uquq93d)YhVY?B2;Kl zeC`oU@?cHY4Urp38PO8@vbmIZ1e+V^2Vdhs2)=;n_PyANbeD?j0q3-4z>;9pQz$*6s%&QxKWEA{q%Um2&I|I}#XkHB-{^_-%x<@fbaqRKfn^&n+u} zREo-`V%RIi_+&Xs0m|)DOr~6hh}OF(y~xWIed`jZmU*mG&GKKvZ-3J#75`gUf#9D% z`{TjmJ1cKQBx|bMLXs zrtXaO_u9@`;Mg2XC$4XW^=cYofprLU1lArGAz1KycMZ*0)*)Cga$P!vpuC5PW%G}> zF_@?70Pa?iD-GN|M5|rGc6zgwo1|vzw^5me*h1hd=Z23Trtg6PeTc$OAaOg5uqFu- zriCO?jwfaXX*-{m7qkP*)PXa8RxKYC;0M2m%x9&FS%EV7fyIirXBdV!D8ye_6Vvnz zUk(b%C)B-1$xKc?+phb=0OP@DU<`3(+c*J5l5yL%adMtv5SGsm0yc^U9moTGynTk| z@_;=Pbk02onKf9~3k`ORQ(`b2-u&iKc}N%yrrUnxj^*((+HHnZq84LBK#_v|D#^g~ z02vv@Nitu1b7(ZP+#FG9;_5DPd4oMtO+TsQD8?c?CnK?Hm>k+w%LY?u&F&Fk0NE5X z!ZNqOR%~lSbGvFr@(L|!t{-fS^S^4VL>XEuiFmNR=eVopv6w@9FOm2xg7mSuvKN1z z^!#y=$e6UhQZV#tS^w>#p_6RrzgIY9GT0vx50V4@cT0#g*k9JXkWKjv)+D{y{QV^7 z4;hueNN)aY|1ahLpL7p9y+P;m_wj)LUoP&I*8Kl+z4kT#`-?mnUVPOZvJ=n&4-`KK zAK(xj`io`YOlRP_*?Cqjmg?-e1$K&i?8Q>J82&!PlT-?pVCV+JIHM(-29`%!F$p-w zb59I2O+myd5n#y{;Os#Ie-v7dhmN4dZ0Jh!@gNk?2!9e@W7G=6Mxa2x?NDSX&U&;k z-oqvihK_Q09I6jg3rtdkPR87FHOHf)(zuGwcl`)6XVI?Oh{FK?A2S-`Oed_Sq+Qtq zowOqVLIb3c;0DoYusL|A6a3}G)*>3Yj+hx3I#TEH1`}9F!$}9h1wUYdyDs>`3kT@- zCmHJGVKMZwzyS@_5Xq#1nLp2R;s{ts0uQ>B;aG}=QID8!5g)}*eAo4_k+md7CYqXt zb8U_MD^9$~=>?;U){jWQ30SOVRaK!ib}`N;2aICF%u2BnVE3R!Ug&_`#?2%4x6Y;s z{lDz6!RhhvO}pP=-2pr6pZ?fA>Krk>J%Dd8jc>Zcm#43X3?TaL-tZ@Odd%9rpV-G*$ULSRPFW7Tv*EPmW;p>xjpPjw#pPddm(ESmh?R9&{edwk0 zs?!^q&?}U&&X4fH1~1zuC)kzNeht+3kv?{KdiGPl`{L!0y*xcR>OkT14p7#9e$tU$ z0aJ%3?d~gs9kpMzUv#L|DPZYq2qvjxZ(eq=2>WZpe}}{FX%E>rJnaqp@M!>-{bADd zO?S{SSi9dH;D8+WPhS}t4kxrYB_z}1#cGPK~0ImTxMiygW z^J}>8|Hrd=|LaTV{}o}$uWsi5RlctOU*h?+_#f^B*3$rGLFjE~E=kt9Ri-qrWLw~ykCyXB~2&Es-;9`ElT|*Jq=b_vhfw9V5NlHvt;8QkT1%TRRte|CJX*RmMmUjSWR1V zT~j~aKA1*3UZv5-+>A46v|}|+>iU)(2y}`)eiPC;_ii@zlLpCwO$X+Ql0l>Fi_pi* zfUq}E8yg9{@bL(PwgIs7x-#z(v+b$3VAY`-d4o4IH77XT@r|q@Jk_XR0xCN1h8_%c94dK zwq?kQMxPKvaWO$y?|r`ME5lCpqTms1cjG}t@EJW@FHFS(afX(E|4}yyR5&aZCilv&}0?GUXwSG$(kTa)XoZ zI^K9~{@WPP7>_rm7babVDvyNvRmG`fctwz`NDmGbtBhhvb3QY#L(KY;*Q%w~uF2e8 za+c~&=8~lDbS10mN)*;sUL-mBG>=zV`1x&FqCjSLfKy9}z%WW2NKnU&!cgh@V39DV zh=NEMy$CGFMb8Hig89Kn6k$u{Igu^JJbcW+pZiyQYyvbkc(Dnrq8-o&ab>{z5M08K z%*z)T`tj^AvSctxEaxNN6^5oVo@6(Mv|2V;v#B3ou;j#Gu;uR=?95;TgZ(U!0yY3> ziq@oP4N%2GwCt++lKo7m!PYLo3@_Ok4Y2p_&CCh;JH)}&8n7<0TT)sv9SCiluBUHO z2suNT?<%eD69+{AzgAJ0@E=e4cfV3H&V2=*NOhXbMOg->5K64)r z?Ej0^@_PKgQm=fC|9_F^TZWf+983p3l`&)ru7h>LNVrWF=V<9!b9i^1kzlz2C_(}) z^4b&AhyN}3U-+8#Ej#vI{975?JJ_LXlum}F?Z8Ge1r`$x+k)H91Tcad%smV?i^4+f ze*n*Ut=_o$3+!#Y60Z#eu*)IBl5q)xPg8Cp~E-xk+Q>AxKx7k};?zz6RCR?7AD{J&Zms=w<0 z7kGZh-+vMxSUTfx|BF!gtZgSPagMx`ob|L_fZeb<;Kjy?~#Q-U(l z@nR#mvG|8Quyq&U=$5ACW&S=2SuS~N6J1G4Do_4)N|KBb=S}=CdtUri+$BqCt61OvdwZW}Aia0y%y#C?nKNf;fIAknv1;;Z)#THv z$){B)pH|^qdL?KHp)essEuG!f@JSS#L#|lN=H~=UmXj1BErCS~noe-SVv0_}7+8t@ z$t%%Vu#~J;jzfnxsT}?$f9XWuIjJ13Ij#6!c7j8`_^m!M3v&e|!GcoE5m2h1{~xXY zcUqZZX)3?tA{EQCWg75ZF3*-M{ahoq3kICz7$ugHng;;vRwyhlYOV64vTC}-{XZ*P zjtk!JGvFe87ni^PM_Mr@27X@{qQY0IIpz0Jo*kqA7iq!3H%2!6GDsMY9S%iA#N0L7 z3k{V-S!y3Dq%*Y+6{R{Mhe|<%LX8%sNEdyy811TSv;rtuy+%tx-u_C%}kU4?>l`$3ogD3(xw{wNkH4tTF35YPh_h*8u?u_0;H=Y2ULaO*aL(R-+1xp2AX%_V4Gkl{i7|4%Uo)wHm&JNH@g4qv8FjFQm z=@Uq6Qwv4YUs@HsyI?gFgjlj=nPQEmSb?|j6TRw8e-cN*_O?6Co+=SIUgOalQqX}X z#}*uVHFZKne$ltIq*>q-<7xRo*zizMS8QI+z|=eoUr0`%MX~VjpmI;Dv50Vu>}Ck)S!``xQ;uq^->{0)5F0!Xig)6CXL5gYiHm>~>zvl-$2yCRQ@(_~ z30e*`p+VEH+y+pR?FmM}fC3TI9S$%dE$)t zo4z;5?d#Xxp+C zZJ;Ay4C*QEd%Rq#4=rU192wR-Wsk7-B!fJvEPt|GyQxn@+feiOsH&(s32s%lmjSv_ z-)idr;ms@mG<_8&>6_=F(+k0fK$`frS_ppjBotyBlu?38>XyF9VXrqS$_b-`Vrv*!ZMSI~FofLo^CyFerWdg4AONMKdr zB5=_`3(6b)ycNq1CB4vt zZjZ}ssnBvRr=yD6@GJZ^({knZ@u18rdMma=&&c3U0_}6VIoNA}Dm+x<6k#8*nxP1L zR55j%LY3cSC3_x?YC;;#P+0I@vYPQomKWq5+;<6*iHEXY(3lOfn}PB4d*k0yZ-t^A zB8EZ!A^4NFTmCx~@W7-ZYDqb$-aG??Iglrb_7F79H-AHv4;5uMcR+9)k-e zXNq~W)&rhIg+k~8lkuSX(A+qm3zYwOk7y;VIJkppb)}VV~#SBYmMKe=?K*X(9oG8{{rq0bV zr6KC*Q1mGPBg2|O+Qjwb1d>X@g_VpN0>cac&__U5c_+gZB%fm7A%-W&E;HBgq&kqj zOLt+M&CWZlNW3uskcW&fTm@3qaCreMA}s~j%*FW8QIH*>A(HYfE25FtwAn3VgV`TM zy%9FpFpM42qhrtqdN3Y`uBk5kwbr*Lt73Zm%nbj^46$9PzVmZIv&NXfEsgXFEvf?E z2hWp59z07Bn0n7tpp~CAdX~`hRBKgrPgT9D5+^FivQjj*6Q~Dazm+0rDQh*+45BVA zyB?Y-*vU`9p6YZB7N?~U1=(QugOVidQ{6_KF%o+LV>S_{M|?x`DyD^37^9M`Qqg+O zNbrb<08;l@*B)(+j5j|~Q_IpAjMmZA_C}DktdWzF5gKzvJAqLMu%e9#;JFm6g8!Fl zV8K#3r-D6j+5(IJ7k*T5If~yiPein=1{b)4dM(noIh77x7hD} zH9$$_;9n6)D*HdgASpV=?4wYVkQrzK4N=60fKU3qyjKjx0NsLm=uRo+R2YnjK^`s? z;{kwR^QZwU4OE~JStjXbng(r%hRw*I<5l2>|Cee-f6cbQa%?8Wpd#2=XDv}CGDME; z-##S&AAUmV|6gngP+juh#H2v^PrS*P)Fl6DjHmwbzv#(URMeMb6f)!Gr+v>v7L*Q% zhYx>Se?K$-=on>i4J|cN*Raz`{lcHJiz=mW0jPykqVacLfTp$~<4vI*APYEk!J9FN z1_Gp+pAfBVw}wUXQbYkO122uV6{5dHUats_q?>^q`P8 zA)(gwPoBX0p9F~im+((CCh++mU;lygPq;1&i$K`xi&jUS_kTvk{a->7(Ep@_Ci>qf z&oC#Pd955o$q?*bm!~R?XZ!y6cy()r$!2oy&zz%+N#=FKsz`82PrStF> zo}3nSaPEvYYS>jYI*DWPqDd|S1E8S^H?0}2c*yyRoW#)03|!E91qJ^>ohTzP^hZWY z6Chsnxg$3pKG6-1D)ia~(It|jO6(b27BF|nJORPltGDvbs%)CKH-$h>z&3li&qH+s z#+yBM8dgR?zdc?TDNL))a|&K^=A~oPyGm3s0I$s!2s?~j#i+nGpCAVX23vQypuPwboF?^^08sYU;;O8)d(-If(Qt7Wte?84 zhw)YTl%hbi^x_bF=g`ih76|5Nj_)Rbeu9tsueQ78uF!eW`y&qxFo&Z;euAO~Xlf`G zH3$H9Ywq);k#ZCG@#uFDE_F%HG9ENeJUr^*g51Dg%R?$kz+lv6yg7tkqjNpHmOx2| z(++(^=ihdE`5>Y?TPh4s1XeYS1@%)!qvhdMv)wIrmJJ#-o+1c|*1T&NIeMv(bNaQPkp4UL4z*@%cv9y~Xg zJyoJDym&Jgj9)Ic=4dluB=CkBV*iPSL^TNLE-$b5I)+w(iVO)QbcKqQ1F9HII>UXG zH&b45x}l5c@1hFSmXQ8|cSjv|M(5*nm0Z8vtQ;~#Mm6?`j4D%t;wya)Xgrj$@m>Wj z0!U2V!vkncX3=T~PZx046+v4lQd%wGD|UT?i`EL)J}?NO+G2-^C)2>Ak9G&0Z2^vP zW&sN@r-r1j@Rzvl)0WY1h&$rx0I-_Sy0JX(W;4V&z#E@5$E+KEbBw^OKo=6P@5Q0tnekyx@q)?Qp?OiKy5HgHYzPI|x0A zRc?EF3&pI#E-x=TIziy*XA?qdPUI~y*M=x z2{o6>!0X_2)W_QJkh!(4n86DWZ-8>J6 zK(gW~BWQ5}c**xjKgIhfAqh-L-NR1{NqlNP6%Hn$P z>slcoe~yP6dGBQGD_(rMYB?kdyxZ(SW*2s2sH2=r&fsAK+2|1d(~0L)uXxS^dpjo4 z7bX6rBK0^Q@2QfOznTGuCPm?{gr{F{#m4I;YQZT;h=2JgQhQ|;7ahrFLd!+M$d}Nb zmtfB|Tn2YzRzct4h+4y(K>T@j2($8KzVdQ9`)&^$%?gL+GgVS9yvLYp@}3zR|3;nf^(U{mGks(lCy+tqS2Z(n$N>~J)j$7 zWPbzgE^&`7Pg6l4cB+>{_G}N;{ZCE3xiC!?F6Dw48{&IXcQ}Dk18kbr6K$gn>QI!mE?ly3A?|Id6=u(Q-Fdou-<0eYIoQ?LbeYuhC|YW}Cb*A?g<>Dwc8KTzQ)`MYiplOb zFsB3unElQhtmUGlp6kXChS%xRxk(eeRZ$vMIN%I)ngYVuUi$R5y{6kuBL86M>i@2Duk@cgim{m^$ z!v>H~Is(B2ljdM+Sk6WBu-9opWDa&jMlxg5xO9;mWY%1Gvyl45iLm5;)!HdBYc2f} zKL>Y>9<>>mRFrDMN}^5Ki4Vn5NK622sJQpz%+shi865jYcnW+|!+zafv6uUz0xq+K53gH9F z)w=Q4gID$2d`Wv5UCyZg}-)Jj)N?^ER6#}X|~|P@}ij{e1&7F8ZH5<$i53^paP(fw@S^~9OiO%PfCO0(Q6aG z22ikL;EIRwUL-SIpV70mlW-D(i)@?Z{OF8=nBP@cp59$yoDcE*?SKOgIeznIxJn^= z_ISJ1XQqA8Gd%U<>ygQeu4}3<@?2lwfDo;YRckas`>VJiEAk>FdWH%-!4Pf^@srJv zA&qL}Ua$)d>H!06l*m`W=}%-)6|Y3XG&&z>PgJE)B}74p74e0os(gao8O2D85>^H` z!tRVSrKW;W$pkB#EWaF+Ij<+Ss3^857U+s1mWp^nB;|rj!Q0eS3j?XpS@v&tlR?LS z&r^&34Ek0>JL73>3^A&_8+MKl@w<(HOYS)kf?=!lezvrr`^S6 zbN$SO({V``EovE2)Y7dBsIegHS;iB}z+aY#`6aspnmGs(GK(QsA`=k}F>P>|Ha)ENk+X74hf zRuzD!6=$Fh6eu-Y0H{i-4mHIR{C4MIN8qD>vH8d#UaMsz`v!0mkW6M`8_XP~}lHpjWTh z7>z1CElmagUAPML2n&)!lA9OI78R=XfS491#mbuQBu~m{| z3h&W#Tpq^!%^yJJ36PQe-3#i-aWPFp3X;qTD0QK`mR>3xFoHHXSh{g?Kgr?1PknE& zi>jcq7O`yp8hv>JtHb7lDrDrMt?Z>D15#plme4=xY!Ot|p^qjVla6P&oIDCNN7HOl zlS(DL&`lyQ{a}e&9}0Ct+7fi>5~?w2pf={kVASZ%#_rWiyikoUFEmewUPdOYQltv0^uiB?l-(6g2T3JF zDnic5r0vP}kM3j*faQG++Inspv3klm)%Sb?#a4IySDDN3e)YGsqu!SqHXe6ae{(~ENQ zvrF(6L%Au_W0Bd$m;1~fs}OQOmUKN-@-~UPuSoMJK!_Ip+yI3;3#U48Ut0^QKQ*PX2r`}^++s%QaX7SupZrG%X=ObHnPBe zP@Xq?9Co0(1eYMZpO$Ib1h&Y6vEotN#m01D%m^SrEnI9IX^NPTW( z4=ORRAo;-)f=;!!LFJjM!7|2&s)6%#*hUb#hnt16uSg}yATwlOO0)`Pc<2(;C}NS8 z4l7lBr$b9Imf1*9#Gz+%Eki1^Q`S(HL(x zJEXe=&5(HW5_h+-fS0KGn<14<|gS;5F}sG?058dV(7K<-Ie z8weP0)S)g5I&Xyq43If)5DXFZ4q^rsI&A>&gQY_(Y;r(S_K{##m9!7C9?;nk1J&c8 zqt=^jvq5`e2Aw`MMxew@pepHDT&9Jx;yW%u>DHevIV~Gm#w)VQfp{n?iYA+Vh1o60 zheouPfW@OBa|gQUlIjG-LrQAop;Qgz!H59wPt44mo~_VS6}@hA>2$g}6GM6|5z9C| z@&BOTkPBXzNl8d$Tvo_Tu>tQwjd52U!yt5jK9(gj%I*?3O#CnA)TXW@Dwydj7F{_Jc?f((GI9Lm8mnuKL~ zV;P&>i7OU%J#Q_mj+GTRZPb3D)EN2&f1*JkED=hp#hn!E$wsJXycIwj0o4ez45XM3 zpt_WY&Svl=0RS@>%VMTbPQq||%;mhQcTmMcT^Ni4Tw4;-4M-gFKr)(+CQ1Q9=U@-! zpyvoO>O8d))N(S@tmq)PWFy(-?kO8%3T_NLL+k0@$||ATKFVW6B2YUiREg~0zb&Er zXP?mW--~wjgHIjzKS@SIp!_#6F0RS`yHTF{-v5-L?xxRac9K;|H)eA=JB9Ed4}*(s zkRVf8D`Cf(*jKiDQ!_c3ryMZ)j)hj2u`j~c9UN>4bP!p56gLsKP- zGG6cB7D`zO>^fS4`A}*cx*30~5!Cv(?naoQc4Xa1-I57|*@ph=;S2^Q(TYkD9PF1D z>O0+W>)UR4pRh@(5 z0oOIy7Pi=BcL9oK=+z*{6AN9jxO z2klIzM$9P-V&rbHkx+Alm%|sdwEQ;(64XtfK=_%|3)C81sW=ZvNdgH6v#eytU!4wI z$Y-Fl`_P0&qY6U#dVx?D(5M1YwJs2W`Tbf!)^Y=yKytIv61AvTX(_ixC3>8oI_oY@ z;ZjWH_PD@26jOnJ#9#DIT zsU_Wr5{pDhQY3ogpbs%)gRmJ57=t>eie3`?<2Yj;q!AD|CV4dyR8dC;l4Q(qf%(f8 zxeT*H;QjZeP;7k&ObhHp&>qwfe7jifr5sn_czZqMI3crNp$mjsOhM%?MqdM<{;f{O z(Dr6Inc6q%-A3C5cIRnUzVD?``4J~-L=5|HDxrTMWOvf=HZGfNVGNs&yy)O^1akP3 zbJ{woaRD1&A12;XvAfoP`vlwc{GF$E{XZ0n>FBciNB#d965|a){{Ncve;eniulxsM z)tjk>Bbm$|4xW|`>i3}wVY@*hMZwP6)}vEH z!nTw1g3qm@3NaHE2N!ugu|k9eQi&39)NQ^%URCNpPF-tX{}*HB)2EL0A7@Mq^#7e? zG&T8uX_V(5UjHJ}Hwpo;+fbu8#IpbE3tnF8c3Z&FMWD7F-*OyqBKB;Q@c~I0AF4aqQ;H?^&gCDR$+Et9`=$N(xbal z=(yy=6a?FZ_0?0M4=H07EOvXnWIkeM6!{eJRINy1z)$J3o|<(Sfzt$}@wfzmb34pl zbWg|RI%EoY7DaZv%We(T_B2s1nmVzoRH|7?foTJUup_{~p4;J~{slN2It^rQ_(b4}Sjn&{PZi>T1fGKd5&S&>q6v)+P7#)| zAayo-Kurt}7_2E|j>vL>lYo{S3cOJQ0D=B<%q-7JBdMW^MwuN0>yiN|bUDb(3Y^gp zC2*2Dqt~O+(tB+cGyN3C`QSF3HGspwCIr_Y!XElw02@|CIMQ+_p8c_DdQ~{?eH2Cf z{KrXt<6y>g7@tTS9V@bIXtR`%JeWL zF_)-3#ej*L#LDYRAZtQx?y#LM3P-%1Wk7bY_KRjlluWXh2-m7^yyAn5v$9| zOU7cu3I^7lmy&4sBH9nZfQyme#ak{~Zv}XvkQi&(hcMaA9ki=?PU_MSZo1giNldZ8 zT7o-2wt#tOharaRTs!rH2)DojJ!5-mh-IhM>;ds6aCVF>fNDgCx9QY*4!v@vzX=zY z374?UX}wMU1HRPH01iisjJ(`>*v6;_>T>BQK$$ktX|{Sq-VikxjT^ZgYz$rH;L8Yf z-D&;wcHQT3(5v;odnu=)?y=!=g8IdVYbW*#I9XaLzGOeiTb0!%8`Y>IrQC{Z1j(=_ zg6l_y-yv$}kvFN535*OvhV2JKx>t#SICV^Ft;LlOW-!3zvB8B*$lhfr+XJ#;S{3+M zkp#-2hm=)j@?>}=`An9LsoK|Y8+@uU6Cd7K=qOfs= z0N2M@7u^2J)=Ex=Tj78!CbU%QgG{$vAs|eoG>KXi?Xz{M4wB)MgzNYJAK7^5{a+v~ zA74orJRLfA`kT6db?pD~@rL*y|G$PN{l7+e>bw6lG(i6k;6^H%(4Sa1YT{qiKw880 zZxF+>GXq%#iW~r(^c;4Ojo2aXj=+-_ zRtT;EdmG4s^jsDU8v)0fif)eup9RdDY(58v`Zr#U$h@HcV>*d08$yJc)Jo2U0VZyv zqIw(C1{bGNW|Y_=R1_jfMlduQj%?i_carV>)L*j9705>IV&E4uYL!U?DO-PtyoNME zdU1jZD7a`=S1ReZ9Rkf>Fna;1TrL4JI}KJ)z>||c^VK{wKPR8VKP%8#rpS9LKs(BifDcYmU8>Z8+Nz3%2n^sW8}@E^ z1##~|CPU+A%hj>P8(dGWQ}B8C0E}g5Dg>jSp6c5Z1l7fn4HZD4eL3~_RR4z4I2?9n z)&u2eSMW2w)4LrZd%}D&(5^JBI*@p{9HLtXxzceyJ6W`7!K8&K7f3F=RSvN@VQIV; zy|Oig3V}(b?qWSEg~c}93KixK&dD8=l{c`YPu{@XlI;9pgNNo805>NJm>K^XAet3d z7)5$ui^w9Z4eK>1jU)@NHYh0HzAy~3awr&)W)N7FV?hcBg5pXyxChf&xe$EC&GezA z%TZ&PLO{`-&Sn+GNq~RqNb#$hQ~T+AFm@9Dx2YvqQdc|_F?7J(2KQlvSfv6!lh!Nx z;|DPGV2FS~2CsCt5Ml>OrLwkE<|bI_V@d9#QUQ^mo6iZvRMf78|3~B(48X7<$r*Q?Dk7nDU|<% z{J$y56vY20BsSguHp*g0qITa!^%2`vhYJuImF{lipmT{I8BD->u z)V0M}Z&V~8odu!%gEpip&HMn zr9o{m8u~+2N-fOq&A2mi_C&6T79`9^NnTFw;Gy6S2^!$!qJ-GdMbY}0%p#3GCNkC! z0aCdlW2`$0I$UmNPQJiHkPadRIN+eC)7hOmdce?$Z%H#r{pwIdjaDJDizL7CR#V!6 zqkVzFRg5V_Nuwi#uKIiS3^~0KTm$);3;BPfk#GNT|F1?vp#G;Z$&}D^|JNu_efNKK z2g5W}=oJj^#6V(^z>39KP*gbe@MzoUmz&a4yquvlI|?#_gX|0-Bs8c1)%_8RVz5yb z2~7{UtON*LZBWvTnW$M+xHLd$8o-W|_^)KBspyK1s{cn; zBZ!EYj2g+1 zf1)9X{u`V0KN{z$Fa5Vj0rVPv`h%m1sGpz>c@{)&NIne)9#evJg+tsLA&1@&UEz?L z!qVA6S*L8IAL9Jze|l`-CKPz$lOvXRr8w7;5@Vgm6+L=}rT&HO?1o6{09R(FXA=zW zfzi4gk~>W<_z22;BtTgRvdxjvWbU{`Me1O_J8Rg`e))fy>87Ge0ep=zmfDDS3Ute( z?=mx~08%_1i`Z&m_d-^m+hMoBL{8RMYf8B&I z5TkXFZy9P?e@Qeaft;jp1sBMKY19hKG6@7+@`xs6ikFeq#-8fP8`U-SvL37b{4e+A zpI-lo2|@Osh9>==#(6HL{TBfML8t#aDZpP456C?Uv64v!rifVgN|p`D{8q9ALb;L^ zFB`U!|JoC{{^73wV%I@!yyI(K{{~Z%y#5o(dt;n2E}?1tH^$@PZRm$jbPYoNU;gWC zY17k4PvH8eN*EWnj=I)=yfHCw{U~lO{(#E9OP?-*XtHiV`B-z zRrt#EWWS5`*ew>b$LgvyJ8d4qkjQvRs^@{3$l!V^T^ObMsejQLVq;XJn8K{+$hTNj zk%P$Ym`N2*oj_zXk-f0s^-tzfh`q>l)W!cBl=5G?{^Ju8!?=Wt+>VgX|M>b3IsX@# z39O6$n-T-*f4tGqr2o|@kD;CqXEfLhW=o3MXyfCPEmljSHPMh@vyusmvysU&o6C~Q zlFQ7A91Ikf4*%X*=5v&rJ$9ddV%4Nf&X{CONJ>mL#T&SuaKsO{I9yIXm7WqCwk7=A zqxOI23Az4pW4w5EG@Sm&#~Txx=zn88wa>rETK)Yx8b1GthNQTr`EQJ;?)hh|_1~zY z;q#vqmuPC5|HgRgJ^z1$Cu}hP2lszX`X7z*H1U7`+fVK5p9)4UMjZ{O{|N~OV-x*v zjHj;or-HG+S4V^CzcDe%)Fl6Hl&6XQ|8GBm>mLGj@m2^OFI==b>RSJa24mp)KhfAE z|7(;df`b{y`9|%FRL;%w4vd=Wa8&8o9|GDna;k**1XEdJqA7tlo2+qVamKhfOJZ4^ z*}@wW49W4eHicIo!%1grwWm_RQ0DaAu3>( zI7X|b%p7MbGg%Xp%B&^}pO_SHvso-A-jHIBGg^6z)mCP(m^eA8$#{d_s5hugR--XK z-jo=}#~Bl>CRfQ(a?B;`oSL^3X>q$G0+8Ioi}B5yOq z+mb8>OBvWQTdna436^+UTztHLNvQK*p8vr0j~+ZPavgQ7e^Z>%6nOuW5Z`qEYm_G^ zuOOTJ*Nmigww!y{b@|{!qxgSV*!i$a&fU{GEbNZQ!or%Lr0J3?$DTdZJ8Dl(zh~bt zJ$dNkz2_HqJ5dy-0`#$$J6#@0QLq+xZT#U~nsW3OedY^x-7oD6H_y3b{?E~GbX>7J ztmVMG?A*bHxwXS->kp^pyQ&@Mj`Y`jXnZC+_JwWzXCD2%aLx9x@Pgc|oI$zzN^5O! z+6DA)$1>w}n=eZnaHd_~A53(f_kE z&%Sm4r#bWQn{>~Yd0Ub`ZSiF8u`m^cC=Tvt2zVX+;a&c&>Xx-j6VhLN;-O2QeDB$5 zAEu?pJ*f?A$KVC@GX%U&{_rk)e%G#Q_tQf;ng3UX++Lu<7-i|yE}9D zO$`edydIy$dx3sl=MSfO?*V(3c{l2Nz5D0!h5PcehR+;&;KpfTZ7CdON5P*B`R}?g zV*+PTEe^ZG8#%LZbH&2+In_nqME$U^LofTO7SS)qBt5n8@Wj^*ox3=G+VEiUKSd$` zZ$H8Nzs>cp>VKg9UtD}b)BR7QJR#?wpNL*RE><0No&Vwz1Lr@%&=miraUSaI!XaSX zPjGR8kqzJnLjoO$XEK6tLlwadq#mIIE&3y((03y&EOYyeLM@LrQj3diV2xy;4z zWAeX!-1@J30@pwFbbWDE02@yK$$7g;{@*B1ZS#LI`GAJce}d5v-!%V?@zgf|AQk*q z=AYjG84{b;f1^AXoPQJz+v2UsDMnjd3U4x|BqSLVQVe_wZ!9yHB_|}t#l;znd|9$B ziPzh$wFnMjg5zuD|H#ZM{lBCBN4z1fN&eR;&!66WOBj{^L26FYWh}KMsg)vylLY)z zn}F1orFJY`$x=s_I)zAAvveIx*DF$ImbxesM^ZOMQnM5pBK06Cilk_gVp!_Ql2(y) zBqD3UQCnMjILqG5J`hc8WNChBB>xC6_PZJq~Rous3qMTkVcYp3roc;jb^EoB}<6Jvt%O)tjFvu zjUfq^O(jcCMFLrmz><%p2_#KqsfwgYELD?qD@ipZO(y9!lI|pF3Q1E*x{IZISenMt zj1XxiOS4FNfTY_6L zFMjw)+TP!64|o54;=*Q0w?0z%$GC^S`SkF);rc(av!3enWGI>UrO$Ldm45dTPqUY~ z>vlBT{1*52==(4I~odg=c=dZ1}3MgqbTAZ%_^G zIb&+%@jF|%9FJEW{qpNMv$XM{5d*Y()yipzePc*XZ? z7Iyu7=F!U1l0&v$9mcG#zW0f%I=Aofjk91vtBvPw=UPAe{bz|^d~nsYx9zQ3zjEgF zkyrB0OJ09(!tKxHbg25U#iDPPMRv<8XttvKwgI<%_;KqFd#CqZkZ}12uS{6cqI&G0 z8RlO3B^{3J(d)C*UbuJSy~j^jhA+LfyzSx+N6+8)!O_y9l;jm^aNu5|RDrkc5RQRjUtdg{t|Xis?md_y0;qVqQ&uY0NU9}{QXKXHm_+d9 zn2p~xAKA5c+6|xo_~6=GzV+@9HvD$`Tm5Ic)p3V=9)04-=E=#H^d6?(lWUH@I!~SH zoW13mcli$8K6-S*f|a`u&Fix&((N4STf4k`cf!ER>%RGQ-qgbN5mBoXW2XE`reP#a5&60XP z+N>)6?)R(b>#oe{k!dWBO?>3WzVRJTzMpcU`dwYB{(x-tZm^1Sm z(`HS#8;@Lf-3Mc*jfnoUqj$A#U83oYeQ{@6FFEwL@yWe!Exa?`e9P1Emk-=w{QB8FlS*!@T4;Ua<)132M<%qrZcpU*-|lMB^{(F{ zw?8xYvxvO53EvbQ*!{snn?8GQKxtx;b?~F@Z(EY&ySe4ZUBXsg^}v)_-}1&CFF7Cl zDP{0$@Bi}dQumad38v(I`spcZ+v)VJ_pduQ`HBy^zvMXBsj^^5J&Ri`@?zxnM<%YiAmOSaw7YQqqnz;90I^ljCDZhBzy zjy`{E*fr>b+du8*yR+qFPn+DF*QamGE8e|-_%$&-($q_hTnleUsevxPIFFQ1bhM^N&rQF*JYA!{?`EUOwivbw@mUNB+#u-Z|gQQu*Mb zm)4%0d((AK{d`ZK{O@LeeqFD7GXC?%mLs#;mE1V8%i6( zSp)8KuP;n*{d3#%trIibpW{6Gu;xiWet%|Y!Eq{OH2>({D~2UL z^_6?=%xk*UOj)1x!qI~--|<7_uoJpppMG=n;+ZFp)I4@g#hPv15{?X?dFk2FgMWK# z=icx`9?#Fuyq9z|dc@-+Uwy}Ps`@SOgEM}=`sm$>Urv7DmXr6|N2Xk7_~?u3w!>4q zJa^;j>l2DI${+rCTJx&kmT$TC4b85wgz5XPC_b z{ZR@t;iLiY0JBNzO)-S!| zg_(0-U$SV~=C+k{KQW&@H{!=((Pw0!f1OU^_!pS8V}rR<9X(NnIi(C)l^@@-QF zsejf_Imq{nnlz{V?auG>Z*9GfD?T`E_m4T{*OYg7Wmo36+O*%^ADVl~$2&gs46PV9 z#w@)0S-V$ePThAleb(HfwS)5Q-JQmLR{PGEjB}Q?NKP2G=9;a;?aAt0QATsm+1sXn z|53)BCredNEg$oGT=|I?AMB>uy*M)E(oP$%<5z4LJLk`@I=;TbIOQGf$u{NVUR}Na z+tH!!e_bQLYvnO&K0Z1(bL+kOahqFQdHYfKx`fwU?YpHUubuSer`nDw`H3x#ds~&Y zxqipIx89qvGH(18&2Dwio^qLQL;rTi7kd`WcvUycG-mFG#V?J2(DzN7q}w~aeQM&M z7M}7e`bIRn@w(r~udlh|n>}ZS)GT~E#lUTT=+(t%f1a@SwU_5!eRJ1i*^_?Sb3E?7 z_Y)6psX4Llrgyd-=+XV@SC zE_kr?r$?`Os)g>1HFD~f{hfNNC!9}hb!17qx8EFm?Yr%?cc05Ld}n>Et)Vl&W^~57 zMe*M}v|w6`8Mp3OWv-;q#tVDP-6*OC)^S&|d?{&|1yz4Ir{ z(;qohbH039<@zlr_c))wf6cuWNlVhZr}SR?*zuf??zm%9{OH-f5BHB8Rr371rK<*i zS#{)_G2JS1H)|D(;5Mt}bOM@46^?0)Sp zzBy-7!?x}kxv2Zq`#$LT``hCSI%fYg_uTcio%?!5wV3W*^D>o_8)G0&bo9}_-pN-Ik0Wn;Q6P9?mEZ6V16xi(ZF+W zJ+D!vjw9;-4hiOZw z8^OiX+D<<@xA~6v0h*h~Upf1+of|v6uz1EK)3z2*Y;Ar28~uv^*!}(QUrZ_pb(i^Q z_3E|9Uu$<+WLN#l|9p}3I-mE3;nAswH}ySyPg(PW)u(rVIqlT!Yxk}Wr5CVlrTv#X z@BBWg=8K6n&8oYP>+t&OpO)|1cD_^R>wdT1@`$?k=#@`vRNBl?clM93c)xXrCDY#9 z;2vB(Fs0>-QMZpjv~t$khmNm}oE{%nr4hdDeEaGNEmNN??C`;Z1D19!`?=GjKaGF( zsw2a?e*4t3PnQmvx3+rW$)ww>&ketOQHQb1SDl;Q_KSj&+plgJpWdqX7tzh~UsI={9|ie={Icu=*c-R zW*_L-XLJ9uQSb2^)_nQ(kX2=~zt}N#&d9Gno~TWG$n)OX!(Bc*G&=p+nXdK+d))SN z=C=MTqMt5zx9RrW=S9LRdBbgAsJ?mb^@)yWnkP8szLRx$+Ye8Fc1N$}?|=DX{-pI+ zZGQI9Wu>2dRnvX?p$-Sve|wMPz4cS(|9bg?{r%=<&3@wY7oWMo-G8WU!xz~(Ie*@? z?Uu7i=g$>JKjD30;fYQBdrKExdik;VV`ozn%eya}w?3x#rnyV*6OL5g@=JH?qCX2O zmVA5ouD)A4EZ*|Y&GBEQN};)_^+4s-um-f?H2w#de-2t`=5I8iARfP44rwy z_R@O>-~YPp{%78eo_#v9_qgs8-+FFGtH|NczT4*-^IbXJMjTj|xBuPnw?f^`@0eR~ z_qE0alauEhd8Mr5h9CETdgY)O?3eO)F6wsG#=U#GymZZoB?lj#W!(AL`V;PDS3maE zvn8tEI=uS(H=kYM*){)UyBl5^H7)+taYE*X|2({Vqfi)1kKm;j_Z@q6y61|n-|c;$ zezZE~}&pE8yQ%8<1vG3$?aP91xA z-`0e(z0LY1jLBc{>ecDzzB)K>Yx#>m94xsf!!me*tuU z_CE8zz96Y+`JI|Uk7Tv&@ZdIn)kf9W4<=j=B0BTCjckcQfyv^WKMv55(NR>9dY8z3#jGwW_&cXM1g*9!hWNqx-^#p1rHlV#L(B%yPw&a-|EDVJNBG6e$saG;+*H_I{S5Qv+Kl< zBZiM!Ht;{k7oC4|(}oin*8C^0kAL{7jtR39W*%RuwqEw!gJUKZrq9^D5zj-mz5M@#ow(L+Q_i(hWH= z|NK?`mamwm?K1zlMKca-f9&%kcVg8enWv^)^@k(r`N2u;v-{kZ@OsUc!{e*%ZA_Dn zcD(P+(|7c4KlbYnSD$QgoPXneRm=5DOdT(KpvZo0e@*Gho0?yDgX&;!(~z9>+g1IJ zP5Csf;A{J*NgL+)h$P~ z1r>{X^!@U(9wWGG|I_8W0jJ*|H&=L~&$F#oKkbZ|b;-J4-uv~5(;G*}ckgrK%OkTr zJx6yxa^}}thR=EG&OP4oFWmI7X7iMd??-?B{O_H=+G_pv*A2UOU%tPvMNGe(N55bF z`?Q^l+N6&Es7tdB*Z=IB+GFq~i+29;Ys!Z!4)@xof4k$Jk>@&a?=LkDzNOL-lYagF zjkAnbq+hXV#^Uq77FACy%Qo=dt+n^smH%|}jzeBk6;1afo{Q`cyIx&>^`CaS z;LT01Jh38t;UiI-bN4*;*+W)D$c+4+Wr%l74Fef#5v|Hs~6hFA48{legmL)=4@ zxO+m}jkvo(+})KZ#ND&w?(XjH?(W3hXY;@BN6s^xGuJ!UJ2PKqe*xC&Rr0Iqs_O1V zEyh#h^uh3}6Voq=$k?gI`{mY4+p$yvijs`ByQ~Eq7T_zics8&~Wg`zA#CbYF$e%tY zSbie^RWFGbbifw5h*O3lYXQsG^I#VoU9uIFiwp*bI3W|Cr(jWa5j6%fOM)n#{`?X-yRa3d}hIu2C=`0_{F9&`pU3( zA0X&bGn*C~T<zK zT%{W3ksPdgBpZ+iS+{av*Y_@9$NbgQiPb|pPF=FSX*C-FU3T+Q!;eZH^U2M7VHyAsyaE6;9svMc zdjNo)4gmPY2LLcE0|4@y0DvMA08lXw00`s)09I%KK%O!HAe96FpbP*20C)gETn7Ls z2HlSUjVp!$(UJ!MSdl@02VExzjb|+d06vic0MM@hfafd#fW{91=*obeg8~5XC;@<^ zOaP$%3jh$E0svs)002Tn0D!(603ay<0AOnZ0AEW00Jk0h0H+)Ppa$I+p{^cS75DwCT7Yrna4*)>uK0RVhK_c3t+fGE(joX4Q&%0T1N z0RRaQou7^XKpF^#IBx*pi!1=Z^c?_Th64Z;YCy7ffH1@c@db^`<_78H1foX)f()cD zJrn5J8ITTVAbsNifGiL;d|ChiViy3QwFZ()2mlai0s!QOL3~0006P$FQb8bEQXtv} z06;<>000Kk&ypG>lNX35=sBWG0KfxuojV)=pveHy90A!9guPQA0H6fYOL7W?Avowd zNKY7T0DzwZWET*wv2`FGAl&#tynK2A09lYNH9(lgf^6&c2>?L#2i>~|07yZ$l>o^I zsRY6wglPrHAG|>Lh=X)-0og?I1%w%hU-=101`rQIkUWhbp7)g0N}egh!zk4Fz*E6YzhDX-#{3xfN*h!>!<^phfz0?>j zqw7>ANZ_YlxpqEHzL4`dd_VY;Dml`eTV4`ynG6^FQ5M()a%o z3glFB6g4TZO)NutC=}t4{N-YKQizcLK6ojpYlnzC68-cDB^|^^Zs7Ej!m3U+HofG- z`?EO?Z~3eiO}zXAY&FxQY6tAUzcV2>efYV~OAsM2Q*|R2dDjQE0`<@?f|*zB*Idl{kKK{3K>0JmG=y$+aZO*gyWe zHeQ)|)26h|M?9zTKyUK%tN>~L@3>OShu0bQ?KBmWVOpgo84Fm$@d=t1M4VpV{g|d_ zzs%vh2Q!yCHRTL?^azBDU%TvdRZsGgkT-}lH&CKpC9q2qVdl!iYDaxvp(G-RP8X5e z*L3kbZl;=hv|V6Ng8Cr^kVIowJcSky&gI1z}4;%V6baTjbl7QjY+#Hrn9&L zonoYFvc-`D?_WYflE|10FwiT4&Vr$pJ6FQpMH!k)vSCHig?zUwKIeaKIU5xf^Gv3& zw~ILBfPq92Zcyw|mL>}<)?+Nk&hN_9mltJ0?*2%`+NUVRr3)7|$+{J#uWugrJ-_gy zanM#1wtq8h(|EA&_p6ii808G8qAzmcsqhZ6j9aXNhTbJiv8IdC^2E`C+Np>fxmcVP zzU;ST=baRiPnO3WwK^mz&BShxa?{H1;JJE`7?ww$29X6R$7+WIlgM=GUwr-KoMtZT zf$fG(jfgi$)um%kJP<$Lzl{&W2~}P@Zz8*9;p1Bo>*Z{@Rg&*?M5T;)?mT~ry6n2!t|%W8`{)p9z1 z`r3At5ydlXTE*tKla?OC?Q1vF-#COBed~O29I7|mqcDqV8!E8D9 zx$Jh|(_{AIvr^aemMF2)m$1r7wa^jly694?(0Uc|wpf7=UvEoKU%}^xH-kfgO+}jZ z301PfC;Fd!;fuU#$+Ln`tiJ=#QfocZDy)6O3hFFL30)+D&WZUFxK5Bl-a}eV@rfO9 zIkH+A4!0x|_8K`yyuFu8#)}a6L}s42bTa2pE=@jq%63?<=YCEv>)t!Zk8QuC-^s)u zMsXRQSMuFkmTr+&RdH z$u$;`rh7;7>;RvMLIF*^aNa**M9d>i$g6Yo&;^ocmwMWiTz9)}OzdawD87BXK+39~ z7m3XdT%%jxz_Y6XO4aXAO-@@PWZaO-3i+~B&Ids(1=OE(m=Br#w&E`UG|1C3|@qxHYmNkrhYJ4ID`*k{OySi~Q4cBH98&*i6B5HL|(&XN? zbz>y>L+iz=i*IXHsH*KQ@9jFYg(GtJ(>(51ZMYr+?QAb-|NgAFw#n0NMHq@OQ4+Eu zS(mg;SuX!V_x=1&pp9mZZD zmEa>~!Ie(?F-k&>MfdxbJ4<7u1^q~g8YTSjQq7gd_-$VMd_!`F@FN}nt@`*Lq&GZd zFaZf~6@PP+4rVGSGKNPA%JmYW2b{lRoF9tSWYNhtJVVe5%Kb;%p zz(l4SGY=&uQw4vB6XL!VQA|des?>!I7II*c)*SLZ33hT$={N2Z$@1A2-k|2iu8xVu z7GZae(coKF-5NABv7~Vuz+D~2L*r4h#U8KqwEE1#lVtE%CAZeMXRu6=h4D%mkLuS$ zhx^IG@n$_<=-uBfjmMVUdu#s%y9+gePJvI*!{^u!4l8ILJRxSa7GwAvc~a@;6GTH^ zXUbr6+7+)OOr{P)YLgJB`;)M*%#@ngSEYlsMfWJ@n-d|I%4f3OnRNWw+F`2R1&h9r zl=T~Ff^XUV+xi@=8|8kUU-zMW;aq{wV>do_enn#0MllNd#mT-!zIht@A#O27`o)1& zO@Y2{ikj6$$$`m6Fb$B_Xa)v7!DyqoVRT*6Xi!!V4iFIZhMsH5abc#Eu4w2Z#I>73*$IR{$v?QHN;KqBeo^U~ zBqd{7TBV$$oa19$M`trkZ1%8y#JorJd^JSsGm>oMSf1J{t{AdqJz9hXC+Q zp_N3jgVK4BL(^F#Gh%28u`LuKy7v= z>fjiJt695EBO&HxzfGOlIDa`e@?w|Ex`VXe%j5>PN>g9H0hk&6aQY`9sYQ^fNmdxC zzDjlOdJc9K)bG?*Y;>qfr~iEUTwh!PSK=v^jvpm6RR#HwvdPgN;;C=Jb+_eRsLqd}eV(=x*bm=0 z^$@0=-SI8&H0ux)R?_4Olh>cQ+}0_0rOG7Jug6H#5oHSq0*sHY*{|I#?rqX506PIv z?$;2)BO?Yor!zHYeSBes_YsQNwB$%oI8a3jGJ5^Y@b!jW`w{jH!?>s zs@ySWy}VWD`nH+9jB*2B$)#ZJ<@T8&5{i{l5n8_Rc4|t{ z4EO!u2UM0ISHz*YF6Tv#NcJ(J1MVL~YOr>J`x;T9^6bq{R6;Ymx$L&7ajY1x9t;m3 ztxcGEZu;Rr{_^1}{!VKyEGbq%m+drfDn^wSD@&Jy(qf~{70{b&R{0Ye4Wr43w0f#4 zyh{nkX*Py9pY&y^qOL3MKEca3aoOIt=rL*nTy6`A|Hs_Qk^^02nXXT5cb7RcEP*_6 zh&*ud@q#Ia1$aU^f4u1xe2<>@X;Zch- z`Ejc)=b&%E)sDBjtr<8~KQ_4eR-C~)`C`#JEdhLgs1cViU>Lm4syJ`?A;kwlUqy;Y;i^%P)cURUDVut<~yb4E6{F z1v%mtaVUBhc}^UYWlDt?OI#`(H^^qnuAx7!2Xb^^lu9bv+WfWQsT;(@FZ^&6dh5@7 zf$v2q140|$7$@X$duq^3HlYi+3)mhYsWJxY71n&r&AgC0=v{RWQ}moSC{Xy{BfoW| z(ttup^hiuLBS7@tQ}k{gehWiFg<5EN$ zY=~I_v7aA$(T}5yrDs^2iE*ZoaY~@cKPiZkHB*1dHDX7U7IM-Tl4KeCv;%((PeLM7 zWo$0aO?3LKbnO@;J`vgs#dhR6WZjA1Ok>f7OQyr;_*q8l`bjnV>WQVS$e2`R_xe3-%@{>CvyF4NuL+SQG*#!SX1SUMfBDyf=>8_!1YcQe%g@ z8V>%kv$?5>p+5piLqS9YKMVTA;0s2{MIrjIv4M>7A{+TLY|{zm$Pk&^nj8}a+(6QX z9i=Zff5<`B97VaxEHk+r?^u3r(;V;4={Q-lt`{;E8b2-)>_msoY2GhjO0(W3!F z8Q=bj$EJUiqRn^kyVdM^&=4!B8Tv=ovPU{H{g>5(8Vz~d9Xmwq73z_urHP1@Gac}A zRwTsf4da>4M;9h4Yvyk3p!|dy;2U&x_d-b&^Pi=TL}aP}a^U$(NP>MWD!WvTIE%t! z6;plJ$fAQq=F;dsblBFyfJB^@V#EDSdvQ-mIx6^f6c)yy9_L4tEsb71zUYteB%+Gw zka}0sdZaYcp;Sd4gv9i_q#tQ`Mn>dGKcb^j`6+zh4BA!~phQL0MSk1=7$8C{N&6uy zmoWMRGj0!upNO<8DG^Fw5=J&8lj~v4PjCbmFIn^Nayk-}u5fmVvTKnEY+g!g91IUgv6>g46Ap)d1S6Ux2RF3%p&R1 z4qG6mra#`+wB>|}xgQ9oOUBphbDYp<7J5;NL(BhI05?6bOX7b`S1>0y)+%>`9RGDm zPwJho|8n_Z75lf)h7$C}V36V0B)5^dUv_D79#*x$6~Sx%i-I;zf^d$`&^%p%w#w?; z(z^0?bi5N?sp3Iya)_U`475_sjXmmx!)W}tc|LdRc@wA8apxpA48~QLsX?v_I!Ad2 z`mK3dN(9sg-mFI!{@iw=2)70%?hiq)Up0-LPLU>hKhziO{nRPL@xsi;QQp!2J>-ouR=gZOPD$cug*yRVAkEXw>A>&qIrf)alLIs3UVQa_4P1Uv! z#SW|8z3{T&YX)jZPS8t{GBY%%eKQQx?i-R2K^0wu6k<0cvhY2pC_NyBbj*Y_pmA85 zD=UbtZ29Ib0K>z%{?@YTl-S)63z;^rTNiklpe+FoWFmI=ndUtRFwfC3pCJtFoD9mF zfesBFCH>8j#0+aEz9|(ZF}0NMw4kzYP<9?%-2QsA(==((zVFmzv4fB3-_Ugo2ML&) z*_%swQBieEh{#BN8^0O&^#qu&5ueOJ`Dg1!7Lo~G`<}8ky zjtP}!MMV^C=>YC|PV^(~`i0l}lMngc;puBXg6`YPN}WBuT+xD>`XKbdheQm5DUy`f zd7gq73rp7(Lw~QJeYwSv*OZk@fUx;HhZ{qOM!83{yZ3go$8!;DIXDbxePWnolZK?@ znEprhFez&YB2caZW+Q$7`tmcxWO_GOsvz=(|v;exm33(*WH3qxRbO@){O+0 zy`EfAK84ZaD^r#fwN@W$9fz&0R%RX3u2Xhc1S8Z0Ifp^DvzXTD5?aacY&6$$wa=5T z8Evoam0dfLKhiNq-r_x8q3emU@%p~EsOZsR@f_EgV)qMAm<bzgaUa*D=Sf4_dL8M<{IZ1V!x zi3c0N6RckZ9Xci=Fez8!}<2V4TSN;8aWk6Yb+K zNcTi_$C)J(g5!iGwl5|V5g@Es6y|l9il`tq&Tp2#+&A~$XY?VcE!gBXy#oAbk3;AO zi=o$D0}q-K+f=;Xse#2Vziu$t!s-_uO%!hTqTVQIma|J>FYa)AoLd2wX>>t66atst zChwPco1L@Ry#j-bD6rv^a%xqDN!$iS30P(^A$)UPz%lYX~Ef(rXCvDwxW&lsx`Ks(Y?{Gpahxtcn67 z8MoH&tuyj*qp%)&je!Eee|z%E+K|Le)3}Q7*>;V`VxY%Opk&A-;XrrWAnasmxAS-Z z!3!&LM=?ps78bnojCC@n{fDV%T`f^khagH|eY@9QxR-b-mu&WL⪻8$UYbtzJ9~n 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

=_s12PEw4M^be9f8g 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 F0sy#vA}jy^ literal 0 HcmV?d00001 -- 2.30.2 From faad6a72fb3edcd89d8981be8e7a77dca757051d Mon Sep 17 00:00:00 2001 From: raul Date: Tue, 21 May 2024 09:22:19 +0200 Subject: [PATCH 19/33] Add requesting weather from individual days --- cmd/serverFunc.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index fc1158e..bf6d6cf 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -3,8 +3,10 @@ package cmd import ( "encoding/json" "fmt" - "github.com/gin-gonic/gin" "net/http" + "strconv" + + "github.com/gin-gonic/gin" ) var ( @@ -17,14 +19,29 @@ var ( ) func server() { + //gin.SetMode(gin.ReleaseMode) router := gin.Default() router.GET("/api/:localidad", returnWeather) + router.GET("/api/:localidad/:dia", returnWeather) fmt.Printf("Listening on port %v...\n", listenPort) router.Run(":" + listenPort) } func returnWeather(c *gin.Context) { + c.Writer.Header().Set("Federal-Agents", "Outside my home") + var n int + var err error codPostal := c.Param("localidad") + numDias := c.Param("dia") + + if numDias != "" { + n, err = strconv.Atoi(numDias) + if err != nil { + c.String(http.StatusNotAcceptable, "Invalid day identifier") + return + } + } + isAvailable := false for k := range localidades { if codPostal == k { @@ -32,7 +49,7 @@ func returnWeather(c *gin.Context) { } } if isAvailable == false { - c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported\n") + c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n") return } jsonData, err := getJSON(localidades[codPostal]) @@ -50,6 +67,11 @@ func returnWeather(c *gin.Context) { return } - c.IndentedJSON(http.StatusOK, aemetRequest) + if numDias != "" { + c.IndentedJSON(http.StatusOK, aemetRequest.Base.Prediccion.Dia[n]) + } else { + c.IndentedJSON(http.StatusOK, aemetRequest) + } + //c.JSON(http.StatusOK, aemetRequest) //c.String(http.StatusOK, jsonData) } -- 2.30.2 From 843cf7ab945ee74a3522c8d677de3c31720a7ff3 Mon Sep 17 00:00:00 2001 From: raul Date: Tue, 21 May 2024 10:38:28 +0200 Subject: [PATCH 20/33] Improve error handling --- cmd/clientFunc.go | 4 ++-- cmd/serverFunc.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index 3639c36..881978b 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -77,7 +77,7 @@ func getJSON(codPostal string) (s string, err error) { client := &http.Client{} req, err := http.NewRequest("GET", "https://www.aemet.es/xml/municipios/localidad_"+codPostal+".xml", nil) if err != nil { - e := fmt.Errorf("Error occurred pulling data: %v\n", err) + e := fmt.Errorf("Error occurred creating GET request: %v\n", err) return "", e } req.Header.Set("User-Agent", "AEMET-Client/1.0 (https://git.bulgariu.xyz/raul/aemet)") @@ -85,7 +85,7 @@ func getJSON(codPostal string) (s string, err error) { resp, err := client.Do(req) if err != nil { - e := fmt.Errorf("Error occurred pulling data: %v\n", err) + e := fmt.Errorf("Error occurred executing GET request: %v\n", err) return "", e } diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index bf6d6cf..26c739d 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -34,14 +34,6 @@ func returnWeather(c *gin.Context) { codPostal := c.Param("localidad") numDias := c.Param("dia") - if numDias != "" { - n, err = strconv.Atoi(numDias) - if err != nil { - c.String(http.StatusNotAcceptable, "Invalid day identifier") - return - } - } - isAvailable := false for k := range localidades { if codPostal == k { @@ -67,6 +59,14 @@ func returnWeather(c *gin.Context) { return } + if numDias != "" { + n, err = strconv.Atoi(numDias) + if err != nil || n > len(aemetRequest.Base.Prediccion.Dia)-1 || n < 0 { + c.String(http.StatusNotAcceptable, "Invalid day identifier") + return + } + } + if numDias != "" { c.IndentedJSON(http.StatusOK, aemetRequest.Base.Prediccion.Dia[n]) } else { -- 2.30.2 From 88d10f976ca0d8af2ba16f13bc4afbb28f10011d Mon Sep 17 00:00:00 2001 From: raul Date: Thu, 23 May 2024 10:45:42 +0200 Subject: [PATCH 21/33] Remove windows support --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8e02d12..567ae8c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -24,7 +24,7 @@ builds: - CGO_ENABLED=0 goos: - linux - - windows + #- windows goarch: - amd64 -- 2.30.2 From 7cfafda5266a8457ab12c27d2122e307db9cbe2b Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 27 May 2024 08:27:54 +0200 Subject: [PATCH 22/33] Add template embedding functions --- cmd/embedTemplates.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 cmd/embedTemplates.go diff --git a/cmd/embedTemplates.go b/cmd/embedTemplates.go new file mode 100644 index 0000000..7718f53 --- /dev/null +++ b/cmd/embedTemplates.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "embed" + "github.com/gin-gonic/gin" + "html/template" + "io/fs" + "regexp" + "strings" +) + +func LoadHTMLFromEmbedFS(engine *gin.Engine, embedFS embed.FS, pattern string) { + root := template.New("") + tmpl := template.Must(root, LoadAndAddToRoot(engine.FuncMap, root, embedFS, pattern)) + engine.SetHTMLTemplate(tmpl) +} +func LoadAndAddToRoot(funcMap template.FuncMap, rootTemplate *template.Template, embedFS embed.FS, pattern string) error { + pattern = strings.ReplaceAll(pattern, ".", "\\.") + pattern = strings.ReplaceAll(pattern, "*", ".*") + + err := fs.WalkDir(embedFS, ".", func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if matched, _ := regexp.MatchString(pattern, path); !d.IsDir() && matched { + data, readErr := embedFS.ReadFile(path) + if readErr != nil { + return readErr + } + t := rootTemplate.New(path).Funcs(funcMap) + if _, parseErr := t.Parse(string(data)); parseErr != nil { + return parseErr + } + } + return nil + }) + return err +} -- 2.30.2 From 915c5f0e0d602871e973e4d9f676db74e9ec4109 Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 27 May 2024 08:28:10 +0200 Subject: [PATCH 23/33] Add HTML template sample --- cmd/templates/index.html | 16 ++++++++++++++++ cmd/templates/style.css | 1 + 2 files changed, 17 insertions(+) create mode 100644 cmd/templates/index.html create mode 100644 cmd/templates/style.css diff --git a/cmd/templates/index.html b/cmd/templates/index.html new file mode 100644 index 0000000..db95a99 --- /dev/null +++ b/cmd/templates/index.html @@ -0,0 +1,16 @@ + + + + + Hello TMPL + + + + + + +

Hello!

+

{{ .title }}

+ + + diff --git a/cmd/templates/style.css b/cmd/templates/style.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cmd/templates/style.css @@ -0,0 +1 @@ + -- 2.30.2 From 7d5c8a26aa880d7218970c3bc36a921b83679f80 Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 27 May 2024 08:28:32 +0200 Subject: [PATCH 24/33] Configure Gin to use embedded templates --- cmd/serverFunc.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 26c739d..6777b93 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -1,6 +1,7 @@ package cmd import ( + "embed" "encoding/json" "fmt" "net/http" @@ -18,13 +19,26 @@ var ( } ) +//go:embed templates/* +var templatesFolder embed.FS + func server() { //gin.SetMode(gin.ReleaseMode) - router := gin.Default() - router.GET("/api/:localidad", returnWeather) - router.GET("/api/:localidad/:dia", returnWeather) + r := gin.Default() + LoadHTMLFromEmbedFS(r, templatesFolder, "templates/*.html") + r.StaticFileFS("/style.css", "./templates/style.css", http.FS(templatesFolder)) + + r.GET("/", returnIndex) + r.GET("/api/:localidad", returnWeather) + r.GET("/api/:localidad/:dia", returnWeather) fmt.Printf("Listening on port %v...\n", listenPort) - router.Run(":" + listenPort) + r.Run(":" + listenPort) +} + +func returnIndex(c *gin.Context) { + c.HTML(http.StatusOK, "templates/index.html", gin.H{ + "title": "Hello world!", + }) } func returnWeather(c *gin.Context) { -- 2.30.2 From 0863a258c6fd4733197ef2dd18698aed17e8daa0 Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 27 May 2024 08:44:59 +0200 Subject: [PATCH 25/33] Clean up codebase --- cmd/serverFunc.go | 65 +++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 6777b93..46f09a7 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -28,26 +28,41 @@ func server() { LoadHTMLFromEmbedFS(r, templatesFolder, "templates/*.html") r.StaticFileFS("/style.css", "./templates/style.css", http.FS(templatesFolder)) - r.GET("/", returnIndex) - r.GET("/api/:localidad", returnWeather) - r.GET("/api/:localidad/:dia", returnWeather) + r.GET("/", autoDetectProvince) + r.GET("/:local", returnProvince) + r.GET("/api/:localidad", returnAPIWeather) + r.GET("/api/:localidad/:dia", returnAPIWeather) fmt.Printf("Listening on port %v...\n", listenPort) r.Run(":" + listenPort) } -func returnIndex(c *gin.Context) { - c.HTML(http.StatusOK, "templates/index.html", gin.H{ - "title": "Hello world!", - }) +func autoDetectProvince(c *gin.Context) { + // c.HTML(http.StatusOK, "templates/index.html", gin.H{ + // "title": "Hello world!", + // }) + + // TODO: Try to autodetect the province of the IP and show the correct corresponding weather } -func returnWeather(c *gin.Context) { - c.Writer.Header().Set("Federal-Agents", "Outside my home") - var n int - var err error - codPostal := c.Param("localidad") - numDias := c.Param("dia") +func returnProvince(c *gin.Context) { + // TODO: Return prettified HTML representation of the weather based on url parameter +} +func pullWeatherInfo(codPostal string) (root, error) { + jsonData, err := getJSON(localidades[codPostal]) + if err != nil { + return root{}, err + } + textBytes := []byte(jsonData) + aemetRequest := root{} + err = json.Unmarshal(textBytes, &aemetRequest) + if err != nil { + return root{}, err + } + return aemetRequest, nil +} + +func localityIsAvailable(codPostal string) bool { isAvailable := false for k := range localidades { if codPostal == k { @@ -55,23 +70,29 @@ func returnWeather(c *gin.Context) { } } if isAvailable == false { + return false + } + return true +} + +func returnAPIWeather(c *gin.Context) { + c.Writer.Header().Set("Federal-Agents", "Outside my home") + var n int + var err error + codPostal := c.Param("localidad") + numDias := c.Param("dia") + + if isAv := localityIsAvailable(codPostal); isAv != true { c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n") return } - jsonData, err := getJSON(localidades[codPostal]) + + aemetRequest, err := pullWeatherInfo(codPostal) if err != nil { e := fmt.Sprint(err) c.String(http.StatusInternalServerError, e) return } - textBytes := []byte(jsonData) - aemetRequest := root{} - err = json.Unmarshal(textBytes, &aemetRequest) - if err != nil { - e := fmt.Sprintf("Error occurred unmarshalling data: %v\n", err) - c.String(http.StatusInternalServerError, e) - return - } if numDias != "" { n, err = strconv.Atoi(numDias) -- 2.30.2 From 64682944780c07944e7aafab373771b536a16096 Mon Sep 17 00:00:00 2001 From: raul Date: Mon, 27 May 2024 09:03:42 +0200 Subject: [PATCH 26/33] Allow pulling raw json for debugging --- cmd/serverFunc.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 46f09a7..92dd8f6 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -48,18 +48,18 @@ func returnProvince(c *gin.Context) { // TODO: Return prettified HTML representation of the weather based on url parameter } -func pullWeatherInfo(codPostal string) (root, error) { +func pullWeatherInfo(codPostal string) (root, string, error) { jsonData, err := getJSON(localidades[codPostal]) if err != nil { - return root{}, err + return root{}, jsonData, err } textBytes := []byte(jsonData) aemetRequest := root{} err = json.Unmarshal(textBytes, &aemetRequest) if err != nil { - return root{}, err + return root{}, jsonData, err } - return aemetRequest, nil + return aemetRequest, jsonData, nil } func localityIsAvailable(codPostal string) bool { @@ -87,10 +87,11 @@ func returnAPIWeather(c *gin.Context) { return } - aemetRequest, err := pullWeatherInfo(codPostal) + aemetRequest, _, err := pullWeatherInfo(codPostal) if err != nil { e := fmt.Sprint(err) c.String(http.StatusInternalServerError, e) + //c.String(500, jsonData) return } @@ -108,5 +109,4 @@ func returnAPIWeather(c *gin.Context) { c.IndentedJSON(http.StatusOK, aemetRequest) } //c.JSON(http.StatusOK, aemetRequest) - //c.String(http.StatusOK, jsonData) } -- 2.30.2 From 3c5e1615a0ab08f667ce4d23af848ebaa1ba1747 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 29 May 2024 08:59:15 +0200 Subject: [PATCH 27/33] Formatting tweaks --- cmd/root.go | 33 ++++++--------------------------- cmd/server.go | 11 +++-------- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 05c5819..56c87ce 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,7 @@ /* -Copyright © 2024 NAME HERE - +Copyright © 2024 raul */ + package cmd import ( @@ -10,25 +10,14 @@ import ( "github.com/spf13/cobra" ) - - -// rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "aemet", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Short: "API Server that fetches spanish weather data from the AEMET", + Long: `API Server that fetches spanish weather data from the AEMET formatted +in XML and translates it to JSON, optionally allowing to render the +obtained data in a web page.`, } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { err := rootCmd.Execute() if err != nil { @@ -37,15 +26,5 @@ func Execute() { } func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.aemet.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - - diff --git a/cmd/server.go b/cmd/server.go index 0c7ab27..c1a500a 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -11,13 +11,8 @@ import ( var serverCmd = &cobra.Command{ Use: "server", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "Start aemet webserver", + Long: `Start aemet webserver`, Run: func(cmd *cobra.Command, args []string) { if err := setServerParameters(cmd); err != nil { log.Fatalf("Error happened trying to set parameters: %v\n", err) @@ -28,7 +23,7 @@ to quickly create a Cobra application.`, func init() { rootCmd.AddCommand(serverCmd) - serverCmd.PersistentFlags().StringP("port", "p", "1302", "port to use for listening") + serverCmd.PersistentFlags().StringP("port", "p", "1302", "Port to use for listening") } func setServerParameters(cmd *cobra.Command) error { -- 2.30.2 From 5c08a756c0a311c18405bce86d00fd02b1d6cc14 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 29 May 2024 10:16:27 +0200 Subject: [PATCH 28/33] Figure out templating range/ifelse logic --- cmd/serverFunc.go | 32 +++++++++++++++++++++++++------- cmd/templates/index.html | 22 +++++++++++++++++++--- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 92dd8f6..d828b3e 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -46,10 +46,28 @@ func autoDetectProvince(c *gin.Context) { func returnProvince(c *gin.Context) { // TODO: Return prettified HTML representation of the weather based on url parameter + localidad := c.Param("local") + if isAv := localityIsAvailable(localidad); isAv != true { + c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n") + return + } + + aemetRequest, _, err := pullWeatherInfo(localidad) + if err != nil { + e := fmt.Sprint(err) + c.String(http.StatusInternalServerError, e) + //c.String(500, jsonData) + return + } + + c.HTML(http.StatusOK, "templates/index.html", gin.H{ + "Base": aemetRequest.Base, + }) + } -func pullWeatherInfo(codPostal string) (root, string, error) { - jsonData, err := getJSON(localidades[codPostal]) +func pullWeatherInfo(localidad string) (root, string, error) { + jsonData, err := getJSON(localidades[localidad]) if err != nil { return root{}, jsonData, err } @@ -62,10 +80,10 @@ func pullWeatherInfo(codPostal string) (root, string, error) { return aemetRequest, jsonData, nil } -func localityIsAvailable(codPostal string) bool { +func localityIsAvailable(localidad string) bool { isAvailable := false for k := range localidades { - if codPostal == k { + if localidad == k { isAvailable = true } } @@ -79,15 +97,15 @@ func returnAPIWeather(c *gin.Context) { c.Writer.Header().Set("Federal-Agents", "Outside my home") var n int var err error - codPostal := c.Param("localidad") + localidad := c.Param("localidad") numDias := c.Param("dia") - if isAv := localityIsAvailable(codPostal); isAv != true { + if isAv := localityIsAvailable(localidad); isAv != true { c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n") return } - aemetRequest, _, err := pullWeatherInfo(codPostal) + aemetRequest, _, err := pullWeatherInfo(localidad) if err != nil { e := fmt.Sprint(err) c.String(http.StatusInternalServerError, e) diff --git a/cmd/templates/index.html b/cmd/templates/index.html index db95a99..9524df2 100644 --- a/cmd/templates/index.html +++ b/cmd/templates/index.html @@ -2,15 +2,31 @@ - Hello TMPL + AEMET Client -

Hello!

-

{{ .title }}

+

Localidad: {{ .Base.Nombre }}

+ + {{ range .Base.Prediccion.Dia}} + +
+

Dia: {{ .Fecha }}

+

+ ĂŤndice ultravioleta: + {{ if eq .UV ""}} + No disponible! + {{ else }} + {{ .UV }} + {{ end }} +

+ + + {{ end }} + -- 2.30.2 From 6fe7383b36c8b6d0de499b18366bdca9cde56d44 Mon Sep 17 00:00:00 2001 From: raul Date: Wed, 29 May 2024 11:21:29 +0200 Subject: [PATCH 29/33] Configure HTML table for storing weather data --- cmd/templates/index.html | 49 +++++++++++++++++++++++++++------------- cmd/templates/style.css | 5 +++- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/cmd/templates/index.html b/cmd/templates/index.html index 9524df2..0cf30a8 100644 --- a/cmd/templates/index.html +++ b/cmd/templates/index.html @@ -9,23 +9,40 @@ -

Localidad: {{ .Base.Nombre }}

+ + + + + {{ range .Base.Prediccion.Dia}} + + {{ end }} + + + + {{ range .Base.Prediccion.Dia}} + + {{ end }} + + + + {{ range .Base.Prediccion.Dia}} + + {{ end }} + - {{ range .Base.Prediccion.Dia}} - -
-

Dia: {{ .Fecha }}

-

- ĂŤndice ultravioleta: - {{ if eq .UV ""}} - No disponible! - {{ else }} - {{ .UV }} - {{ end }} -

- - - {{ end }} + + + {{ range .Base.Prediccion.Dia}} + + {{ end }} + +
{{ .Base.Nombre }}
Dia{{ .Fecha }}
Temp. Máxima{{ .Temperatura.Maxima }}
Temp. MĂ­nima{{ .Temperatura.Minima }}
ĂŤndice UV + {{ if eq .UV "" }} + No disponible! + {{ else }} + {{ .UV }} + {{ end }} +
diff --git a/cmd/templates/style.css b/cmd/templates/style.css index 8b13789..a4e0c13 100644 --- a/cmd/templates/style.css +++ b/cmd/templates/style.css @@ -1 +1,4 @@ - +table { + border: 1px solid black; + border-collapse: collapse; +} -- 2.30.2 From 7ebc68a9a479e639621ee235b67e3d7476139b46 Mon Sep 17 00:00:00 2001 From: raul Date: Thu, 30 May 2024 08:26:08 +0200 Subject: [PATCH 30/33] Add styling template --- cmd/clientFunc.go | 22 +-------- cmd/templates/index.html | 73 +++++++++++++++-------------- cmd/templates/style.css | 99 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 53 deletions(-) diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index 881978b..da9e377 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -49,31 +49,12 @@ type root struct { // Probabilidad string `json:"#content"` // Periodo string `json:"-periodo"` // } `json:"prob_precipitacion"` - } `json:"dia"` } `json:"prediccion"` } `json:"root"` } -// func client() { -// jsonData, err := getJSON() -// if err != nil { -// log.Fatal(err) -// } -// textBytes := []byte(jsonData) -// aemetRequest := root{} -// err = json.Unmarshal(textBytes, &aemetRequest) -// if err != nil { -// log.Fatalf("Error occurred unmarshalling data: %v\n", err) -// } -// -// fmt.Println(aemetRequest.Base.Nombre) -// fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha) -// fmt.Printf("Temperatura máxima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima) -// fmt.Printf("Temperatura mínima: %v°C\n", aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima) -// } - -func getJSON(codPostal string) (s string, err error) { +func getJSON(codPostal string) (string, error) { client := &http.Client{} req, err := http.NewRequest("GET", "https://www.aemet.es/xml/municipios/localidad_"+codPostal+".xml", nil) if err != nil { @@ -106,6 +87,7 @@ func getJSON(codPostal string) (s string, err error) { // I am in tremendous pain after what I had to go through to get this damn thing working, json, err := xj.Convert(xml, xj.WithNodes( xj.NodePlugin("root.prediccion.dia.estado_cielo", xj.ToArray()), + xj.NodePlugin("root.prediccion.dia.prob_precipitacion", xj.ToArray()), )) if err != nil { diff --git a/cmd/templates/index.html b/cmd/templates/index.html index 0cf30a8..f70b4a7 100644 --- a/cmd/templates/index.html +++ b/cmd/templates/index.html @@ -9,41 +9,48 @@ - - - - - {{ range .Base.Prediccion.Dia}} - - {{ end }} - - - - {{ range .Base.Prediccion.Dia}} - - {{ end }} - - - - {{ range .Base.Prediccion.Dia}} - - {{ end }} - +
+
+
{{ .Base.Nombre }}
Dia{{ .Fecha }}
Temp. Máxima{{ .Temperatura.Maxima }}
Temp. MĂ­nima{{ .Temperatura.Minima }}
+ - - - {{ range .Base.Prediccion.Dia}} - - {{ end }} - -
{{ .Base.Nombre }}
ĂŤndice UV - {{ if eq .UV "" }} - No disponible! - {{ else }} - {{ .UV }} - {{ end }} -
+ + Dia + {{ range .Base.Prediccion.Dia}} + {{ .Fecha }} + {{ end }} + + + Temp. Máxima + {{ range .Base.Prediccion.Dia}} + {{ .Temperatura.Maxima }} + {{ end }} + + + + Temp. Mínima + {{ range .Base.Prediccion.Dia}} + {{ .Temperatura.Minima }} + {{ end }} + + + + Índice UV + {{ range .Base.Prediccion.Dia}} + + {{ if eq .UV "" }} + No disponible! + {{ else }} + {{ .UV }} + {{ end }} + + {{ end }} + + + + + diff --git a/cmd/templates/style.css b/cmd/templates/style.css index a4e0c13..63f8677 100644 --- a/cmd/templates/style.css +++ b/cmd/templates/style.css @@ -1,4 +1,103 @@ +* { + font-family: arial; +} + table { border: 1px solid black; border-collapse: collapse; } + +table * { + text-align: center; +} + +input { + margin-bottom: 10px; +} + +#but { + background-color: #eee; + border: 2px black solid; +} + +.centered { + text-align: center; +} + +img { + text-align: center; + border: 2px solid #ff6e00; + border-radius: 50%; + padding: 1%; +} + +button { + background-color: #eee; + border: 2px black solid; + margin-bottom: 10px; +} + +#but:hover { + background-color: #ff6e00; + border: 2px black solid; +} + +body { + background-color: #aaa; +} + +h1 { + text-align: center; + bottom: 0%; +} + +h3 { + text-align: center; +} + +form { + text-align: center; +} + +a { + color: #ff6e00; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +progress::-moz-progress-bar { + background-color: #ff6e00; +} + +progress::-webkit-progress-value { + background-color: #ff6e00; +} + +progress { + color: #ff6e00; +} + +footer { + top: 100%; +} + +#main { + background-color: #eee; + bottom: 100%; + height: 100%; + padding-left: 5%; + padding-right: 5%; + outline: solid 2px #ff6e00; +} + +.container { + display: flex; + /* outline: solid 1px black; */ + justify-content: center; + height: 100%; + max-width: 100%; + background-color: #aaa; +} -- 2.30.2 From 8148f756d73183d06c1ab36cc0e1e4c2df9a3f35 Mon Sep 17 00:00:00 2001 From: raul Date: Thu, 30 May 2024 09:29:47 +0200 Subject: [PATCH 31/33] Styling improvements and selecting locality manually --- cmd/serverFunc.go | 3 ++- cmd/templates/index.html | 24 +++++++++++++++++++ cmd/templates/style.css | 50 ++++++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index d828b3e..fcae361 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -61,7 +61,8 @@ func returnProvince(c *gin.Context) { } c.HTML(http.StatusOK, "templates/index.html", gin.H{ - "Base": aemetRequest.Base, + "Base": aemetRequest.Base, + "Localidades": localidades, }) } diff --git a/cmd/templates/index.html b/cmd/templates/index.html index f70b4a7..db6c327 100644 --- a/cmd/templates/index.html +++ b/cmd/templates/index.html @@ -11,6 +11,30 @@
+ + + diff --git a/cmd/templates/style.css b/cmd/templates/style.css index 63f8677..4e31dc1 100644 --- a/cmd/templates/style.css +++ b/cmd/templates/style.css @@ -5,6 +5,21 @@ table { border: 1px solid black; border-collapse: collapse; + margin-left: auto; + margin-right: auto; +} + +ul { + text-align: center; + list-style: none; + top: 0; + margin: 0; + padding: 0; +} + +li { + display: inline-flex; + margin-right: 20px; } table * { @@ -86,18 +101,39 @@ footer { #main { background-color: #eee; - bottom: 100%; - height: 100%; - padding-left: 5%; - padding-right: 5%; + width: 80vw; + height: 80vh; + padding: 10px; outline: solid 2px #ff6e00; + overflow: scroll; +} + +@media (max-width: 600px) { + #main { + width: 60vw; + } } .container { display: flex; - /* outline: solid 1px black; */ + position: fixed; + margin: 1px; + padding: 1px; + top: 1px; + left: 1px; + + outline: solid 1px black; justify-content: center; - height: 100%; - max-width: 100%; + align-items: center; + height: 99vh; + width: 99vw; background-color: #aaa; + + /* width: 100%; */ + /* height: 100%; */ + /* margin: 50px; */ + /* outline: solid 1px black; */ + /* display: flex; */ + /* justify-content: center; */ + /* align-items: center; */ } -- 2.30.2 From f78c66fdc3678d1a77f52913aba945a793aa5d87 Mon Sep 17 00:00:00 2001 From: raul Date: Fri, 31 May 2024 08:57:52 +0200 Subject: [PATCH 32/33] Add page for selecting province to pull weather from --- cmd/serverFunc.go | 14 +++++--------- cmd/templates/index.html | 12 ++++++------ cmd/templates/redirect.html | 27 +++++++++++++++++++++++++++ cmd/templates/style.css | 7 ++++++- 4 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 cmd/templates/redirect.html diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index fcae361..a2d17f4 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -28,7 +28,7 @@ func server() { LoadHTMLFromEmbedFS(r, templatesFolder, "templates/*.html") r.StaticFileFS("/style.css", "./templates/style.css", http.FS(templatesFolder)) - r.GET("/", autoDetectProvince) + r.GET("/", chooseProvince) r.GET("/:local", returnProvince) r.GET("/api/:localidad", returnAPIWeather) r.GET("/api/:localidad/:dia", returnAPIWeather) @@ -36,16 +36,13 @@ func server() { r.Run(":" + listenPort) } -func autoDetectProvince(c *gin.Context) { - // c.HTML(http.StatusOK, "templates/index.html", gin.H{ - // "title": "Hello world!", - // }) - - // TODO: Try to autodetect the province of the IP and show the correct corresponding weather +func chooseProvince(c *gin.Context) { + c.HTML(http.StatusOK, "templates/redirect.html", gin.H{ + "Localidades": localidades, + }) } func returnProvince(c *gin.Context) { - // TODO: Return prettified HTML representation of the weather based on url parameter localidad := c.Param("local") if isAv := localityIsAvailable(localidad); isAv != true { c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n") @@ -56,7 +53,6 @@ func returnProvince(c *gin.Context) { if err != nil { e := fmt.Sprint(err) c.String(http.StatusInternalServerError, e) - //c.String(500, jsonData) return } diff --git a/cmd/templates/index.html b/cmd/templates/index.html index db6c327..9c1fb80 100644 --- a/cmd/templates/index.html +++ b/cmd/templates/index.html @@ -13,22 +13,22 @@
    -
  • +
  • Source code

  • -
  • +
  • API Docs

  • -
  • +
  • But why though?

  • -
  • +
  • -

    diff --git a/cmd/templates/redirect.html b/cmd/templates/redirect.html new file mode 100644 index 0000000..0f6d7d8 --- /dev/null +++ b/cmd/templates/redirect.html @@ -0,0 +1,27 @@ + + + + + AEMET Client + + + + + + +
    +
    + +
      + {{ range $key, $value := .Localidades }} +
    • +

      {{ $key }}

      +
    • + {{ end }} +
    +
    +
    +
    + + + diff --git a/cmd/templates/style.css b/cmd/templates/style.css index 4e31dc1..52444fc 100644 --- a/cmd/templates/style.css +++ b/cmd/templates/style.css @@ -9,6 +9,10 @@ table { margin-right: auto; } +.optionCap { + text-transform: capitalize; +} + ul { text-align: center; list-style: none; @@ -17,11 +21,12 @@ ul { padding: 0; } -li { +li.hor { display: inline-flex; margin-right: 20px; } + table * { text-align: center; } -- 2.30.2 From 4cdc2b0a18eb91b6977cc88e5764caa9158bd560 Mon Sep 17 00:00:00 2001 From: raul Date: Fri, 31 May 2024 09:32:49 +0200 Subject: [PATCH 33/33] Different colors for cold/hot temperatures --- cmd/templates/index.html | 25 +++++++++++++++++-------- cmd/templates/style.css | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/cmd/templates/index.html b/cmd/templates/index.html index 9c1fb80..621da88 100644 --- a/cmd/templates/index.html +++ b/cmd/templates/index.html @@ -39,28 +39,37 @@
- {{ range .Base.Prediccion.Dia}} - + {{ end }} - + + + {{ range .Base.Prediccion.Dia}} - + {{ end }} - - {{ range .Base.Prediccion.Dia}} - + + + + {{ range .Base.Prediccion.Dia }} + {{ end }} - + + + {{ range .Base.Prediccion.Dia}}
{{ .Base.Nombre }}{{ .Base.Nombre }}
Dia{{ .Fecha }}{{ .Fecha }}
Temp. MáximaTemperatura mínima y máxima (°C)
{{ .Temperatura.Maxima }} + {{ .Temperatura.Minima }} / {{ .Temperatura.Maxima }} +
Temp. Mínima{{ .Temperatura.Minima }}Sens. térmica mínima y máxima (°C)
+ {{ .Sens_Termica.Minima }} / {{ .Sens_Termica.Maxima }} +
ĂŤndice UVĂŤndice UV
{{ if eq .UV "" }} diff --git a/cmd/templates/style.css b/cmd/templates/style.css index 52444fc..c9e4858 100644 --- a/cmd/templates/style.css +++ b/cmd/templates/style.css @@ -26,6 +26,26 @@ li.hor { margin-right: 20px; } +td.left { + text-align: left; + padding-left: 5px; +} + +td.date { + text-align: center; + padding-left: 5px; + padding-right: 5px; +} + +cold { + color: darkcyan; + font-weight: 500; +} + +hot { + color: darkorange; + font-weight: 500; +} table * { text-align: center; -- 2.30.2