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
|
||||
|
||||
# compiled via go prepare
|
||||
assets/tailwind.css
|
||||
src/assets/tailwind.css
|
||||
26
README.md
26
README.md
@@ -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
3
go.mod
@@ -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
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/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=
|
||||
|
||||
|
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"
|
||||
"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"`
|
||||
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")
|
||||
}
|
||||
|
||||
type UptimeManager struct {
|
||||
sites []UptimeRobotSite
|
||||
lastUpdate time.Time
|
||||
mutex sync.RWMutex
|
||||
updateChan chan struct{}
|
||||
updateInterval int
|
||||
apiKey string
|
||||
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
|
||||
}
|
||||
|
||||
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
|
||||
if config.APIKey == "" {
|
||||
log.Fatalln("UptimeRobot API Key is required!")
|
||||
return nil
|
||||
}
|
||||
outputImg := image.NewRGBA(image.Rect(0, 0, outputSize, outputSize))
|
||||
|
||||
updateInterval := config.UpdateInterval
|
||||
if updateInterval < 1 {
|
||||
updateInterval = 300
|
||||
}
|
||||
draw.CatmullRom.Scale(outputImg, outputImg.Rect, croppedSquareImg, croppedSquareImg.Bounds(), draw.Src, nil)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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")
|
||||
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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
Reference in New Issue
Block a user