import asyncio, asyncpg, urllib.request, json, re DB_URL = "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff" WP_API_URL = "https://teeoff.no/wp-json/wp/v2/golfbaner?per_page=100&_embed" def decode_html(text): if not text: return "" return str(text).replace('&', '&').replace('&', '&').replace(' ', ' ').strip() def parse_int(val): if val is None or val == '': return None try: nums = re.findall(r'\d+', str(val)) return int(nums[0]) if nums else None except: return None async def run_master_import(): print("🚀 Starter MASTER IMPORT v6.0 (ACF Mapped)...") conn = await asyncpg.connect(DB_URL) await conn.execute("TRUNCATE facilities, courses, holes RESTART IDENTITY CASCADE;") page = 1 while True: try: req = urllib.request.Request(f"{WP_API_URL}&page={page}", headers={'User-Agent': 'TeeOff-V6'}) with urllib.request.urlopen(req) as response: data = json.loads(response.read().decode()) except: break if not data: break for post in data: acf = post.get('acf', {}) name = decode_html(post.get('title', {}).get('rendered', '')) print(f"📦 Mapper {name}...") # 1. Medlemskap (Mappet mot field_6040...) membership = { "url": acf.get('medlemskap_url'), "standard": { "navn": decode_html(acf.get('navn_standard_medlemskap')), "pris": parse_int(acf.get('standard_medlemskap')), "kommentar": decode_html(acf.get('standard_medlemskap_kommentarer')) }, "rimeligste": { "navn": decode_html(acf.get('navn_rimeligste_alternativ')), "pris": parse_int(acf.get('rimeligste_alternativ')), "kommentar": decode_html(acf.get('rimeligste_alternativ_kommentarer')) } } # 2. Greenfee (Repeatere) greenfee = { "voksne": acf.get('greenfee_-_voksne') or [], "junior": acf.get('greenfee_-_junior') or [], "golfpakke": decode_html(acf.get('golfpakke')), "rabattert": acf.get('rabattert_greenfee') } # 3. Amenities (Fasiliteter) amenities = { "drivingrange": decode_html(acf.get("drivingrange")), "treningsgreen": decode_html(acf.get("treningsgreen")), "proshop": decode_html(acf.get("proshop")), "kafe": decode_html(acf.get("kafe")), "bilutleie": decode_html(acf.get("bilutleie")), "pro": decode_html(acf.get("pro")), "antall_hull": decode_html(acf.get("antall_hull")) } # 4. Lagre Facility fac_id = await conn.fetchval(''' INSERT INTO facilities ( name, slug, description, established_year, season, address, city, county, lat, lng, email, phone, website_url, image_url, amenities, greenfee, membership, vtg, status_updated_at, logo_url, video_url, guest_requirements, faqs, shotzoom, translations ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15::jsonb, $16::jsonb, $17::jsonb, $18::jsonb, TO_DATE(NULLIF($19, ''), 'YYYYMMDD'), $20, $21, $22, $23::jsonb, $24::jsonb, $25::jsonb) RETURNING id ''', name, post['slug'], decode_html(acf.get('beskrivelse')), parse_int(acf.get('byggear')), acf.get('sesong'), acf.get('gateadresse'), acf.get('postnummer_og_poststed'), acf.get('fylke'), float(acf.get('banekart', {}).get('lat', 0)) or None, float(acf.get('banekart', {}).get('lng', 0)) or None, acf.get('e-post'), acf.get('telefon'), acf.get('hjemmeside'), post.get('_embedded', {}).get('wp:featuredmedia', [{}])[0].get('source_url'), json.dumps(amenities), json.dumps(greenfee), json.dumps(membership), json.dumps(acf.get('vtg') or {}), acf.get('dato_for_oppdatert_status'), acf.get('logo'), f"https://youtube.com/embed/{acf.get('videopresentasjon_youtube')}" if acf.get('videopresentasjon_youtube') else None, decode_html(acf.get('krav_til_gjestespillere')), json.dumps(acf.get('faq') or []), json.dumps(acf.get('shotzoom') or []), json.dumps({})) # 5. Baner og Hull (Bruker ACF-felt for Hovedbane og Bane 2) for suffix in ['', '_bane_to']: course_name = acf.get('navn_pa_hovedbane' if suffix == '' else 'navn_pa_sekundar_bane') or ('Hovedbane' if suffix == '' else 'Bane 2') status = acf.get('banestatus' if suffix == '' else 'banestatus_sekundar_bane') # Sjekk om det i det hele tatt finnes data for denne banen if suffix == '_bane_to' and (status == 'finnes_ingen_bane_to' or not parse_int(acf.get('hull_1_par_bane_to'))): continue course_id = await conn.fetchval(''' INSERT INTO courses (facility_id, name, status, par, length_meters, is_main_course, tee_boxes) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb) RETURNING id ''', fac_id, course_name, status, parse_int(acf.get('totalt_par' if suffix == '' else 'totalt_par_bane_to')), parse_int(acf.get('lengde' if suffix == '' else 'lengst_totalt_bane_to')), (suffix == ''), json.dumps({"herrer": acf.get(f"utslag_herrer{suffix}"), "damer": acf.get(f"utslag_damer{suffix}")})) for h_num in range(1, 19): p = parse_int(acf.get(f'hull_{h_num}_par{suffix}')) if p: idx = parse_int(acf.get(f'hull_{h_num}_index{suffix}')) lengths = {k: parse_int(acf.get(f'{k}_hull_{h_num}{suffix}')) for k in ['lengst', 'lang', 'mellomlang', 'mellomkort', 'kort', 'kortest']} await conn.execute('INSERT INTO holes (course_id, hole_number, par, hcp_index, lengths) VALUES ($1, $2, $3, $4, $5::jsonb)', course_id, h_num, p, idx, json.dumps(lengths)) page += 1 await conn.close() print("✅ Ferdig! All data er nÃ¥ korrekt importert.") if __name__ == "__main__": asyncio.run(run_master_import())