first commit

This commit is contained in:
thuanle
2024-07-13 23:58:47 +07:00
parent 5a47b94c85
commit 4bb5ee09f0
23 changed files with 650316 additions and 0 deletions

37
internal/data/ipdb.go Normal file
View 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
View 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
}

View 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())
}

View 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)
}

View 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")
}

View 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
}

View 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
}
}
}

View 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
}