Etter dagens økt. (Database mot database)
BIN
backend/__pycache__/main.cpython-311.pyc
Normal file
|
|
@ -1,7 +1,65 @@
|
|||
import asyncio, asyncpg, urllib.request, json, re
|
||||
import asyncio, asyncpg, urllib.request, json, re, os, requests
|
||||
|
||||
# --- KONFIGURASJON ---
|
||||
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"
|
||||
MEDIA_ENDPOINT = "https://teeoff.no/wp-json/wp/v2/media"
|
||||
MEDIA_DIR = "./public/media"
|
||||
|
||||
os.makedirs(MEDIA_DIR, exist_ok=True)
|
||||
|
||||
# Cache for å slippe å spørre om samme bilde-ID flere ganger
|
||||
media_cache = {}
|
||||
|
||||
def get_url_from_id(media_id):
|
||||
"""Slår opp en WordPress Media-ID og returnerer den faktiske bilde-URLen"""
|
||||
if not media_id or not isinstance(media_id, int):
|
||||
return None
|
||||
|
||||
if media_id in media_cache:
|
||||
return media_cache[media_id]
|
||||
|
||||
try:
|
||||
print(f" 🔍 Slår opp Media-ID: {media_id}...")
|
||||
resp = requests.get(f"{MEDIA_ENDPOINT}/{media_id}", timeout=10)
|
||||
if resp.status_code == 200:
|
||||
url = resp.json().get('source_url')
|
||||
media_cache[media_id] = url
|
||||
return url
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Kunne ikke finne URL for Media-ID {media_id}: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def download_media(url, slug, prefix):
|
||||
if not isinstance(url, str) or not url:
|
||||
return None
|
||||
|
||||
# Reparer "triple-slash" og andre WP-feil
|
||||
clean_url = url.replace("https:///", "https://").replace("http:///", "http://")
|
||||
|
||||
if "teeoff.no" not in clean_url:
|
||||
return clean_url
|
||||
|
||||
try:
|
||||
ext = clean_url.split('.')[-1].split('?')[0].lower()
|
||||
if len(ext) > 4 or len(ext) < 3: ext = "jpg"
|
||||
|
||||
filename = f"{prefix}_{slug}.{ext}"
|
||||
filepath = os.path.join(MEDIA_DIR, filename)
|
||||
|
||||
if os.path.exists(filepath):
|
||||
return f"/media/{filename}"
|
||||
|
||||
response = requests.get(clean_url, timeout=15)
|
||||
if response.status_code == 200:
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(response.content)
|
||||
return f"/media/{filename}"
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Feil ved nedlasting: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def decode_html(text):
|
||||
if not text: return ""
|
||||
|
|
@ -14,105 +72,127 @@ def parse_int(val):
|
|||
return int(nums[0]) if nums else None
|
||||
except: return None
|
||||
|
||||
def extract_url(val):
|
||||
if isinstance(val, dict): return val.get('url')
|
||||
if isinstance(val, str): return val
|
||||
return None
|
||||
|
||||
async def run_master_import():
|
||||
print("🚀 Starter MASTER IMPORT v6.0 (ACF Mapped)...")
|
||||
print("🚀 Starter MASTER IMPORT v8.9.2 (Media ID Resolver)...")
|
||||
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'})
|
||||
req = urllib.request.Request(f"{WP_API_URL}&page={page}", headers={'User-Agent': 'TeeOff-V8.9.2'})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
except: break
|
||||
except Exception: break
|
||||
if not data: break
|
||||
|
||||
for post in data:
|
||||
acf = post.get('acf', {})
|
||||
slug = post['slug']
|
||||
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'))
|
||||
}
|
||||
}
|
||||
# --- 1. HOVEDBILDE ---
|
||||
featured_img = post.get('_embedded', {}).get('wp:featuredmedia', [{}])[0].get('source_url')
|
||||
local_main_img = download_media(featured_img, slug, "main")
|
||||
|
||||
# --- 2. LOGO ---
|
||||
logo_field = acf.get('logo')
|
||||
logo_url = extract_url(logo_field)
|
||||
if not logo_url and isinstance(logo_field, int):
|
||||
logo_url = get_url_from_id(logo_field)
|
||||
local_logo = download_media(logo_url, slug, "logo")
|
||||
|
||||
# --- 3. GALLERI (SLIDER) ---
|
||||
slides = acf.get('slides') or []
|
||||
local_gallery = []
|
||||
if isinstance(slides, list):
|
||||
for idx, s in enumerate(slides):
|
||||
url = None
|
||||
if isinstance(s, int): # DIN CASE: Vi har en ID
|
||||
url = get_url_from_id(s)
|
||||
elif isinstance(s, dict):
|
||||
url = s.get('url')
|
||||
elif isinstance(s, str):
|
||||
url = s
|
||||
|
||||
if url:
|
||||
res = download_media(url, f"{slug}_{idx}", "slide")
|
||||
if res: local_gallery.append(res)
|
||||
|
||||
# 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')
|
||||
}
|
||||
# --- GOLFBOX & SOSIALE ---
|
||||
booking_id = acf.get('golfbox_booking_id')
|
||||
gb_booking_url = f"http://www.golfbox.no/site/system/redirect.asp?locale=nb_NO&rUrl=%2Fsite%2Fressources%2Fbooking%2Fgrid.asp%3FRessource_GUID%3D%{{{str(booking_id).strip().replace('{','').replace('}','')}}}" if booking_id else None
|
||||
|
||||
# 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 FACILITY ---
|
||||
await conn.execute('''
|
||||
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
|
||||
status_updated_at, logo_url, video_url, guest_requirements,
|
||||
faqs, shotzoom, gallery, ngf_number, golfbox_club_id, golfbox_booking_url,
|
||||
facebook_url, instagram_url, baneguide_url, flyfoto_url, golfbox_tournament_url,
|
||||
footnote, social_links, webcam_url, weather_url, architect,
|
||||
navn_standard_medlemskap, standard_medlemskap, standard_medlemskap_kommentarer,
|
||||
navn_rimeligste_alternativ, rimeligste_alternativ, rimeligste_alternativ_kommentarer,
|
||||
medlemskap_url
|
||||
) 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'),
|
||||
$16::jsonb, TO_DATE(NULLIF($17, ''), 'YYYYMMDD'),
|
||||
$18, $19, $20, $21::jsonb, $22::jsonb, $23::jsonb,
|
||||
$24, $25, $26, $27, $28, $29, $30, $31, $32, $33::jsonb, $34, $35, $36,
|
||||
$37, $38, $39, $40, $41, $42, $43)
|
||||
''',
|
||||
name, 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({}))
|
||||
float(acf.get('banekart', {}).get('lat', 0)) if acf.get('banekart') else None,
|
||||
float(acf.get('banekart', {}).get('lng', 0)) if acf.get('banekart') else None,
|
||||
acf.get('e-post'), acf.get('telefon'), extract_url(acf.get('hjemmeside')),
|
||||
local_main_img,
|
||||
json.dumps({"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")), "kolleutleie": decode_html(acf.get("kolleutleie")), "pro": decode_html(acf.get("pro")), "simulator": decode_html(acf.get("golfsimulator")), "antall_hull": decode_html(acf.get("antall_hull"))}),
|
||||
json.dumps(acf.get('greenfee_-_voksne') or []),
|
||||
acf.get('dato_for_oppdatert_status'), local_logo,
|
||||
None, decode_html(acf.get('krav_til_gjestespillere')),
|
||||
json.dumps([]), json.dumps(acf.get('shotzoom') or []), json.dumps(local_gallery),
|
||||
parse_int(acf.get('klubbnummer_norges_golfforbund')), parse_int(acf.get('klubbnummer_golfbox')),
|
||||
gb_booking_url, extract_url(acf.get('facebook_url')), extract_url(acf.get('instagram_url')),
|
||||
extract_url(acf.get('baneguide')), extract_url(acf.get('flyfoto')), extract_url(acf.get('golfbox')),
|
||||
decode_html(acf.get('fotnote')), json.dumps(acf.get('sosiale_lenker') or []),
|
||||
decode_html(acf.get('webkamera')), extract_url(acf.get('varmelding_yr')), decode_html(acf.get('arkitekt')),
|
||||
decode_html(acf.get('navn_standard_medlemskap')), parse_int(acf.get('standard_medlemskap')),
|
||||
decode_html(acf.get('standard_medlemskap_kommentarer')), decode_html(acf.get('navn_rimeligste_alternativ')),
|
||||
parse_int(acf.get('rimeligste_alternativ')), decode_html(acf.get('rimeligste_alternativ_kommentarer')),
|
||||
extract_url(acf.get('medlemskap_url')))
|
||||
|
||||
# 5. Baner og Hull (Bruker ACF-felt for Hovedbane og Bane 2)
|
||||
# Hent facility id for baner
|
||||
fac_row = await conn.fetchrow("SELECT id FROM facilities WHERE slug = $1", slug)
|
||||
fac_id = fac_row['id']
|
||||
|
||||
# --- BANER OG HULL (Samme som før) ---
|
||||
fac_main_len = 0
|
||||
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')
|
||||
c_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}")}))
|
||||
|
||||
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, is_main_course, tee_boxes, architect) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id', fac_id, c_name, status, parse_int(acf.get('totalt_par' if suffix == '' else 'totalt_par_bane_to')), (suffix == ''), json.dumps({"herrer": acf.get(f"utslag_herrer{suffix}"), "damer": acf.get(f"utslag_damer{suffix}")}), decode_html(acf.get('arkitekt')))
|
||||
curr_len = 0
|
||||
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))
|
||||
lens = {k: parse_int(acf.get(f'{k}_hull_{h_num}{suffix}')) for k in ['lengst', 'lang', 'mellomlang', 'mellomkort', 'kort', 'kortest']}
|
||||
curr_len += (lens['lengst'] or 0)
|
||||
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(lens))
|
||||
await conn.execute("UPDATE courses SET length_meters = $1 WHERE id = $2", curr_len, course_id)
|
||||
if suffix == '': fac_main_len = curr_len
|
||||
await conn.execute("UPDATE facilities SET length_meters = $1 WHERE id = $2", fac_main_len, fac_id)
|
||||
page += 1
|
||||
await conn.close()
|
||||
print("✅ Ferdig! All data er nå korrekt importert.")
|
||||
print("✅ GALLERI-BILDER RESOLVED OG IMPORT FERDIG!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_master_import())
|
||||
asyncio.run(run_master_import())
|
||||
|
|
@ -3,3 +3,4 @@ uvicorn[standard]
|
|||
asyncpg
|
||||
httpx
|
||||
beautifulsoup4
|
||||
requests
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ services:
|
|||
container_name: teeoff_api
|
||||
ports:
|
||||
- "8001:8000"
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
# Denne linjen sørger for at bilder lastet ned av import_wp.py
|
||||
# lagres direkte i frontendens public-mappe på serveren din:
|
||||
- ./frontend/public/media:/app/public/media
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
|
|
@ -33,4 +38,4 @@ services:
|
|||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
teeoff_db_data:
|
||||
teeoff_db_data:
|
||||
72
eksport_script.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
# --- KONFIGURASJON ---
|
||||
KILDE_MAPPE = "/opt/teeoff/"
|
||||
EKSPORT_MAPPE = "/opt/teeoff/kode_eksport/"
|
||||
TRE_FIL = "/opt/teeoff/filtre.txt"
|
||||
|
||||
# Filtyper vi vil kopiere
|
||||
FILTYPER = ['.py', '.ts', '.tsx']
|
||||
|
||||
# Mapper vi IKKE vil ha med i treet eller skanne (sparer tid og rot)
|
||||
IGNORER_MAPPER = ['.git', 'node_modules', '__pycache__', 'kode_eksport', '.next']
|
||||
|
||||
def generer_tre_og_kopier():
|
||||
kilde_sti = Path(KILDE_MAPPE)
|
||||
eksport_sti = Path(EKSPORT_MAPPE)
|
||||
|
||||
# 1. Opprett eksportmappen hvis den ikke finnes
|
||||
eksport_sti.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
tre_linjer = []
|
||||
kopierte_filer = 0
|
||||
|
||||
print("Skanner filer og genererer tre...")
|
||||
|
||||
# 2. Gå gjennom alle mapper og filer
|
||||
for root, dirs, files in os.walk(kilde_sti):
|
||||
# Fjern ignorerte mapper så vi ikke går inn i dem
|
||||
dirs[:] = [d for d in dirs if d not in IGNORER_MAPPER]
|
||||
|
||||
# Regn ut innrykk basert på hvor dypt vi er i mappestrukturen
|
||||
nivaa = root.replace(KILDE_MAPPE, '').count(os.sep)
|
||||
innrykk = ' ' * 4 * nivaa
|
||||
mappe_navn = os.path.basename(root)
|
||||
|
||||
# Legg til mappen i treet
|
||||
if mappe_navn:
|
||||
tre_linjer.append(f"{innrykk}📁 {mappe_navn}/")
|
||||
else:
|
||||
tre_linjer.append(f"📁 {kilde_sti.name}/")
|
||||
|
||||
sub_innrykk = ' ' * 4 * (nivaa + 1)
|
||||
|
||||
# 3. Gå gjennom filene i mappen
|
||||
for fil in files:
|
||||
tre_linjer.append(f"{sub_innrykk}📄 {fil}")
|
||||
|
||||
fil_sti = Path(root) / fil
|
||||
|
||||
# 4. Sjekk om filen har riktig endelse og skal kopieres
|
||||
if fil_sti.suffix in FILTYPER:
|
||||
# Lag et unikt filnavn for å unngå overskriving
|
||||
relativ_sti = fil_sti.relative_to(kilde_sti)
|
||||
nytt_navn = str(relativ_sti).replace(os.sep, '_').replace('.', '_') + '.txt'
|
||||
ny_sti = eksport_sti / nytt_navn
|
||||
|
||||
# Kopier filen
|
||||
shutil.copy2(fil_sti, ny_sti)
|
||||
kopierte_filer += 1
|
||||
|
||||
# 5. Lagre filteret til tekstfilen
|
||||
with open(TRE_FIL, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(tre_linjer))
|
||||
|
||||
print(f"\n✅ Ferdig!")
|
||||
print(f"📁 Filtre er lagret i: {TRE_FIL}")
|
||||
print(f"📝 Kopierte {kopierte_filer} kodefiler til: {EKSPORT_MAPPE}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
generer_tre_og_kopier()
|
||||
47
filtre.txt
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
📁 teeoff/
|
||||
📄 seed.sql
|
||||
📄 docker-compose.yml
|
||||
📄 schema.sql
|
||||
📄 init.sql
|
||||
📁 frontend/
|
||||
📄 eslint.config.mjs
|
||||
📄 next-env.d.ts
|
||||
📄 tsconfig.json
|
||||
📄 README.md
|
||||
📄 next.config.ts
|
||||
📄 postcss.config.mjs
|
||||
📄 package-lock.json
|
||||
📄 .gitignore
|
||||
📄 package.json
|
||||
📄 Dockerfile
|
||||
📁 public/
|
||||
📄 globe.svg
|
||||
📄 vercel.svg
|
||||
📄 Toppbilde-standard.jpg
|
||||
📄 TeeOff-logo-Retina-1.png
|
||||
📄 window.svg
|
||||
📄 next.svg
|
||||
📄 file.svg
|
||||
📁 src/
|
||||
📁 config/
|
||||
📄 constants.ts
|
||||
📁 app/
|
||||
📄 FacilitySearch.tsx
|
||||
📄 HeroSlider.tsx
|
||||
📄 favicon.ico
|
||||
📄 globals.css
|
||||
📄 page.tsx
|
||||
📄 layout.tsx
|
||||
📁 golfbaner/
|
||||
📁 [slug]/
|
||||
📄 CourseDisplay.tsx
|
||||
📄 page.tsx
|
||||
📄 FacilityDetailView.tsx
|
||||
📁 backend/
|
||||
📄 scrape_nsg_3.py
|
||||
📄 import_gallery.py
|
||||
📄 scrape_golfamore1.3.py
|
||||
📄 requirements.txt
|
||||
📄 import_wp.py
|
||||
📄 main.py
|
||||
📄 Dockerfile
|
||||
BIN
frontend/public/media/logo_alesund-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/media/logo_alsten-golfklubb.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
frontend/public/media/logo_alta-golfklubb.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
frontend/public/media/logo_arendal-omegn-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
frontend/public/media/logo_asker-golfklubb.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
frontend/public/media/logo_askim-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
frontend/public/media/logo_atlungstad-golfklubb.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
frontend/public/media/logo_aurskog-golfpark.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
frontend/public/media/logo_austratt-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
frontend/public/media/logo_baerum-golfklubb.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
frontend/public/media/logo_ballerud-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
frontend/public/media/logo_bamble-golfklubb.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
frontend/public/media/logo_bergen-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
frontend/public/media/logo_bjaavann-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
frontend/public/media/logo_bjornefjorden-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
frontend/public/media/logo_bleik-golfstrombane.jpg
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
frontend/public/media/logo_bodo-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
frontend/public/media/logo_borre-golfklubb.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
frontend/public/media/logo_borregaard-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
frontend/public/media/logo_byneset-golf.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
frontend/public/media/logo_dalane-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/public/media/logo_drammen-golfklubb.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
frontend/public/media/logo_drobak-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
frontend/public/media/logo_egersund-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
frontend/public/media/logo_eidskog-golfklubb.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend/public/media/logo_eiker-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
frontend/public/media/logo_ekholtbruket-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/media/logo_elverum-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
frontend/public/media/logo_fana-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
frontend/public/media/logo_fet-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
frontend/public/media/logo_floro-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
frontend/public/media/logo_frosta-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
frontend/public/media/logo_gamle-fredrikstad-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
frontend/public/media/logo_garder-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/public/media/logo_giske-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
frontend/public/media/logo_gjerdrum-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
frontend/public/media/logo_gjersjoen-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
frontend/public/media/logo_gjovik-og-toten-golfklubb.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
frontend/public/media/logo_grenland-og-omegn-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 8 KiB |
BIN
frontend/public/media/logo_grimstad-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
frontend/public/media/logo_grini-golfklubb.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/public/media/logo_gronmo-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
frontend/public/media/logo_groruddalen-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
frontend/public/media/logo_gumoy-golf.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
frontend/public/media/logo_hafjell-golfklubb.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
frontend/public/media/logo_haga-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
frontend/public/media/logo_hakadal-golfklubb.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
frontend/public/media/logo_halden-golfklubb.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
frontend/public/media/logo_hallingdal-golfklubb.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
frontend/public/media/logo_hammerfest-og-kvalsund-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
frontend/public/media/logo_hardanger-golfklubb.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
frontend/public/media/logo_harstad-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
frontend/public/media/logo_hasvik-golfklubb.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
frontend/public/media/logo_haugaland-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
frontend/public/media/logo_hauger-golfklubb.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
frontend/public/media/logo_haugesund-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
frontend/public/media/logo_helgeland-golfklubb.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
frontend/public/media/logo_hemsedal-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/media/logo_herdla-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
frontend/public/media/logo_hinnoy-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
frontend/public/media/logo_hitra-golfklubb.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
frontend/public/media/logo_hof-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
frontend/public/media/logo_holtsmark-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
frontend/public/media/logo_hovden-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
frontend/public/media/logo_hurum-golfklubb.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/media/logo_huseby-hanko-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
frontend/public/media/logo_husoy-golfklubb.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/media/logo_hvaler-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
frontend/public/media/logo_hvam-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/media/logo_ibestad-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
frontend/public/media/logo_imjelt-pitch-putt.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
frontend/public/media/logo_jaeren-golfklubb.png
Normal file
|
After Width: | Height: | Size: 518 B |
BIN
frontend/public/media/logo_karasjok-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
frontend/public/media/logo_karmoy-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
frontend/public/media/logo_kjekstad-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
frontend/public/media/logo_klaebu-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
frontend/public/media/logo_kongsberg-golfklubb.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
frontend/public/media/logo_kongsvingers-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
frontend/public/media/logo_kragero-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
frontend/public/media/logo_kristiansand-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/public/media/logo_kristiansund-og-omegn-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
frontend/public/media/logo_krokhol-golfklubb.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
frontend/public/media/logo_kvinnherad-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
frontend/public/media/logo_laerdal-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
frontend/public/media/logo_land-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
frontend/public/media/logo_larvik-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
frontend/public/media/logo_lillestrom-golfklubb.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
frontend/public/media/logo_lindesnes-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/media/logo_lofoten-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
frontend/public/media/logo_lommedalen-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
frontend/public/media/logo_lonne-golfklubb.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/media/logo_losby-golfklubb.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/public/media/logo_mandal-golfklubb.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
frontend/public/media/logo_meland-golfklubb.png
Normal file
|
After Width: | Height: | Size: 2 KiB |