diff --git a/backend/.env b/backend/.env index de45315..5cd6024 100644 --- a/backend/.env +++ b/backend/.env @@ -2,4 +2,5 @@ SMTP_SERVER=send.one.com SMTP_PORT=465 SMTP_USER=teeoff@teeoff.no SMTP_PASS=Shallot Distress43, Serving Smog Hangnail Shower -EMAIL_TO=erol.haagenrud@teeoff.no \ No newline at end of file +EMAIL_TO=erol.haagenrud@teeoff.no +GEMINI_API_KEY=AIzaSyDX_WCvZcH3Z8xRpH-XWaoeVYWuE0Wrlog \ No newline at end of file diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index a958cb9..0226cfe 100644 Binary files a/backend/__pycache__/main.cpython-311.pyc and b/backend/__pycache__/main.cpython-311.pyc differ diff --git a/backend/requirements.txt b/backend/requirements.txt index ff5702f..97d1232 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,4 +10,5 @@ apscheduler python-dotenv python-jose[cryptography] passlib[bcrypt] -pyotp \ No newline at end of file +pyotp +google-generativeai \ No newline at end of file diff --git a/backend/scrape_status.py b/backend/scrape_status.py index baf53fd..0a18990 100644 --- a/backend/scrape_status.py +++ b/backend/scrape_status.py @@ -106,6 +106,34 @@ async def run_daily_scraping(): warnings.append(f"❌ {f['name']}: Fant ikke elementet '{f['scrape_status_selector']}' i iframen") continue full_text = await element.inner_text() + + elif method == 'click_then_css': + # Vi forventer formatet: "knappe_selector||tekst_selector" + parts = f['scrape_status_selector'].split('||') + if len(parts) != 2: + warnings.append(f"❌ {f['name']}: Ugyldig selector for click_then_css (mangler ||)") + continue + + btn_selector, text_selector = parts + + # 1. Finn og klikk på knappen + btn = page.locator(btn_selector).first + if await btn.count() == 0: + warnings.append(f"❌ {f['name']}: Fant ikke knappen å klikke på: '{btn_selector}'") + continue + + await btn.click() + + # 2. Vent 2 sekunder så animasjonen (sidepanelet) rekker å bli ferdig + await asyncio.sleep(2) + + # 3. Les av teksten + element = page.locator(text_selector).first + if await element.count() == 0: + warnings.append(f"❌ {f['name']}: Fant ikke tekstboksen '{text_selector}' etter klikk") + continue + + full_text = await element.inner_text() else: warnings.append(f"⚠️ {f['name']}: Ukjent skrapemetode i databasen: '{method}'") diff --git a/backend/test_login.py b/backend/test_login.py new file mode 100644 index 0000000..f026cbf --- /dev/null +++ b/backend/test_login.py @@ -0,0 +1,47 @@ +import asyncio +import asyncpg +import os +from passlib.context import CryptContext + +DB_URL = os.getenv("DATABASE_URL", "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff") + +# Vi setter opp passord-sjekkeren AKKURAT slik main.py gjør det +pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") + +async def test_sannheten(): + print("\n" + "="*50) + print(" 🔍 TEE OFF SANNHETSSERUM") + print("="*50) + + username = "Envide Webutvikling" + test_password = "Solveig Vilde Ingvild Gina" # Sørg for at dette er det du satte sist! + + try: + conn = await asyncpg.connect(DB_URL) + row = await conn.fetchrow("SELECT password_hash FROM admins WHERE username = $1", username) + + if not row: + print("❌ FEIL: Fant ikke brukeren i det hele tatt!") + return + + db_hash = row['password_hash'] + print(f"1. Hash funnet i databasen: {db_hash[:30]}...") + + print(f"2. Tester mot passordet: '{test_password}'") + + # Den magiske testen + is_valid = pwd_context.verify(test_password, db_hash) + + print("-" * 50) + if is_valid: + print("✅ SUKSESS! Passordet og hashen stemmer 100% overens.") + print("➡️ KONKLUSJON: Hashingen fungerer perfekt. Problemet MÅ være at FastAPI (main.py) ikke klarer å lese JSON-dataene fra curl/frontend riktig.") + else: + print("❌ FEIL! Passordet stemmer IKKE med hashen i databasen.") + print("➡️ KONKLUSJON: Scriptet som oppdaterer passordet gjør en feil (f.eks. legger til usynlige tegn), eller lagringen i databasen blir korrupt.") + + finally: + await conn.close() + +if __name__ == "__main__": + asyncio.run(test_sannheten()) \ No newline at end of file diff --git a/backend/update_admin.py b/backend/update_admin.py new file mode 100644 index 0000000..4883f01 --- /dev/null +++ b/backend/update_admin.py @@ -0,0 +1,85 @@ +""" +TEE OFF ADMIN PASSWORD UPDATER (API CONTAINER VERSION) +--------------------------------------------------------------------------- +FUNKSJON: Kobler direkte til databasen inni API-containeren, sjekker at + brukeren finnes, og utfører passordoppdateringen automatisk. +STATUS: Påvirker IKKE tofaktor (2FA). Gjør jobben fra start til slutt. +--------------------------------------------------------------------------- +""" +import asyncio +import asyncpg +import os +import sys +import getpass +from passlib.hash import pbkdf2_sha256 + +# Henter database-URL fra miljøvariabler (samme metode som backenden din bruker) +DB_URL = os.getenv("DATABASE_URL", "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff") + +async def update_admin_password(): + print("\n" + "="*50) + print(" TEE OFF ADMIN PASSORD-OPPDATERER (DIREKTE TILKOBLING)") + print("="*50) + + # Kobler til databasen på ekte backend-vis + try: + conn = await asyncpg.connect(DB_URL) + except Exception as e: + print(f"❌ Kunne ikke koble til databasen: {e}") + sys.exit(1) + + try: + # Brukernavn-verifisering + while True: + username = input("Brukernavn på admin som skal oppdateres: ").strip() + + print("⏳ Sjekker databasen...") + # Spør databasen direkte hvor mange som har dette navnet + count = await conn.fetchval("SELECT COUNT(*) FROM admins WHERE username = $1", username) + + if count == 0: + print(f"❌ Fant ingen bruker med navnet '{username}'. Prøv igjen.\n") + elif count > 1: + print(f"⚠️ KRITISK FEIL: Fant {count} brukere med navnet '{username}'. Avbryter.") + sys.exit(1) + else: + print(f"✅ Bruker '{username}' funnet i databasen!\n") + break + + # Passord-verifisering + while True: + password = getpass.getpass("Skriv inn NYTT passord: ") + password_confirm = getpass.getpass("Gjenta NYTT passord: ") + + if password == password_confirm: + if len(password) < 8: + print("⚠️ Advarsel: Passordet bør være minst 8 tegn.") + print(f"\n[DEBUG] Passord akseptert.") + break + else: + print("❌ Passordene er ikke like. Prøv igjen.\n") + + print("⏳ Genererer PBKDF2-hash...") + password_hash = pbkdf2_sha256.hash(password) + + print("⏳ Oppdaterer databasen automatisk...") + # Utfører selve oppdateringen (sikret mot SQL-injeksjoner) + await conn.execute("UPDATE admins SET password_hash = $1 WHERE username = $2", password_hash, username) + + print("\n✅ PASSORD OPPDATERT VELLYKKET!") + print("-" * 50) + print(f"Passordet for '{username}' er nå endret i databasen.") + print("Tofaktor (2FA) og alt annet er beholdt urørt.") + print("-" * 50 + "\n") + + finally: + # Lukk tilkoblingen pent + await conn.close() + +if __name__ == "__main__": + try: + # Siden vi bruker asyncpg, må scriptet kjøres i en asyncio-loop + asyncio.run(update_admin_password()) + except KeyboardInterrupt: + print("\nAvbrutt.") + sys.exit(0) \ No newline at end of file diff --git a/fil-tre.txt b/fil-tre.txt index 7a33a89..26d6c9c 100644 --- a/fil-tre.txt +++ b/fil-tre.txt @@ -1,7 +1,9 @@ 📁 teeoff/ + 📄 test_tjome.py 📄 fil-tre.txt 📄 struktur2_dump.txt 📄 seed.sql + 📄 struktur3_dump.txt 📄 eksport_script.py 📄 update_golfbox.sql 📄 docker-compose.yml @@ -682,22 +684,32 @@ 📄 page.tsx 📁 kode_eksport_1/ 📄 frontend_src_components_Header_tsx.txt + 📄 backend_scrape_nsg_3_py.txt 📄 frontend_next-env_d_ts.txt 📄 frontend_src_app_layout_tsx.txt 📄 frontend_src_app_page_tsx.txt 📄 eksport_script_py.txt 📄 frontend_src_app_golfbaner_[slug]_page_tsx.txt + 📄 backend_import_wp_py.txt 📄 frontend_src_middleware_ts.txt + 📄 test_tjome_py.txt 📄 frontend_src_app_golfbaner_[slug]_CourseDisplay_tsx.txt 📄 frontend_next_config_ts.txt 📄 frontend_src_app_admin_login_page_tsx.txt 📄 frontend_src_app_golfbaner_[slug]_FacilityDetailView_tsx.txt + 📄 backend_main_py.txt 📄 frontend_src_app_admin_page_tsx.txt 📄 frontend_src_app_HeroSlider_tsx.txt + 📄 backend_create_admin_py.txt + 📄 backend_sync_greenfee_py.txt + 📄 backend_scrape_status_py.txt + 📄 backend_scrape_golfamore1_3_py.txt 📄 frontend_src_app_FacilitySearch_tsx.txt 📄 frontend_src_config_constants_ts.txt + 📄 backend_import_gallery_py.txt 📁 backend/ 📄 scrape_nsg_3.py + 📄 update_admin.py 📄 import_gallery.py 📄 .env 📄 sync_greenfee.py diff --git a/frontend/src/app/admin/login/page.tsx b/frontend/src/app/admin/login/page.tsx index 9f19401..084efbd 100644 --- a/frontend/src/app/admin/login/page.tsx +++ b/frontend/src/app/admin/login/page.tsx @@ -83,8 +83,8 @@ export default function AdminLogin() {