diff --git a/service/maxmind/mmdb/city.go b/service/maxmind/mmdb/city.go new file mode 100644 index 0000000..3cd5a62 --- /dev/null +++ b/service/maxmind/mmdb/city.go @@ -0,0 +1,189 @@ +package mmdb + +import ( + "errors" + "net/netip" + + "git.kor-elf.net/kor-elf-shield/geoip2" + "git.kor-elf.net/kor-elf-shield/geoip2/internal/pkg" + "git.kor-elf.net/kor-elf-shield/geoip2/internal/service/maxmind/mmdb" + + oschwaldGeoip2 "github.com/oschwald/geoip2-golang/v2" +) + +const ( + DownloadURLCityLite = "https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz" + NameDirNameCityLite = "GeoLite2-City" +) + +// City is a structure that contains information about the IP address. +type City struct { + // lang is a language. + lang language +} + +// NewCity creates a new City instance. +// @param download - an interface for downloading MaxMind GeoIP2 database +// @param logger - a logger +// @param dir - a directory for storing MaxMind GeoIP2 database +// @return geoip2.RefreshableGeoIP2 - a MaxMind GeoIP2 database service +func NewCity(download *Download, logger geoip2.Logger, dir string, language language) geoip2.RefreshableGeoIP2 { + city := &City{ + lang: language, + } + + newDir := pkg.NewDir(dir, NameDirNameCityLite) + return mmdb.NewMMDB(download, city.infoCity, logger, newDir) +} + +// infoCity returns information about the IP address. +// @param ip - IP address +// @param reader - MaxMind GeoIP2 database reader +// @return geoip2.Info - information about the IP address +// @return error - an error if the IP address could not be found +func (c *City) infoCity(ip netip.Addr, reader *oschwaldGeoip2.Reader) (geoip2.Info, error) { + record, err := reader.City(ip) + if err != nil { + return geoip2.Info{}, err + } + if !record.HasData() { + return geoip2.Info{}, errors.New(geoip2.ErrNotFound) + } + + var timeZone string + if record.Location.HasData() { + timeZone = record.Location.TimeZone + } + + return geoip2.Info{ + IP: ip, + ISOCode: record.Country.ISOCode, + Country: c.country(record.Country), + City: c.city(record.City), + CitySubdivisions: c.subdivisions(record.Subdivisions), + TimeZone: timeZone, + Continent: c.continent(record.Continent), + }, nil +} + +func (c *City) continent(continent oschwaldGeoip2.Continent) string { + if !continent.HasData() { + return "" + } + + switch c.lang { + case LanguageRussian: + return continent.Names.Russian + case LanguageEnglish: + return continent.Names.English + case LanguageBrazilianPortuguese: + return continent.Names.BrazilianPortuguese + case LanguageSimplifiedChinese: + return continent.Names.SimplifiedChinese + case LanguageJapanese: + return continent.Names.Japanese + case LanguageGerman: + return continent.Names.German + case LanguageFrench: + return continent.Names.French + case LanguageSpanish: + return continent.Names.Spanish + default: + return continent.Names.English + } +} + +func (c *City) country(country oschwaldGeoip2.CountryRecord) string { + if !country.HasData() { + return "" + } + + switch c.lang { + case LanguageRussian: + return country.Names.Russian + case LanguageEnglish: + return country.Names.English + case LanguageBrazilianPortuguese: + return country.Names.BrazilianPortuguese + case LanguageSimplifiedChinese: + return country.Names.SimplifiedChinese + case LanguageJapanese: + return country.Names.Japanese + case LanguageGerman: + return country.Names.German + case LanguageFrench: + return country.Names.French + case LanguageSpanish: + return country.Names.Spanish + default: + return country.Names.English + } +} + +func (c *City) city(city oschwaldGeoip2.CityRecord) string { + if !city.HasData() { + return "" + } + + switch c.lang { + case LanguageRussian: + return city.Names.Russian + case LanguageEnglish: + return city.Names.English + case LanguageBrazilianPortuguese: + return city.Names.BrazilianPortuguese + case LanguageSimplifiedChinese: + return city.Names.SimplifiedChinese + case LanguageJapanese: + return city.Names.Japanese + case LanguageGerman: + return city.Names.German + case LanguageFrench: + return city.Names.French + case LanguageSpanish: + return city.Names.Spanish + default: + return city.Names.English + } +} + +func (c *City) subdivisions(subdivisions []oschwaldGeoip2.CitySubdivision) []string { + var citySubdivisions []string + + if len(subdivisions) == 0 { + return citySubdivisions + } + + for _, subdivision := range subdivisions { + citySubdivisions = append(citySubdivisions, c.subdivision(subdivision)) + } + + return citySubdivisions +} + +func (c *City) subdivision(subdivision oschwaldGeoip2.CitySubdivision) string { + if !subdivision.HasData() { + return "" + } + + switch c.lang { + case LanguageRussian: + return subdivision.Names.Russian + case LanguageEnglish: + return subdivision.Names.English + case LanguageBrazilianPortuguese: + return subdivision.Names.BrazilianPortuguese + case LanguageSimplifiedChinese: + return subdivision.Names.SimplifiedChinese + case LanguageJapanese: + return subdivision.Names.Japanese + case LanguageGerman: + return subdivision.Names.German + case LanguageFrench: + return subdivision.Names.French + case LanguageSpanish: + return subdivision.Names.Spanish + default: + return subdivision.Names.English + } +}