Compare commits
13 Commits
fc1e203c48
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e1efa39ed | |||
| 42e1bc2975 | |||
| f971086446 | |||
| a64470ef75 | |||
| f0d548aa3b | |||
| dc5dd34835 | |||
| ee1a597c9e | |||
| 48ad83bea5 | |||
| e7e911ebda | |||
| 1cf9245882 | |||
| 9d0b710bdc | |||
| b5dd3faedc | |||
| 950dbee8a6 |
41
cache/cache.go
vendored
Normal file
41
cache/cache.go
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gabdlr/api-cuit-go/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cacheDir = "./.cache"
|
||||||
|
|
||||||
|
func cacheIsOld(fPath string) bool {
|
||||||
|
fInfo, _ := os.Stat(fPath)
|
||||||
|
fCreationDate := fInfo.ModTime()
|
||||||
|
oneYearAgo := (time.Now()).AddDate(-1, 0, 0)
|
||||||
|
isOld := fCreationDate.Before(oneYearAgo)
|
||||||
|
if isOld {
|
||||||
|
os.Remove(fPath)
|
||||||
|
}
|
||||||
|
return isOld
|
||||||
|
}
|
||||||
|
|
||||||
|
func Search(cuit string) ([]byte, error) {
|
||||||
|
cuit = utils.StandardizeCuit(cuit)
|
||||||
|
fPath := fmt.Sprintf("%s/%s.json", cacheDir, cuit)
|
||||||
|
f, err := os.ReadFile(fPath)
|
||||||
|
if err == nil {
|
||||||
|
if cacheIsOld(fPath) {
|
||||||
|
return []byte{0}, errors.New("cached file expired")
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
return []byte{0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Save(cuit string, cuitInfo []byte) {
|
||||||
|
cuit = utils.StandardizeCuit(cuit)
|
||||||
|
os.WriteFile(fmt.Sprintf("%s/%s.json", cacheDir, cuit), cuitInfo, 0644)
|
||||||
|
}
|
||||||
153
cuit/search.go
Normal file
153
cuit/search.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package cuit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gabdlr/api-cuit-go/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
Provincia string `json:"provincia"`
|
||||||
|
Localidad string `json:"localidad"`
|
||||||
|
Domicilio string `json:"domicilio"`
|
||||||
|
PisoDeptoOfi string `json:"pisoDeptoOfi"`
|
||||||
|
CodigoPostal int `json:"codigoPostal"`
|
||||||
|
EstadoDeDomicilio string `json:"estadoDeDomicilio"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Society struct {
|
||||||
|
RazonSocial string `json:"razonSocial"`
|
||||||
|
Cuit string `json:"cuit"`
|
||||||
|
TipoSocietario string `json:"tipoSocietario"`
|
||||||
|
FechaDeContrato string `json:"fechaDeContrato"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CuitInfo struct {
|
||||||
|
Sociedad Society `json:"sociedad"`
|
||||||
|
DomicilioFiscal Address `json:"domicilioFiscal"`
|
||||||
|
DomicilioLegal Address `json:"domicilioLegal"`
|
||||||
|
FechaActualizacion string `json:"fechaActualizacion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SocietyRegister struct {
|
||||||
|
Cuit string `json:"cuit"`
|
||||||
|
Razon_social string `json:"razon_social"`
|
||||||
|
Fecha_contrato_social string `json:"fecha_contrato_social"`
|
||||||
|
Tipo_societario string `json:"tipo_societario"`
|
||||||
|
Numero_inscripcion string `json:"numero_inscripcion"`
|
||||||
|
Fecha_actualizacion string `json:"fecha_actualizacion"`
|
||||||
|
Domicilio_fiscal string `json:"domicilio_fiscal"`
|
||||||
|
Df_provincia string `json:"df_provincia"`
|
||||||
|
Df_localidad string `json:"df_localidad"`
|
||||||
|
Df_calle string `json:"df_calle"`
|
||||||
|
Df_numero string `json:"df_numero"`
|
||||||
|
Df_piso string `json:"df_piso"`
|
||||||
|
Df_departamento string `json:"df_departamento"`
|
||||||
|
Df_domicilio string `json:"df_domicilio"`
|
||||||
|
Df_cp int `json:"df_cp"`
|
||||||
|
Df_estado_domicilio string `json:"df_estado_domicilio"`
|
||||||
|
Domicilio_legal string `json:"domicilio_legal"`
|
||||||
|
Dl_provincia string `json:"dl_provincia"`
|
||||||
|
Dl_localidad string `json:"dl_localidad"`
|
||||||
|
Dl_calle string `json:"dl_calle"`
|
||||||
|
Dl_numero string `json:"dl_numero"`
|
||||||
|
Dl_piso string `json:"dl_piso"`
|
||||||
|
Dl_departamento string `json:"dl_departamento"`
|
||||||
|
Dl_domicilio string `json:"dl_domicilio"`
|
||||||
|
Dl_cp int `json:"dl_cp"`
|
||||||
|
Dl_estado_domicilio string `json:"dl_estado_domicilio"`
|
||||||
|
Fecha_corte string `json:"fecha_corte"`
|
||||||
|
Desc_actividad string `json:"desc_actividad"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlOfInterestStart = `Drupal.settings.registroSociedades.allItems = `
|
||||||
|
const htmlOfInterestEnd = `}];
|
||||||
|
`
|
||||||
|
const exitSignal = "No se encuentran resultados"
|
||||||
|
|
||||||
|
func Search(cuit string) ([]byte, error) {
|
||||||
|
url := fmt.Sprintf("https://argentina.gob.ar/justicia/registro-nacional-sociedades?cuit=%s&razon=", utils.StandardizeCuit(cuit))
|
||||||
|
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{0}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cuitInfo, err := parseResponse(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{0}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cuitInfoJSON, err := json.Marshal(cuitInfo)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{0}, err
|
||||||
|
}
|
||||||
|
return cuitInfoJSON, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCuitInfo(cuitInfo *CuitInfo, rawRegister *SocietyRegister) {
|
||||||
|
cuitInfo.Sociedad.RazonSocial = rawRegister.Razon_social
|
||||||
|
cuitInfo.Sociedad.Cuit = rawRegister.Cuit
|
||||||
|
cuitInfo.Sociedad.TipoSocietario = rawRegister.Tipo_societario
|
||||||
|
cuitInfo.Sociedad.FechaDeContrato = rawRegister.Fecha_contrato_social
|
||||||
|
|
||||||
|
cuitInfo.DomicilioFiscal.Provincia = rawRegister.Df_provincia
|
||||||
|
cuitInfo.DomicilioFiscal.Localidad = rawRegister.Df_localidad
|
||||||
|
cuitInfo.DomicilioFiscal.Domicilio = rawRegister.Df_domicilio
|
||||||
|
cuitInfo.DomicilioFiscal.PisoDeptoOfi = rawRegister.Df_piso + ", " + rawRegister.Df_departamento
|
||||||
|
cuitInfo.DomicilioFiscal.CodigoPostal = rawRegister.Df_cp
|
||||||
|
cuitInfo.DomicilioFiscal.EstadoDeDomicilio = rawRegister.Df_estado_domicilio
|
||||||
|
|
||||||
|
cuitInfo.DomicilioLegal.Provincia = rawRegister.Dl_provincia
|
||||||
|
cuitInfo.DomicilioLegal.Localidad = rawRegister.Dl_localidad
|
||||||
|
cuitInfo.DomicilioLegal.Domicilio = rawRegister.Dl_domicilio
|
||||||
|
cuitInfo.DomicilioLegal.PisoDeptoOfi = rawRegister.Dl_piso + ", " + rawRegister.Dl_departamento
|
||||||
|
cuitInfo.DomicilioLegal.CodigoPostal = rawRegister.Dl_cp
|
||||||
|
cuitInfo.DomicilioLegal.EstadoDeDomicilio = rawRegister.Dl_estado_domicilio
|
||||||
|
|
||||||
|
cuitInfo.FechaActualizacion = rawRegister.Fecha_actualizacion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseResponse(html string) (CuitInfo, error) {
|
||||||
|
var cuitInfo CuitInfo
|
||||||
|
notFoundErr := "información no disponible"
|
||||||
|
jsonParseErr := "ocurrió un error"
|
||||||
|
if strings.Contains(html, exitSignal) {
|
||||||
|
return cuitInfo, errors.New(notFoundErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
startPosition := strings.Index(html, htmlOfInterestStart)
|
||||||
|
endPosition := strings.Index(html, htmlOfInterestEnd)
|
||||||
|
|
||||||
|
if startPosition != -1 && endPosition != -1 {
|
||||||
|
info := html[startPosition+len(htmlOfInterestStart) : endPosition+2]
|
||||||
|
|
||||||
|
registers := []SocietyRegister{}
|
||||||
|
parseError := json.Unmarshal([]byte(info), ®isters)
|
||||||
|
|
||||||
|
if parseError != nil {
|
||||||
|
return cuitInfo, errors.New(jsonParseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(registers) > 0 {
|
||||||
|
updateCuitInfo(&cuitInfo, ®isters[0])
|
||||||
|
return cuitInfo, nil
|
||||||
|
} else {
|
||||||
|
return cuitInfo, errors.New(notFoundErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return cuitInfo, errors.New(notFoundErr)
|
||||||
|
}
|
||||||
80
cuit/validations.go
Normal file
80
cuit/validations.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package cuit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gabdlr/api-cuit-go/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CUIT_REGEX = `^([\d]{2}-[\d]{8}-[\d]{1}|[\d]{11})$`
|
||||||
|
|
||||||
|
var CUIT_TYPES = map[uint8]bool{
|
||||||
|
30: true,
|
||||||
|
33: true,
|
||||||
|
34: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsValid(cuit string) (isValidCuit bool) {
|
||||||
|
if validateFormat(cuit) {
|
||||||
|
cuit = utils.StandardizeCuit(cuit)
|
||||||
|
if validateCuitType(cuit) {
|
||||||
|
isValidCuit = validateWithVerifierDigit(cuit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isValidCuit
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateWithVerifierDigit(cuit string) bool {
|
||||||
|
verificationResult := false
|
||||||
|
toVerify := utils.ReverseStringWithBuffer(cuit[:10])
|
||||||
|
|
||||||
|
weightUpResult := 0
|
||||||
|
weightUpFactorCounter := -1
|
||||||
|
weightUpCheckFactor := []int{2, 3, 4, 5, 6, 7}
|
||||||
|
|
||||||
|
verifierDigit, err := strconv.Atoi(cuit[len(cuit)-1:])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range 10 {
|
||||||
|
if i%6 == 0 {
|
||||||
|
weightUpFactorCounter += 1
|
||||||
|
}
|
||||||
|
weightUp, err := strconv.Atoi(string(toVerify[i]))
|
||||||
|
if err != nil {
|
||||||
|
return verificationResult
|
||||||
|
}
|
||||||
|
weightUpResult += weightUp * weightUpCheckFactor[i-6*weightUpFactorCounter]
|
||||||
|
}
|
||||||
|
mod11WeightupResult := 11 - (weightUpResult % 11)
|
||||||
|
|
||||||
|
switch mod11WeightupResult {
|
||||||
|
case 11:
|
||||||
|
verificationResult = verifierDigit == 0
|
||||||
|
case 10:
|
||||||
|
verificationResult = verifierDigit == 9
|
||||||
|
default:
|
||||||
|
verificationResult = verifierDigit == mod11WeightupResult
|
||||||
|
}
|
||||||
|
|
||||||
|
return verificationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCuitType(cuit string) bool {
|
||||||
|
validationResult := false
|
||||||
|
cuitType, err := strconv.Atoi(cuit[:2])
|
||||||
|
if err == nil {
|
||||||
|
validationResult = CUIT_TYPES[uint8(cuitType)]
|
||||||
|
}
|
||||||
|
return validationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFormat(cuit string) bool {
|
||||||
|
regexExp, err := regexp.Compile(CUIT_REGEX)
|
||||||
|
if err == nil {
|
||||||
|
return regexExp.MatchString(cuit)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
15
main.go
Normal file
15
main.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
_, err := os.Stat("./.cache")
|
||||||
|
if err != nil {
|
||||||
|
os.Mkdir("./.cache", 0700)
|
||||||
|
}
|
||||||
|
http.HandleFunc("/", RequestHandler)
|
||||||
|
http.ListenAndServe(":3333", nil)
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@ package rate_limit
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TIMEFRAME = 60
|
const TIMEFRAME = 60
|
||||||
|
|
||||||
func TimeLeft(addr string) int64 {
|
func TimeLeft(addr string) int64 {
|
||||||
|
addr = (strings.Split(addr, ":"))[0]
|
||||||
timeLeft := int64(0)
|
timeLeft := int64(0)
|
||||||
file, err := os.OpenFile("addr_table.gob", os.O_RDWR|os.O_CREATE, 0644)
|
file, err := os.OpenFile("addr_table.gob", os.O_RDWR|os.O_CREATE, 0644)
|
||||||
|
|
||||||
|
|||||||
57
request_handler.go
Normal file
57
request_handler.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gabdlr/api-cuit-go/cache"
|
||||||
|
"github.com/gabdlr/api-cuit-go/cuit"
|
||||||
|
"github.com/gabdlr/api-cuit-go/rate_limit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CuitError struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const NO_SEARCH_ARG = "Sin argumento de búsqueda"
|
||||||
|
const INVALID_CUIT = "CUIT inválido"
|
||||||
|
|
||||||
|
func RequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
errorResponse := &CuitError{Error: "ocurrió un error"}
|
||||||
|
argument := r.URL.Path
|
||||||
|
if len(argument) == 1 {
|
||||||
|
errorResponse.Error = NO_SEARCH_ARG
|
||||||
|
} else {
|
||||||
|
argument = argument[1:]
|
||||||
|
timeLeft := rate_limit.TimeLeft(r.RemoteAddr)
|
||||||
|
|
||||||
|
if timeLeft > 0 {
|
||||||
|
errorResponse.Error = fmt.Sprintf("recurso no disponible, debe esperar %v segundos", timeLeft)
|
||||||
|
} else {
|
||||||
|
if cuit.IsValid(argument) {
|
||||||
|
cRes, cErr := cache.Search(argument)
|
||||||
|
if cErr == nil {
|
||||||
|
w.Write(cRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := cuit.Search(argument)
|
||||||
|
if err == nil {
|
||||||
|
cache.Save(argument, res)
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
errorResponse.Error = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorResponse.Error = INVALID_CUIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse, _ := json.Marshal(errorResponse)
|
||||||
|
w.Write(jsonResponse)
|
||||||
|
}
|
||||||
10
utils/formatters.go
Normal file
10
utils/formatters.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func StandardizeCuit(cuit string) string {
|
||||||
|
if len(cuit) > 11 {
|
||||||
|
cuit = strings.ReplaceAll(cuit, "-", "")
|
||||||
|
}
|
||||||
|
return cuit
|
||||||
|
}
|
||||||
12
utils/strings.go
Normal file
12
utils/strings.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
func ReverseStringWithBuffer(input string) string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
length := len(input) - 1
|
||||||
|
for i := length; i >= 0; i-- {
|
||||||
|
buffer.WriteByte(input[i])
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user