feat: refactor MiogestHandler to use typedefs and add ParseDettaglio function with tests

This commit is contained in:
Marco Pedone 2025-08-21 12:45:53 +02:00
parent 0741ee0a68
commit 2ef3c38c8a
3 changed files with 342 additions and 30 deletions

View file

@ -1,6 +1,7 @@
package main
import (
"backend/typedefs"
"fmt"
"io"
"log"
@ -17,27 +18,27 @@ import (
)
type MiogestHandler struct {
Caratterisiche ListaCaratteristiche
Categorie []Categoria
AnnunciXML AnnunciXML
AnnunciParsed AnnunciParsed
Caratterisiche typedefs.ListaCaratteristiche
Categorie []typedefs.Categoria
AnnunciXML typedefs.AnnunciXML
AnnunciParsed typedefs.AnnunciParsed
annunci_cache_expires time.Time
vars_cache_expires time.Time
}
func NewMiogestHandler() *MiogestHandler {
return &MiogestHandler{
Caratterisiche: ListaCaratteristiche{},
Caratterisiche: typedefs.ListaCaratteristiche{},
Categorie: nil,
AnnunciXML: AnnunciXML{},
AnnunciParsed: AnnunciParsed{},
AnnunciXML: typedefs.AnnunciXML{},
AnnunciParsed: typedefs.AnnunciParsed{},
annunci_cache_expires: time.Now(),
vars_cache_expires: time.Now(),
}
}
// ANNUNCI FROM MIOGEST
func (m *MiogestHandler) GetAnnunci() AnnunciXML {
func (m *MiogestHandler) GetAnnunci() typedefs.AnnunciXML {
if len(m.AnnunciXML.Annuncio) == 0 || time.Now().After(m.annunci_cache_expires) {
m.GetAnnunciFromMiogest()
}
@ -45,7 +46,7 @@ func (m *MiogestHandler) GetAnnunci() AnnunciXML {
}
func (m *MiogestHandler) GetAnnunciFromMiogest() {
m.AnnunciXML = GetDataXML[AnnunciXML]("http://partner.miogest.com/agenzie/infoalloggi.xml")
m.AnnunciXML = GetDataXML[typedefs.AnnunciXML]("http://partner.miogest.com/agenzie/infoalloggi.xml")
m.annunci_cache_expires = time.Now().Add(1 * time.Hour)
}
@ -55,14 +56,14 @@ func (m *MiogestHandler) InitDefaults() {
m.GenerateCategorie()
}
func (m *MiogestHandler) GetCaratteristiche() ListaCaratteristiche {
func (m *MiogestHandler) GetCaratteristiche() typedefs.ListaCaratteristiche {
if len(m.Caratterisiche.Caratteristiche) == 0 || time.Now().After(m.vars_cache_expires) {
m.GenerateCaratteristiche()
}
return m.Caratterisiche
}
func (m *MiogestHandler) GetCategorie() []Categoria {
func (m *MiogestHandler) GetCategorie() []typedefs.Categoria {
if len(m.Categorie) == 0 || time.Now().After(m.vars_cache_expires) {
m.GenerateCategorie()
}
@ -70,10 +71,10 @@ func (m *MiogestHandler) GetCategorie() []Categoria {
}
func (m *MiogestHandler) GenerateCaratteristiche() {
var result = GetDataXML[DefaultCaratteristiche]("https://www.miogest.com/apps/revo.aspx?tipo=schede")
var array ListaCaratteristiche
var result = GetDataXML[typedefs.DefaultCaratteristiche]("https://www.miogest.com/apps/revo.aspx?tipo=schede")
var array typedefs.ListaCaratteristiche
for _, tag := range result.Scheda {
var c = Caratteristica{Id: tag.Id, Tagxml: tag.Tagxml}
var c = typedefs.Caratteristica{Id: tag.Id, Tagxml: tag.Tagxml}
array.Caratteristiche = append(array.Caratteristiche, c)
}
m.Caratterisiche = array
@ -81,10 +82,10 @@ func (m *MiogestHandler) GenerateCaratteristiche() {
}
func (m *MiogestHandler) GenerateCategorie() {
var result = GetDataXML[DefaultCategories]("https://www.miogest.com/apps/revo.aspx?tipo=categorie")
var listaCategorie []Categoria
var result = GetDataXML[typedefs.DefaultCategories]("https://www.miogest.com/apps/revo.aspx?tipo=categorie")
var listaCategorie []typedefs.Categoria
for _, elem := range result.Cat {
var c = Categoria{Id: elem.ID, Nome: elem.Nome}
var c = typedefs.Categoria{Id: elem.ID, Nome: elem.Nome}
listaCategorie = append(listaCategorie, c)
}
m.Categorie = listaCategorie
@ -92,7 +93,7 @@ func (m *MiogestHandler) GenerateCategorie() {
}
// ANNUNCI PARSED
func (m *MiogestHandler) GetAnnunciParsed() AnnunciParsed {
func (m *MiogestHandler) GetAnnunciParsed() typedefs.AnnunciParsed {
m.ParseAnnunci()
return m.AnnunciParsed
}
@ -112,8 +113,8 @@ func (m *MiogestHandler) ParseAnnunci() {
ImageManager.InsertImagesRef(&annunci)
}
var AnnunciArray AnnunciParsed
AnnunciArray.Annuncio = make([]AnnuncioParsed, 0, len(annunci.Annuncio))
var AnnunciArray typedefs.AnnunciParsed
AnnunciArray.Annuncio = make([]typedefs.AnnuncioParsed, 0, len(annunci.Annuncio))
Miogest.InitDefaults()
var wg sync.WaitGroup
wg.Add(len(annunci.Annuncio))
@ -152,8 +153,8 @@ func strUpd(s string) *string {
return &s
}
func extractData_update(annuncio *AnnuncioXML, updateImages bool) AnnuncioParsed {
var tmp AnnuncioParsed = AnnuncioParsed{}
func extractData_update(annuncio *typedefs.AnnuncioXML, updateImages bool) typedefs.AnnuncioParsed {
var tmp typedefs.AnnuncioParsed = typedefs.AnnuncioParsed{}
tmp.Codice = RemoveWhitespace(GetStringValue(annuncio.Codice))
processLocatore(&tmp, annuncio)
processIndirizzo(&tmp, annuncio)
@ -240,10 +241,11 @@ func extractData_update(annuncio *AnnuncioXML, updateImages bool) AnnuncioParsed
}
}
}
return tmp
}
func processLocatore(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
func processLocatore(tmp *typedefs.AnnuncioParsed, annuncio *typedefs.AnnuncioXML) {
if annuncio.Clienti == nil || annuncio.Clienti.Cliente == nil {
return
}
@ -266,7 +268,7 @@ func processLocatore(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
tmp.Idlocatore = annuncio.Clienti.Cliente.Id
}
func processIndirizzo(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
func processIndirizzo(tmp *typedefs.AnnuncioParsed, annuncio *typedefs.AnnuncioXML) {
tmp.Indirizzo = strUpd(MakeUppercase(GetStringValue(annuncio.Indirizzo)))
tmp.Civico = annuncio.Civico
tmp.Comune = strUpd(MakeUppercase(GetStringValue(annuncio.Comune)))
@ -281,7 +283,7 @@ func processIndirizzo(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
tmp.Lon_secondario = annuncio.LongitudineSecondario
}
func processTipologia(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
func processTipologia(tmp *typedefs.AnnuncioParsed, annuncio *typedefs.AnnuncioXML) {
if annuncio.Tipologia == nil {
tmp.Tipo = strUpd("Errore")
return
@ -306,7 +308,7 @@ func processTipologia(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
}
func processCategorie(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
func processCategorie(tmp *typedefs.AnnuncioParsed, annuncio *typedefs.AnnuncioXML) {
if annuncio.Categoria == nil {
return
}
@ -340,7 +342,7 @@ func prezzoParse(prezzo *string) int {
return prezzoInt
}
func processDatiImmobile(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
func processDatiImmobile(tmp *typedefs.AnnuncioParsed, annuncio *typedefs.AnnuncioXML) {
tmpanno := GetStringValue(annuncio.Anno)
if tmpanno != "0" && tmpanno != "" {
tmp.Anno = &tmpanno
@ -364,7 +366,7 @@ func processDatiImmobile(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
tmp.Numero_postiauto = annuncio.PostiAuto
}
func processImages(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
func processImages(tmp *typedefs.AnnuncioParsed, annuncio *typedefs.AnnuncioXML) {
path := filepath.Join(imgbasepath, tmp.Codice)
err := os.MkdirAll(path, 0755)
if err != nil {
@ -463,7 +465,7 @@ func folderCleanup(codice string) error {
return nil
}
func parseCaratteristiche(wrkrecord *AnnuncioXML) map[string]interface{} {
func parseCaratteristiche(wrkrecord *typedefs.AnnuncioXML) map[string]interface{} {
if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
return nil
}
@ -520,3 +522,98 @@ func parseConsegna(original *string) *int {
}
return &currentMonth
}
func ParseDettaglio(dettaglio string) (disp_time *string, perm_arr *[]int, persone_arr *[]string, err error) {
dettaglio = strings.TrimSpace(dettaglio)
if dettaglio == "" {
return
}
disp := getKeyValueUpToXorWhitespace(dettaglio, "disp:", "perm:")
perm := getKeyValueUpToXorWhitespace(dettaglio, "perm:", "persone:")
persone := getKeyValueUpToXorWhitespace(dettaglio, "persone:", "")
if disp != "" {
// Validate date format
_, err := time.Parse("02/01", disp)
if err != nil {
disp_time = nil
} else {
p := fmt.Sprintf("%s/%d", disp, time.Now().Year())
disp_time = &p
}
}
if perm != "" {
parts := strings.Split(perm, "-")
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
num, err := strconv.Atoi(part)
if err == nil {
if perm_arr == nil {
perm_arr = &[]int{}
}
*perm_arr = append(*perm_arr, num)
}
}
}
}
if persone != "" {
parts := strings.Split(persone, "/")
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
if persone_arr == nil {
persone_arr = &[]string{}
}
*persone_arr = append(*persone_arr, part)
}
}
}
return
}
func getKeyValueUpToXorWhitespace(input, key, stopstring string) string {
input = strings.TrimSpace(input)
i := strings.Index(input, key)
if i == -1 {
return ""
}
start := i + len(key)
rest := strings.TrimSpace(input[start:])
// Find stopstring, whitespace, or repeated key
stop_pos := len(rest)
stops := []int{}
if stopstring != "" {
if j := strings.Index(rest, stopstring); j != -1 {
stops = append(stops, j)
}
}
// Find whitespace
if ws := strings.IndexAny(rest, " \n\r\t"); ws != -1 {
stops = append(stops, ws)
}
// Find repeated key
if k := strings.Index(rest, key); k != -1 {
stops = append(stops, k)
}
// Use the earliest stop
for _, s := range stops {
if s < stop_pos {
stop_pos = s
}
}
val := strings.TrimSpace(rest[:stop_pos])
// Only take the first token before any whitespace or another key
if idx := strings.IndexAny(val, " \n\r\t"); idx != -1 {
val = val[:idx]
}
return val
}

View file

@ -0,0 +1,208 @@
package main
import (
"fmt"
"testing"
"time"
)
func ptr[T any](v T) *T { return &v }
func TestParseDettaglio(t *testing.T) {
tests := []struct {
input string
wantDisp *string
wantPerm *[]int
wantPers *[]string
}{
{
input: "disp:01/10 perm:3-6 persone:single",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp: perm: persone:",
wantDisp: nil,
wantPerm: nil,
wantPers: nil,
},
{
input: "disp: perm:3-6 persone:single", // disp missing
wantDisp: nil,
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm: persone:single", // perm missing
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: nil,
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:", // persone missing
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: nil,
},
{
input: "disp: perm: persone:", // all fields missing
wantDisp: nil,
wantPerm: nil,
wantPers: nil,
},
{
input: "disp:bad_date perm:3-6 persone:single", // invalid date
wantDisp: nil,
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:abc-6 persone:single", // non-numeric perm
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3- persone:single", // trailing delimiter in perm
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:-6 persone:single", // leading delimiter in perm
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:single/", // trailing delimiter in persone
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:/single", // leading delimiter in persone
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:single//coppia", // double delimiter in persone
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single", "coppia"}),
},
{
input: "perm:3-6 persone:single", // disp missing
wantDisp: nil,
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6", // persone missing
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: nil,
},
{
input: "disp:01/10 perm: persone:", // perm empty
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: nil,
wantPers: nil,
},
{
input: "", // completely empty string
wantDisp: nil,
wantPerm: nil,
wantPers: nil,
},
{
input: "disp: 01/10 perm: 3-6 persone: single ",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "persone:single perm:3-6 disp:01/10",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3--6 persone:single",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10\tperm:3-6\tpersone:single",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:single/coppia!",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single", "coppia!"}),
},
{
input: "disp:01/10 disp:02/11 perm:3-6 persone:single",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())), // Only first disp: is parsed
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
{
input: "disp:01/10perm:3-6persone:single",
wantDisp: ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: ptr([]int{3, 6}),
wantPers: ptr([]string{"single"}),
},
}
for _, tt := range tests {
disp_time, perm_arr, persone_arr, _ := ParseDettaglio(tt.input)
//Handle *string comparison, including nil
if tt.wantDisp != nil {
if disp_time == nil {
t.Errorf("disp_time: got nil, want %v, input: %v", *tt.wantDisp, tt.input)
} else if *disp_time != *tt.wantDisp {
t.Errorf("disp_time: got %v, want %v, input: %v", *disp_time, *tt.wantDisp, tt.input)
}
} else if disp_time != nil {
t.Errorf("disp_time: got %v, want nil, input: %v", *disp_time, tt.input)
}
// Handle *[]int comparison, including nil
if tt.wantPerm != nil {
if perm_arr == nil || len(*perm_arr) != len(*tt.wantPerm) {
t.Errorf("perm_arr: got %v, want %v, input: %v", perm_arr, *tt.wantPerm, tt.input)
} else {
for i := range *perm_arr {
if (*perm_arr)[i] != (*tt.wantPerm)[i] {
t.Errorf("perm_arr[%d]: got %v, want %v, input: %v", i, (*perm_arr)[i], (*tt.wantPerm)[i], tt.input)
}
}
}
} else if perm_arr != nil && len(*perm_arr) != 0 {
t.Errorf("perm_arr: got %v, want nil, input: %v", perm_arr, tt.input)
}
// Handle *[]string comparison, including nil
if tt.wantPers != nil {
if persone_arr == nil || len(*persone_arr) != len(*tt.wantPers) {
t.Errorf("persone_arr: got %v, want %v, input: %v", persone_arr, *tt.wantPers, tt.input)
} else {
for i := range *persone_arr {
if (*persone_arr)[i] != (*tt.wantPers)[i] {
t.Errorf("persone_arr[%d]: got %v, want %v, input: %v", i, (*persone_arr)[i], (*tt.wantPers)[i], tt.input)
}
}
}
} else if persone_arr != nil && len(*persone_arr) != 0 {
t.Errorf("persone_arr: got %v, want nil, input: %v", persone_arr, tt.input)
}
}
}

View file

@ -1,4 +1,4 @@
package main
package typedefs
import (
"os"
@ -206,6 +206,7 @@ type AnnuncioXML struct {
Url *string `xml:"Url"`
} `xml:"AMVideo"`
} `xml:"AMMedias"`
Dettaglio *string `xml:"Dettaglio"`
}
type AnnunciParsed struct {
@ -260,6 +261,9 @@ type AnnuncioParsed struct {
Url_video *[]string
Web *bool
Og_url *string
Disponibile_da *time.Time
Permanenza *[]int
Persone *[]string
}
type AnnunciDB struct {
@ -312,6 +316,9 @@ type AnnunciDB struct {
Creato_il *time.Time
Modificato_il *time.Time
Og_url *string
Disponibile_da *time.Time
Permanenza *[]int
Persone *[]string
}
type FilebrowserData struct {