feat: parse project metadata from PF links
This commit is contained in:
@@ -356,6 +356,10 @@ func (a *App) CreateProject(ctx context.Context, ownerID int64, p ProjectPayload
|
||||
}
|
||||
p.Title = title
|
||||
p.DealType = deal
|
||||
p, err = a.enrichProjectPayloadFromURL(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateProjectRequired(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -395,6 +399,10 @@ func (a *App) UpdateProject(ctx context.Context, ownerID, projectID int64, p Pro
|
||||
p = mergeProjectPayload(current, p)
|
||||
p.Title = title
|
||||
p.DealType = deal
|
||||
p, err = a.enrichProjectPayloadFromURL(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateProjectRequired(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -437,6 +445,51 @@ func mergeProjectPayload(current *Project, p ProjectPayload) ProjectPayload {
|
||||
return p
|
||||
}
|
||||
|
||||
func (a *App) enrichProjectPayloadFromURL(ctx context.Context, p ProjectPayload) (ProjectPayload, error) {
|
||||
url := cleanPtr(p.OurURL)
|
||||
if url == nil || a.Worker == nil {
|
||||
return p, nil
|
||||
}
|
||||
parsed, err := a.Worker.ParseOwnListing(ctx, *url)
|
||||
if err != nil {
|
||||
if projectMissingParsedFields(p) {
|
||||
return p, fmt.Errorf("parse our_url: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
return applyParsedOwnListing(p, parsed), nil
|
||||
}
|
||||
|
||||
func projectMissingParsedFields(p ProjectPayload) bool {
|
||||
return p.OurPrice == nil ||
|
||||
cleanPtr(p.DLDPermit) == nil ||
|
||||
cleanPtr(p.Building) == nil ||
|
||||
p.Bedrooms == nil ||
|
||||
p.SizeSqft == nil
|
||||
}
|
||||
|
||||
func applyParsedOwnListing(p ProjectPayload, parsed *ParsedOwnListing) ProjectPayload {
|
||||
if parsed == nil {
|
||||
return p
|
||||
}
|
||||
if parsed.OurPrice != nil && *parsed.OurPrice > 0 {
|
||||
p.OurPrice = parsed.OurPrice
|
||||
}
|
||||
if permit := cleanPtr(parsed.DLDPermit); permit != nil {
|
||||
p.DLDPermit = permit
|
||||
}
|
||||
if building := cleanPtr(parsed.Building); building != nil {
|
||||
p.Building = building
|
||||
}
|
||||
if parsed.Bedrooms != nil {
|
||||
p.Bedrooms = parsed.Bedrooms
|
||||
}
|
||||
if parsed.SizeSqft != nil && *parsed.SizeSqft > 0 {
|
||||
p.SizeSqft = parsed.SizeSqft
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func validateProjectRequired(p ProjectPayload) error {
|
||||
if cleanString(p.Title) == "" {
|
||||
return fmt.Errorf("title is required")
|
||||
|
||||
@@ -65,3 +65,41 @@ func TestValidateProjectRequiredRejectsListingLikeURLWithoutID(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParsedOwnListingFillsProjectMetadata(t *testing.T) {
|
||||
payload := ProjectPayload{
|
||||
Title: "Full Park View",
|
||||
DealType: "sale",
|
||||
OurURL: strPtr(
|
||||
"https://www.propertyfinder.ae/en/plp/buy/apartment-for-sale-dubai-dubai-creek-harbour-the-lagoons-harbour-gate-harbour-gate-tower-2-86176216.html",
|
||||
),
|
||||
}
|
||||
parsed := &ParsedOwnListing{
|
||||
OurPrice: float64Ptr(3500000),
|
||||
DLDPermit: strPtr("7140504127"),
|
||||
Building: strPtr("Harbour Gate Tower 2"),
|
||||
Bedrooms: int64Ptr(2),
|
||||
SizeSqft: float64Ptr(1081),
|
||||
}
|
||||
|
||||
payload = applyParsedOwnListing(payload, parsed)
|
||||
|
||||
if err := validateProjectRequired(payload); err != nil {
|
||||
t.Fatalf("validateProjectRequired() after parsed metadata returned error: %v", err)
|
||||
}
|
||||
if payload.OurPrice == nil || *payload.OurPrice != 3500000 {
|
||||
t.Fatalf("our_price was not applied: %#v", payload.OurPrice)
|
||||
}
|
||||
if payload.DLDPermit == nil || *payload.DLDPermit != "7140504127" {
|
||||
t.Fatalf("dld_permit was not applied: %#v", payload.DLDPermit)
|
||||
}
|
||||
if payload.Building == nil || *payload.Building != "Harbour Gate Tower 2" {
|
||||
t.Fatalf("building was not applied: %#v", payload.Building)
|
||||
}
|
||||
if payload.Bedrooms == nil || *payload.Bedrooms != 2 {
|
||||
t.Fatalf("bedrooms was not applied: %#v", payload.Bedrooms)
|
||||
}
|
||||
if payload.SizeSqft == nil || *payload.SizeSqft != 1081 {
|
||||
t.Fatalf("size_sqft was not applied: %#v", payload.SizeSqft)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,16 @@ type Suggestion struct {
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type ParsedOwnListing struct {
|
||||
Title *string `json:"title"`
|
||||
OurPrice *float64 `json:"our_price"`
|
||||
DLDPermit *string `json:"dld_permit"`
|
||||
Building *string `json:"building"`
|
||||
Bedrooms *int64 `json:"bedrooms"`
|
||||
SizeSqft *float64 `json:"size_sqft"`
|
||||
Currency *string `json:"currency"`
|
||||
}
|
||||
|
||||
type SuggestionsResponse struct {
|
||||
OurPermit *string `json:"our_permit"`
|
||||
BayutEnabled bool `json:"bayut_enabled"`
|
||||
@@ -108,6 +118,14 @@ func (w *Worker) Suggest(ctx context.Context, projectID int64) (*SuggestionsResp
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (w *Worker) ParseOwnListing(ctx context.Context, url string) (*ParsedOwnListing, error) {
|
||||
var out ParsedOwnListing
|
||||
if err := w.call(ctx, "parse-own-listing", map[string]any{"url": url}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (w *Worker) Health(ctx context.Context) error {
|
||||
var out HealthResult
|
||||
if err := w.call(ctx, "health", map[string]any{}, &out); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user