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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ public
|
|||||||
zqdgr
|
zqdgr
|
||||||
|
|
||||||
# compiled via go prepare
|
# compiled via go prepare
|
||||||
assets/tailwind.css
|
src/assets/tailwind.css
|
||||||
30
README.md
30
README.md
@@ -58,8 +58,6 @@ You can then run the binary.
|
|||||||
| -------------------------------------- | ------------------------------------------------------------------------------- | -------- | ------- |
|
| -------------------------------------- | ------------------------------------------------------------------------------- | -------- | ------- |
|
||||||
| `PASSPORT_DEV_MODE` | Enables dev mode | false | false |
|
| `PASSPORT_DEV_MODE` | Enables dev mode | false | false |
|
||||||
| `PASSPORT_ENABLE_PREFORK` | Enables preforking | 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_USERNAME` | The username for the admin dashboard | true |
|
||||||
| `PASSPORT_ADMIN_PASSWORD` | The password 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 |
|
| `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
|
#### 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 |
|
| Environment Variable | Description | Required | Default |
|
||||||
| ----------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
|
| ------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
|
||||||
| `OPENWEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | true | openweathermap |
|
| `WEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | false | openweathermap |
|
||||||
| `OPENWEATHER_API_KEY` | The OpenWeather API key | true | |
|
| `WEATHER_API_KEY` | The OpenWeather API key | true | |
|
||||||
| `OPENWEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
|
| `WEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
|
||||||
| `OPENWEATHER_LAT` | The latitude of your location | true | |
|
| `WEATHER_LAT` | The latitude of your location | true | |
|
||||||
| `OPENWEATHER_LON` | The longitude of your location | true | |
|
| `WEATHER_LON` | The longitude of your location | true | |
|
||||||
| `OPENWEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 |
|
| `WEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 |
|
||||||
|
|
||||||
#### Uptime configuration
|
#### 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 |
|
| Environment Variable | Description | Required | Default |
|
||||||
| ----------------------------- | ------------------------------------------------- | -------- | ------- |
|
| ------------------------ | ------------------------------------------------- | -------- | ------- |
|
||||||
| `UPTIMEROBOT_API_KEY` | The UptimeRobot API key | true | |
|
| `UPTIME_API_KEY` | The UptimeRobot API key | true | |
|
||||||
| `UPTIMEROBOT_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
|
| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
|
||||||
|
|
||||||
### Adding links and categories
|
### Adding links and categories
|
||||||
|
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -5,6 +5,7 @@ go 1.25.0
|
|||||||
require (
|
require (
|
||||||
github.com/HugoSmits86/nativewebp v1.2.0
|
github.com/HugoSmits86/nativewebp v1.2.0
|
||||||
github.com/caarlos0/env/v11 v11.3.1
|
github.com/caarlos0/env/v11 v11.3.1
|
||||||
|
golang.org/x/image v0.24.0
|
||||||
modernc.org/sqlite v1.39.0
|
modernc.org/sqlite v1.39.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ require (
|
|||||||
github.com/tinylib/msgp v1.4.0 // indirect
|
github.com/tinylib/msgp v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/crypto v0.42.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // 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/net v0.44.0 // indirect
|
||||||
golang.org/x/text v0.29.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
modernc.org/libc v1.66.3 // 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-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // 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/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.66.0 // indirect
|
github.com/valyala/fasthttp v1.66.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -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/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 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
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 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -6,7 +6,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
@@ -23,7 +22,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -35,8 +33,9 @@ import (
|
|||||||
"github.com/gofiber/template/handlebars/v2"
|
"github.com/gofiber/template/handlebars/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/juls0730/passport/middleware"
|
"github.com/juls0730/passport/src/middleware"
|
||||||
"github.com/nfnt/resize"
|
"github.com/juls0730/passport/src/services"
|
||||||
|
"golang.org/x/image/draw"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,37 +69,15 @@ var (
|
|||||||
insertLinkStmt *sql.Stmt
|
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 {
|
type Config struct {
|
||||||
DevMode bool `env:"PASSPORT_DEV_MODE" envDefault:"false"`
|
DevMode bool `env:"PASSPORT_DEV_MODE" envDefault:"false"`
|
||||||
Prefork bool `env:"PASSPORT_ENABLE_PREFORK" envDefault:"false"`
|
Prefork bool `env:"PASSPORT_ENABLE_PREFORK" envDefault:"false"`
|
||||||
|
|
||||||
WeatherEnabled bool `env:"PASSPORT_ENABLE_WEATHER" envDefault:"false"`
|
WeatherAPIKey string `env:"PASSPORT_WEATHER_API_KEY"`
|
||||||
Weather *WeatherConfig
|
Weather *services.WeatherConfig
|
||||||
|
|
||||||
UptimeEnabled bool `env:"PASSPORT_ENABLE_UPTIME" envDefault:"false"`
|
UptimeAPIKey string `env:"PASSPORT_UPTIME_API_KEY"`
|
||||||
Uptime *UptimeConfig
|
Uptime *services.UptimeConfig
|
||||||
|
|
||||||
Admin struct {
|
Admin struct {
|
||||||
Username string `env:"PASSPORT_ADMIN_USERNAME"`
|
Username string `env:"PASSPORT_ADMIN_USERNAME"`
|
||||||
@@ -111,6 +88,11 @@ type Config struct {
|
|||||||
URL string `env:"PASSPORT_SEARCH_PROVIDER"`
|
URL string `env:"PASSPORT_SEARCH_PROVIDER"`
|
||||||
Query string `env:"PASSPORT_SEARCH_PROVIDER_QUERY_PARAM" envDefault:"q"`
|
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) {
|
func ParseConfig() (*Config, error) {
|
||||||
@@ -121,18 +103,46 @@ func ParseConfig() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.WeatherEnabled {
|
if config.WeatherAPIKey != "" {
|
||||||
config.Weather = &WeatherConfig{}
|
config.Weather = &services.WeatherConfig{
|
||||||
|
APIKey: config.WeatherAPIKey,
|
||||||
|
}
|
||||||
if err := env.Parse(config.Weather); err != nil {
|
if err := env.Parse(config.Weather); err != nil {
|
||||||
return nil, err
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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.UptimeEnabled {
|
if config.UptimeAPIKey != "" {
|
||||||
config.Uptime = &UptimeConfig{}
|
config.Uptime = &services.UptimeConfig{
|
||||||
|
APIKey: config.UptimeAPIKey,
|
||||||
|
}
|
||||||
if err := env.Parse(config.Uptime); err != nil {
|
if err := env.Parse(config.Uptime); err != nil {
|
||||||
return nil, err
|
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
|
return &config, nil
|
||||||
@@ -141,8 +151,8 @@ func ParseConfig() (*Config, error) {
|
|||||||
type App struct {
|
type App struct {
|
||||||
*Config
|
*Config
|
||||||
*CategoryManager
|
*CategoryManager
|
||||||
*WeatherCache
|
*services.WeatherManager
|
||||||
*UptimeManager
|
*services.UptimeManager
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,242 +209,56 @@ func NewApp(dbPath string, options map[string]any) (*App, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var weatherCache *WeatherCache
|
var weatherCache *services.WeatherManager
|
||||||
if config.WeatherEnabled {
|
if config.WeatherAPIKey != "" {
|
||||||
weatherCache = NewWeatherCache(config.Weather)
|
weatherCache = services.NewWeatherManager(config.Weather)
|
||||||
}
|
}
|
||||||
|
|
||||||
var uptimeManager *UptimeManager
|
var uptimeManager *services.UptimeManager
|
||||||
if config.UptimeEnabled {
|
if config.UptimeAPIKey != "" {
|
||||||
uptimeManager = NewUptimeManager(config.Uptime)
|
uptimeManager = services.NewUptimeManager(config.Uptime)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
Config: config,
|
Config: config,
|
||||||
WeatherCache: weatherCache,
|
WeatherManager: weatherCache,
|
||||||
CategoryManager: categoryManager,
|
CategoryManager: categoryManager,
|
||||||
UptimeManager: uptimeManager,
|
UptimeManager: uptimeManager,
|
||||||
db: db,
|
db: db,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type UptimeRobotSite struct {
|
func CropToCenter(img image.Image, outputSize int) (image.Image, error) {
|
||||||
FriendlyName string `json:"friendly_name"`
|
if img == nil {
|
||||||
Url string `json:"url"`
|
return nil, fmt.Errorf("input image is nil")
|
||||||
Status int `json:"status"`
|
}
|
||||||
}
|
if outputSize <= 0 {
|
||||||
|
return nil, fmt.Errorf("output size must be positive")
|
||||||
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
|
srcBounds := img.Bounds()
|
||||||
if updateInterval < 1 {
|
srcWidth := srcBounds.Dx()
|
||||||
updateInterval = 300
|
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{
|
outputImg := image.NewRGBA(image.Rect(0, 0, outputSize, outputSize))
|
||||||
updateChan: make(chan struct{}),
|
|
||||||
updateInterval: updateInterval,
|
|
||||||
apiKey: config.APIKey,
|
|
||||||
sites: []UptimeRobotSite{},
|
|
||||||
}
|
|
||||||
|
|
||||||
go uptimeManager.updateWorker()
|
draw.CatmullRom.Scale(outputImg, outputImg.Rect, croppedSquareImg, croppedSquareImg.Bounds(), draw.Src, nil)
|
||||||
|
|
||||||
uptimeManager.updateChan <- struct{}{}
|
return outputImg, nil
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fiber.Ctx) (string, error) {
|
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()
|
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
|
var buf bytes.Buffer
|
||||||
options := &nativewebp.Options{}
|
options := &nativewebp.Options{}
|
||||||
@@ -859,8 +688,8 @@ func main() {
|
|||||||
"Categories": app.CategoryManager.GetCategories(),
|
"Categories": app.CategoryManager.GetCategories(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Config.WeatherEnabled {
|
if app.Config.WeatherAPIKey != "" {
|
||||||
weather := app.WeatherCache.GetWeather()
|
weather := app.WeatherManager.GetWeather()
|
||||||
|
|
||||||
renderData["WeatherData"] = fiber.Map{
|
renderData["WeatherData"] = fiber.Map{
|
||||||
"Temp": weather.Temperature,
|
"Temp": weather.Temperature,
|
||||||
@@ -869,8 +698,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Config.UptimeEnabled {
|
if app.Config.UptimeAPIKey != "" {
|
||||||
renderData["UptimeData"] = app.UptimeManager.getUptime()
|
renderData["UptimeData"] = app.UptimeManager.GetUptime()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render("views/index", renderData, "layouts/main")
|
return c.Render("views/index", renderData, "layouts/main")
|
||||||
111
src/services/uptimeService.go
Normal file
111
src/services/uptimeService.go
Normal 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()
|
||||||
|
}
|
||||||
158
src/services/weatherService.go
Normal file
158
src/services/weatherService.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@
|
|||||||
"url": "https://github.com/juls0730/passport.git"
|
"url": "https://github.com/juls0730/passport.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "go generate; PASSPORT_DEV_MODE=true go run main.go",
|
"dev": "go generate ./src/; PASSPORT_DEV_MODE=true go run src/main.go",
|
||||||
"build": "go generate && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport"
|
"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"
|
"shutdown_signal": "SIGINT"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user