first commit
This commit is contained in:
37
internal/data/ipdb.go
Normal file
37
internal/data/ipdb.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"sync"
|
||||
"thuanle.me/ip-info/configs"
|
||||
)
|
||||
import reader "github.com/oschwald/maxminddb-golang"
|
||||
|
||||
type IpDb struct {
|
||||
r *reader.Reader
|
||||
}
|
||||
|
||||
var (
|
||||
ins *IpDb
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func Ins() *IpDb {
|
||||
once.Do(func() {
|
||||
ins = &IpDb{}
|
||||
_ = ins.Reload()
|
||||
})
|
||||
return ins
|
||||
}
|
||||
|
||||
func (d *IpDb) Reload() error {
|
||||
r, err := reader.Open(configs.MmdbDbFile)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to open mmdb")
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
d.r = r
|
||||
return nil
|
||||
}
|
||||
23
internal/data/query.go
Normal file
23
internal/data/query.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func (d *IpDb) Query(ipArg string) (map[string]any, *net.IPNet, error) {
|
||||
ip := net.ParseIP(ipArg)
|
||||
if ip == nil {
|
||||
return nil, nil, fmt.Errorf("invalid IP: %s", ipArg)
|
||||
}
|
||||
// Get data of IP.
|
||||
anyData := make(map[string]any)
|
||||
recordNet, ok, err := d.r.LookupNetwork(ip, &anyData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("IP not found: %s", ipArg)
|
||||
}
|
||||
return anyData, recordNet, nil
|
||||
}
|
||||
10
internal/services/api/handler_ip.go
Normal file
10
internal/services/api/handler_ip.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func HandleIp(c *gin.Context) {
|
||||
c.String(http.StatusOK, c.ClientIP())
|
||||
}
|
||||
19
internal/services/api/handler_json.go
Normal file
19
internal/services/api/handler_json.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/http"
|
||||
"thuanle.me/ip-info/internal/data"
|
||||
)
|
||||
|
||||
func HandleJson(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
ipData, _, err := data.Ins().Query(ip)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to query IP")
|
||||
c.String(http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, ipData)
|
||||
}
|
||||
47
internal/services/api/main.go
Normal file
47
internal/services/api/main.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/http"
|
||||
"os"
|
||||
"thuanle.me/ip-info/configs/key"
|
||||
)
|
||||
|
||||
var srv *http.Server
|
||||
|
||||
func StartApiService() {
|
||||
log.Info().Msg("Starting API service")
|
||||
|
||||
engine := gin.Default()
|
||||
|
||||
trustedProxyIP := os.Getenv(key.EnvGinTrustedProxyIp)
|
||||
if trustedProxyIP != "" {
|
||||
_ = engine.SetTrustedProxies([]string{trustedProxyIP})
|
||||
}
|
||||
|
||||
engine.GET("/", HandleIp)
|
||||
engine.GET("/json", HandleJson)
|
||||
|
||||
port := os.Getenv(key.EnvApiPort)
|
||||
srv = &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: engine,
|
||||
}
|
||||
|
||||
go func() {
|
||||
// service connections
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal().Err(err).Msg("Error running API service")
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info().Str("port", port).Msg("API service started")
|
||||
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
_ = srv.Close()
|
||||
log.Info().Msg("API service stopped")
|
||||
}
|
||||
42
internal/services/db_updater/downloader.go
Normal file
42
internal/services/db_updater/downloader.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package db_updater
|
||||
|
||||
import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/http"
|
||||
"path"
|
||||
"thuanle.me/ip-info/configs"
|
||||
)
|
||||
|
||||
var etags = map[string]string{}
|
||||
|
||||
func download(url string) bool {
|
||||
log.Info().Str("url", url).Msg("Downloading DB")
|
||||
filename := configs.GeoDbFolder + path.Base(url)
|
||||
|
||||
client := resty.New()
|
||||
resp, err := client.R().
|
||||
SetOutput(filename).
|
||||
SetHeader("If-None-Match", etags[url]).
|
||||
Get(url)
|
||||
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("url", url).
|
||||
Msg("Failed to fetch DB")
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode() == http.StatusOK {
|
||||
etags[url] = resp.Header().Get("Etag")
|
||||
} else {
|
||||
//log.Printf("No update needed: %v", resp.Status())
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("Etag", etags[url]).
|
||||
Str("filename", filename).
|
||||
Msg("Downloaded DB")
|
||||
return true
|
||||
}
|
||||
33
internal/services/db_updater/main.go
Normal file
33
internal/services/db_updater/main.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package db_updater
|
||||
|
||||
import (
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"thuanle.me/ip-info/configs"
|
||||
"thuanle.me/ip-info/internal/data"
|
||||
)
|
||||
|
||||
func StartUpdateDbService() {
|
||||
c := cron.New()
|
||||
_, _ = c.AddFunc("@daily", fetchDbs)
|
||||
c.Start()
|
||||
fetchDbs()
|
||||
}
|
||||
|
||||
func fetchDbs() {
|
||||
newFlag := false
|
||||
for _, url := range configs.GeoDbSourcePaths {
|
||||
newFlag = newFlag || download(url)
|
||||
}
|
||||
|
||||
if newFlag {
|
||||
log.Info().Msg("New DB downloaded. Recreating mmdb")
|
||||
_ = mergeMmdb()
|
||||
|
||||
err := data.Ins().Reload()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to reload mmdb")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
54
internal/services/db_updater/merger.go
Normal file
54
internal/services/db_updater/merger.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package db_updater
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/safing/mmdbmeld"
|
||||
"sync"
|
||||
"thuanle.me/ip-info/configs"
|
||||
)
|
||||
|
||||
func mergeMmdb() error {
|
||||
c, err := mmdbmeld.LoadConfig(configs.MmdbmeldConfig)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to load mmdbmeld config")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, db := range c.Databases {
|
||||
log.Info().Str("name", db.Name).Msg("Building mmdb file")
|
||||
|
||||
// Apply defaults.
|
||||
dbP := &db //nolint:gosec,scopelint // Only used within loop.
|
||||
c.Defaults.ApplyTo(dbP)
|
||||
|
||||
// Load sources for database.
|
||||
sources, err := mmdbmeld.LoadSources(db)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to load sources")
|
||||
return err
|
||||
}
|
||||
|
||||
// Create wait group.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Start log writer.
|
||||
updates := make(chan string, 100)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for msg := range updates {
|
||||
log.Debug().Str("msg", msg).Msg("MMDB update")
|
||||
}
|
||||
}()
|
||||
|
||||
// Read all sources and write to mmdb.
|
||||
err = mmdbmeld.WriteMMDB(db, sources, updates)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to write mmdb")
|
||||
return err
|
||||
}
|
||||
wg.Wait()
|
||||
log.Info().Str("file", db.Output).Msg("MMDB created")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user