Overhaul code org, and improve image uploading

This commit introduces breaking changes. It overhauls how and where
services are configured and placed in the codebase, as well as moving
the entire source into src/ It also changes how these integrations
are configured via environment variables. Old configs will still work
for now, but it is strongly suggested that you migrate your config.
This commit is contained in:
Zoe
2025-09-29 17:50:57 +00:00
parent 8c9ad40776
commit cd6ac6e771
18 changed files with 376 additions and 283 deletions

2
.gitignore vendored
View File

@@ -5,4 +5,4 @@ public
zqdgr
# compiled via go prepare
assets/tailwind.css
src/assets/tailwind.css

View File

@@ -58,8 +58,6 @@ You can then run the binary.
| -------------------------------------- | ------------------------------------------------------------------------------- | -------- | ------- |
| `PASSPORT_DEV_MODE` | Enables dev mode | false | false |
| `PASSPORT_ENABLE_PREFORK` | Enables preforking | false | false |
| `PASSPORT_ENABLE_WEATHER` | Enables weather data, see [Weather configuration](#weather-configuration) | false | false |
| `PASSPORT_ENABLE_UPTIME` | Enables uptime data, see [Uptime configuration](#uptime-configuration) | false | false |
| `PASSPORT_ADMIN_USERNAME` | The username for the admin dashboard | true |
| `PASSPORT_ADMIN_PASSWORD` | The password for the admin dashboard | true |
| `PASSPORT_SEARCH_PROVIDER` | The search provider to use for the search bar, without any query parameters | true |
@@ -67,25 +65,25 @@ You can then run the binary.
#### Weather configuration
The following only applies if you are using the OpenWeather integration.
The weather integration is optional, and will be enabled automatically if you provide an API key. The following only applies if you are using the OpenWeatherMap integration.
| Environment Variable | Description | Required | Default |
| ----------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
| `OPENWEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | true | openweathermap |
| `OPENWEATHER_API_KEY` | The OpenWeather API key | true | |
| `OPENWEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
| `OPENWEATHER_LAT` | The latitude of your location | true | |
| `OPENWEATHER_LON` | The longitude of your location | true | |
| `OPENWEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 |
| ------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
| `WEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | false | openweathermap |
| `WEATHER_API_KEY` | The OpenWeather API key | true | |
| `WEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
| `WEATHER_LAT` | The latitude of your location | true | |
| `WEATHER_LON` | The longitude of your location | true | |
| `WEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 |
#### Uptime configuration
The following only applies if you are using the UptimeRobot integration.
The uptime integration is optional, and will be enabled automatically if you provide an API key. The following only applies if you are using the UptimeRobot integration.
| Environment Variable | Description | Required | Default |
| ----------------------------- | ------------------------------------------------- | -------- | ------- |
| `UPTIMEROBOT_API_KEY` | The UptimeRobot API key | true | |
| `UPTIMEROBOT_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
| ------------------------ | ------------------------------------------------- | -------- | ------- |
| `UPTIME_API_KEY` | The UptimeRobot API key | true | |
| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
### Adding links and categories

3
go.mod
View File

@@ -5,6 +5,7 @@ go 1.25.0
require (
github.com/HugoSmits86/nativewebp v1.2.0
github.com/caarlos0/env/v11 v11.3.1
golang.org/x/image v0.24.0
modernc.org/sqlite v1.39.0
)
@@ -19,7 +20,6 @@ require (
github.com/tinylib/msgp v1.4.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/text v0.29.0 // indirect
modernc.org/libc v1.66.3 // indirect
@@ -41,7 +41,6 @@ require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.66.0 // indirect

2
go.sum
View File

@@ -43,8 +43,6 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -6,7 +6,6 @@ import (
"bytes"
"database/sql"
"embed"
"encoding/json"
"errors"
"fmt"
"image"
@@ -23,7 +22,6 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
@@ -35,8 +33,9 @@ import (
"github.com/gofiber/template/handlebars/v2"
"github.com/google/uuid"
"github.com/joho/godotenv"
"github.com/juls0730/passport/middleware"
"github.com/nfnt/resize"
"github.com/juls0730/passport/src/middleware"
"github.com/juls0730/passport/src/services"
"golang.org/x/image/draw"
_ "modernc.org/sqlite"
)
@@ -70,37 +69,15 @@ var (
insertLinkStmt *sql.Stmt
)
type WeatherProvider string
const (
OpenWeatherMap WeatherProvider = "openweathermap"
)
type WeatherConfig struct {
Provider WeatherProvider `env:"OPENWEATHER_PROVIDER" envDefault:"openweathermap"`
OpenWeather struct {
APIKey string `env:"OPENWEATHER_API_KEY"`
Units string `env:"OPENWEATHER_TEMP_UNITS" envDefault:"metric"`
Lat float64 `env:"OPENWEATHER_LAT"`
Lon float64 `env:"OPENWEATHER_LON"`
}
UpdateInterval int `env:"OPENWEATHER_UPDATE_INTERVAL" envDefault:"15"`
}
type UptimeConfig struct {
APIKey string `env:"UPTIMEROBOT_API_KEY"`
UpdateInterval int `env:"UPTIMEROBOT_UPDATE_INTERVAL" envDefault:"300"`
}
type Config struct {
DevMode bool `env:"PASSPORT_DEV_MODE" envDefault:"false"`
Prefork bool `env:"PASSPORT_ENABLE_PREFORK" envDefault:"false"`
WeatherEnabled bool `env:"PASSPORT_ENABLE_WEATHER" envDefault:"false"`
Weather *WeatherConfig
WeatherAPIKey string `env:"PASSPORT_WEATHER_API_KEY"`
Weather *services.WeatherConfig
UptimeEnabled bool `env:"PASSPORT_ENABLE_UPTIME" envDefault:"false"`
Uptime *UptimeConfig
UptimeAPIKey string `env:"PASSPORT_UPTIME_API_KEY"`
Uptime *services.UptimeConfig
Admin struct {
Username string `env:"PASSPORT_ADMIN_USERNAME"`
@@ -111,6 +88,11 @@ type Config struct {
URL string `env:"PASSPORT_SEARCH_PROVIDER"`
Query string `env:"PASSPORT_SEARCH_PROVIDER_QUERY_PARAM" envDefault:"q"`
}
Depricated struct {
WeatherEnabled bool `env:"PASSPORT_ENABLE_WEATHER" envDefault:"false"`
UptimeEnabled bool `env:"PASSPORT_ENABLE_UPTIME" envDefault:"false"`
}
}
func ParseConfig() (*Config, error) {
@@ -121,18 +103,46 @@ func ParseConfig() (*Config, error) {
return nil, err
}
if config.WeatherEnabled {
config.Weather = &WeatherConfig{}
if config.WeatherAPIKey != "" {
config.Weather = &services.WeatherConfig{
APIKey: config.WeatherAPIKey,
}
if err := env.Parse(config.Weather); err != nil {
return nil, err
}
} else if config.Depricated.WeatherEnabled {
slog.Warn("Your configuration file contains depricated Weather settings. Please update your configuration file!")
depricatedWeatherConfig := &services.DepricatedWeatherConfig{}
if err := env.Parse(depricatedWeatherConfig); err != nil {
return nil, err
}
if config.UptimeEnabled {
config.Uptime = &UptimeConfig{}
config.Weather = &services.WeatherConfig{}
config.Weather.Provider = depricatedWeatherConfig.OpenWeather.Provider
config.Weather.APIKey = depricatedWeatherConfig.OpenWeather.APIKey
config.Weather.Units = depricatedWeatherConfig.OpenWeather.Units
config.Weather.Lat = depricatedWeatherConfig.OpenWeather.Lat
config.Weather.Lon = depricatedWeatherConfig.OpenWeather.Lon
config.Weather.UpdateInterval = depricatedWeatherConfig.UpdateInterval
}
if config.UptimeAPIKey != "" {
config.Uptime = &services.UptimeConfig{
APIKey: config.UptimeAPIKey,
}
if err := env.Parse(config.Uptime); err != nil {
return nil, err
}
} else if config.Depricated.UptimeEnabled {
slog.Warn("Your configuration file contains depricated Uptime settings. Please update your configuration file!")
depricatedUptimeConfig := &services.DepricatedUptimeConfig{}
if err := env.Parse(depricatedUptimeConfig); err != nil {
return nil, err
}
config.Uptime = &services.UptimeConfig{}
config.Uptime.APIKey = depricatedUptimeConfig.APIKey
config.Uptime.UpdateInterval = depricatedUptimeConfig.UpdateInterval
}
return &config, nil
@@ -141,8 +151,8 @@ func ParseConfig() (*Config, error) {
type App struct {
*Config
*CategoryManager
*WeatherCache
*UptimeManager
*services.WeatherManager
*services.UptimeManager
db *sql.DB
}
@@ -199,242 +209,56 @@ func NewApp(dbPath string, options map[string]any) (*App, error) {
return nil, err
}
var weatherCache *WeatherCache
if config.WeatherEnabled {
weatherCache = NewWeatherCache(config.Weather)
var weatherCache *services.WeatherManager
if config.WeatherAPIKey != "" {
weatherCache = services.NewWeatherManager(config.Weather)
}
var uptimeManager *UptimeManager
if config.UptimeEnabled {
uptimeManager = NewUptimeManager(config.Uptime)
var uptimeManager *services.UptimeManager
if config.UptimeAPIKey != "" {
uptimeManager = services.NewUptimeManager(config.Uptime)
}
return &App{
Config: config,
WeatherCache: weatherCache,
WeatherManager: weatherCache,
CategoryManager: categoryManager,
UptimeManager: uptimeManager,
db: db,
}, nil
}
type UptimeRobotSite struct {
FriendlyName string `json:"friendly_name"`
Url string `json:"url"`
Status int `json:"status"`
}
type UptimeManager struct {
sites []UptimeRobotSite
lastUpdate time.Time
mutex sync.RWMutex
updateChan chan struct{}
updateInterval int
apiKey string
}
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
if config.APIKey == "" {
log.Fatalln("UptimeRobot API Key is required!")
return nil
func CropToCenter(img image.Image, outputSize int) (image.Image, error) {
if img == nil {
return nil, fmt.Errorf("input image is nil")
}
if outputSize <= 0 {
return nil, fmt.Errorf("output size must be positive")
}
updateInterval := config.UpdateInterval
if updateInterval < 1 {
updateInterval = 300
srcBounds := img.Bounds()
srcWidth := srcBounds.Dx()
srcHeight := srcBounds.Dy()
squareSide := min(srcWidth, srcHeight)
cropX := (srcWidth - squareSide) / 2
cropY := (srcHeight - squareSide) / 2
srcCropRect := image.Rect(cropX, cropY, cropX+squareSide, cropY+squareSide)
croppedSquareImg := image.NewRGBA(image.Rect(0, 0, squareSide, squareSide))
draw.Draw(croppedSquareImg, croppedSquareImg.Rect, img, srcCropRect.Min, draw.Src)
if squareSide == outputSize {
return croppedSquareImg, nil
}
uptimeManager := &UptimeManager{
updateChan: make(chan struct{}),
updateInterval: updateInterval,
apiKey: config.APIKey,
sites: []UptimeRobotSite{},
}
outputImg := image.NewRGBA(image.Rect(0, 0, outputSize, outputSize))
go uptimeManager.updateWorker()
draw.CatmullRom.Scale(outputImg, outputImg.Rect, croppedSquareImg, croppedSquareImg.Bounds(), draw.Src, nil)
uptimeManager.updateChan <- struct{}{}
return uptimeManager
}
func (u *UptimeManager) getUptime() []UptimeRobotSite {
u.mutex.RLock()
defer u.mutex.RUnlock()
return u.sites
}
func (u *UptimeManager) updateWorker() {
ticker := time.NewTicker(time.Duration(u.updateInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-u.updateChan:
u.update()
case <-ticker.C:
u.update()
}
}
}
type UptimeRobotResponse struct {
Monitors []UptimeRobotSite `json:"monitors"`
}
func (u *UptimeManager) update() {
resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil)
if err != nil {
fmt.Printf("Error fetching uptime data: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return
}
var monitors UptimeRobotResponse
if err := json.Unmarshal(body, &monitors); err != nil {
fmt.Printf("Error parsing uptime data: %v\n", err)
return
}
u.mutex.Lock()
u.sites = monitors.Monitors
u.lastUpdate = time.Now()
u.mutex.Unlock()
}
type OpenWeatherResponse struct {
Weather []struct {
Name string `json:"main"`
IconId string `json:"icon"`
} `json:"weather"`
Main struct {
Temp float64 `json:"temp"`
} `json:"main"`
Code int `json:"cod"`
Message string `json:"message"`
}
type WeatherData struct {
Temperature float64
WeatherText string
Icon string
}
type WeatherCache struct {
data *WeatherData
lastUpdate time.Time
mutex sync.RWMutex
updateChan chan struct{}
tempUnits string
updateInterval int
apiKey string
lat float64
lon float64
}
func NewWeatherCache(config *WeatherConfig) *WeatherCache {
if config.Provider != OpenWeatherMap {
log.Fatalln("Only OpenWeatherMap is supported!")
return nil
}
if config.OpenWeather.APIKey == "" {
log.Fatalln("An API Key required for OpenWeather!")
return nil
}
updateInterval := config.UpdateInterval
if updateInterval < 1 {
updateInterval = 15
}
units := config.OpenWeather.Units
if units == "" {
units = "metric"
}
cache := &WeatherCache{
data: &WeatherData{},
updateChan: make(chan struct{}),
tempUnits: units,
updateInterval: updateInterval,
apiKey: config.OpenWeather.APIKey,
lat: config.OpenWeather.Lat,
lon: config.OpenWeather.Lon,
}
go cache.weatherWorker()
cache.updateChan <- struct{}{}
return cache
}
func (c *WeatherCache) GetWeather() WeatherData {
c.mutex.RLock()
defer c.mutex.RUnlock()
return *c.data
}
func (c *WeatherCache) weatherWorker() {
ticker := time.NewTicker(time.Duration(c.updateInterval) * time.Minute)
defer ticker.Stop()
for {
select {
case <-c.updateChan:
c.updateWeather()
case <-ticker.C:
c.updateWeather()
}
}
}
func (c *WeatherCache) updateWeather() {
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=%s",
c.lat, c.lon, c.apiKey, c.tempUnits)
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Error fetching weather: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return
}
var weatherResp OpenWeatherResponse
if err := json.Unmarshal(body, &weatherResp); err != nil {
fmt.Printf("Error parsing weather data: %v\n", err)
return
}
// if the request failed
if weatherResp.Code != 200 {
// if there is no pre-existing data in the cache
if c.data.WeatherText == "" {
log.Fatalf("Fetching the weather data failed!\n%s\n", weatherResp.Message)
} else {
return
}
}
c.mutex.Lock()
c.data.Temperature = weatherResp.Main.Temp
c.data.WeatherText = weatherResp.Weather[0].Name
c.data.Icon = weatherResp.Weather[0].IconId
c.lastUpdate = time.Now()
c.mutex.Unlock()
return outputImg, nil
}
func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fiber.Ctx) (string, error) {
@@ -477,7 +301,12 @@ func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fibe
}
defer outFile.Close()
resizedImg := resize.Resize(64, 0, img, resize.MitchellNetravali)
// crop slightly larger than 64px to vastly increase the quality of the image, but not increase the file size
// *too* much and so that we dont have a ton of extra file data that will never be seen by the user
resizedImg, err := CropToCenter(img, 96)
if err != nil {
return "", err
}
var buf bytes.Buffer
options := &nativewebp.Options{}
@@ -859,8 +688,8 @@ func main() {
"Categories": app.CategoryManager.GetCategories(),
}
if app.Config.WeatherEnabled {
weather := app.WeatherCache.GetWeather()
if app.Config.WeatherAPIKey != "" {
weather := app.WeatherManager.GetWeather()
renderData["WeatherData"] = fiber.Map{
"Temp": weather.Temperature,
@@ -869,8 +698,8 @@ func main() {
}
}
if app.Config.UptimeEnabled {
renderData["UptimeData"] = app.UptimeManager.getUptime()
if app.Config.UptimeAPIKey != "" {
renderData["UptimeData"] = app.UptimeManager.GetUptime()
}
return c.Render("views/index", renderData, "layouts/main")

View File

@@ -0,0 +1,111 @@
package services
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"sync"
"time"
)
type DepricatedUptimeConfig struct {
APIKey string `env:"UPTIMEROBOT_API_KEY"`
UpdateInterval int `env:"UPTIMEROBOT_UPDATE_INTERVAL" envDefault:"300"`
}
type UptimeConfig struct {
APIKey string
UpdateInterval int `env:"UPTIME_UPDATE_INTERVAL" envDefault:"300"`
}
type UptimeRobotSite struct {
FriendlyName string `json:"friendly_name"`
Url string `json:"url"`
Status int `json:"status"`
}
type UptimeManager struct {
sites []UptimeRobotSite
lastUpdate time.Time
mutex sync.RWMutex
updateChan chan struct{}
updateInterval int
apiKey string
}
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
if config.APIKey == "" {
log.Fatalln("UptimeRobot API Key is required!")
return nil
}
updateInterval := config.UpdateInterval
if updateInterval < 1 {
updateInterval = 300
}
uptimeManager := &UptimeManager{
updateChan: make(chan struct{}),
updateInterval: updateInterval,
apiKey: config.APIKey,
sites: []UptimeRobotSite{},
}
go uptimeManager.updateWorker()
uptimeManager.updateChan <- struct{}{}
return uptimeManager
}
func (u *UptimeManager) GetUptime() []UptimeRobotSite {
u.mutex.RLock()
defer u.mutex.RUnlock()
return u.sites
}
func (u *UptimeManager) updateWorker() {
ticker := time.NewTicker(time.Duration(u.updateInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-u.updateChan:
u.update()
case <-ticker.C:
u.update()
}
}
}
type UptimeRobotResponse struct {
Monitors []UptimeRobotSite `json:"monitors"`
}
func (u *UptimeManager) update() {
resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil)
if err != nil {
fmt.Printf("Error fetching uptime data: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return
}
var monitors UptimeRobotResponse
if err := json.Unmarshal(body, &monitors); err != nil {
fmt.Printf("Error parsing uptime data: %v\n", err)
return
}
u.mutex.Lock()
u.sites = monitors.Monitors
u.lastUpdate = time.Now()
u.mutex.Unlock()
}

View File

@@ -0,0 +1,158 @@
package services
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"sync"
"time"
)
type WeatherProvider string
const (
OpenWeatherMap WeatherProvider = "openweathermap"
)
type DepricatedWeatherConfig struct {
OpenWeather struct {
Provider WeatherProvider `env:"OPENWEATHER_PROVIDER" envDefault:"openweathermap"`
APIKey string `env:"OPENWEATHER_API_KEY"`
Units string `env:"OPENWEATHER_TEMP_UNITS" envDefault:"metric"`
Lat float64 `env:"OPENWEATHER_LAT"`
Lon float64 `env:"OPENWEATHER_LON"`
}
UpdateInterval int `env:"OPENWEATHER_UPDATE_INTERVAL" envDefault:"15"`
}
type WeatherConfig struct {
Provider WeatherProvider `env:"WEATHER_PROVIDER" envDefault:"openweathermap"`
APIKey string `env:"WEATHER_API_KEY"`
Units string `env:"WEATHER_TEMP_UNITS" envDefault:"metric"`
Lat float64 `env:"WEATHER_LAT"`
Lon float64 `env:"WEATHER_LON"`
UpdateInterval int `env:"WEATHER_UPDATE_INTERVAL" envDefault:"15"`
}
type OpenWeatherResponse struct {
Weather []struct {
Name string `json:"main"`
IconId string `json:"icon"`
} `json:"weather"`
Main struct {
Temp float64 `json:"temp"`
} `json:"main"`
Code int `json:"cod"`
Message string `json:"message"`
}
type WeatherData struct {
Temperature float64
WeatherText string
Icon string
}
type WeatherManager struct {
data *WeatherData
lastUpdate time.Time
mutex sync.RWMutex
updateChan chan struct{}
config *WeatherConfig
}
func NewWeatherManager(config *WeatherConfig) *WeatherManager {
if config.Provider != OpenWeatherMap {
log.Fatalln("Only OpenWeatherMap is supported!")
return nil
}
if config.APIKey == "" {
log.Fatalln("An API Key required for OpenWeather!")
return nil
}
updateInterval := config.UpdateInterval
if updateInterval < 1 {
updateInterval = 15
}
units := config.Units
if units == "" {
units = "metric"
}
cache := &WeatherManager{
data: &WeatherData{},
updateChan: make(chan struct{}),
config: config,
}
go cache.weatherWorker()
cache.updateChan <- struct{}{}
return cache
}
func (c *WeatherManager) GetWeather() WeatherData {
c.mutex.RLock()
defer c.mutex.RUnlock()
return *c.data
}
func (c *WeatherManager) weatherWorker() {
ticker := time.NewTicker(time.Duration(c.config.UpdateInterval) * time.Minute)
defer ticker.Stop()
for {
select {
case <-c.updateChan:
c.updateWeather()
case <-ticker.C:
c.updateWeather()
}
}
}
func (c *WeatherManager) updateWeather() {
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=%s",
c.config.Lat, c.config.Lon, c.config.APIKey, c.config.Units)
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Error fetching weather: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return
}
var weatherResp OpenWeatherResponse
if err := json.Unmarshal(body, &weatherResp); err != nil {
fmt.Printf("Error parsing weather data: %v\n", err)
return
}
// if the request failed
if weatherResp.Code != 200 {
// if there is no pre-existing data in the cache
if c.data.WeatherText == "" {
log.Fatalf("Fetching the weather data failed!\n%s\n", weatherResp.Message)
} else {
return
}
}
c.mutex.Lock()
c.data.Temperature = weatherResp.Main.Temp
c.data.WeatherText = weatherResp.Weather[0].Name
c.data.Icon = weatherResp.Weather[0].IconId
c.lastUpdate = time.Now()
c.mutex.Unlock()
}

View File

@@ -10,9 +10,9 @@
"url": "https://github.com/juls0730/passport.git"
},
"scripts": {
"dev": "go generate; PASSPORT_DEV_MODE=true go run main.go",
"build": "go generate && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport"
"dev": "go generate ./src/; PASSPORT_DEV_MODE=true go run src/main.go",
"build": "go generate ./src/ && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport"
},
"pattern": "**/*.go,templates/**/*.hbs,styles/**/*.css,assets/**/*.{svg,png,jpg,jpeg,webp,woff2,ttf,otf,eot,ico,gif,webp}",
"pattern": "src/**/*.{go,hbs,css,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
"shutdown_signal": "SIGINT"
}