from fastapi import HTTPException
import requests
from sqlalchemy import text
from core.config import GEONETWORK_PASS, GEONETWORK_URL, GEONETWORK_USER
from database.connection import sync_engine as engine
from datetime import datetime
from uuid import uuid4
import re
def create_gn_session():
session = requests.Session()
session.auth = (GEONETWORK_USER, GEONETWORK_PASS)
session.get(f"{GEONETWORK_URL}/srv/eng/info?type=me")
xsrf_token = session.cookies.get("XSRF-TOKEN")
if not xsrf_token:
raise Exception("XSRF token missing")
return session, xsrf_token
def escape_url_params(url: str) -> str:
"""
Escape karakter berbahaya di dalam URL agar valid dalam XML.
Khususnya mengganti '&' menjadi '&' kecuali jika sudah '&'.
"""
# Ganti semua & yang bukan bagian dari &
url = re.sub(r'&(?!amp;)', '&', url)
return url
def fix_xml_urls(xml: str) -> str:
"""
Temukan semua ... dalam XML dan escape URL-nya.
"""
def replacer(match):
original = match.group(1).strip()
fixed = escape_url_params(original)
return f"{fixed}"
# Replace semua ...
xml_fixed = re.sub(
r"(.*?)",
replacer,
xml,
flags=re.DOTALL
)
return xml_fixed
def get_extent(table_name: str):
sql = f"""
SELECT
ST_XMin(extent), ST_YMin(extent),
ST_XMax(extent), ST_YMax(extent)
FROM (
SELECT ST_Extent(geom) AS extent
FROM public.{table_name}
) AS box;
"""
conn = engine.connect()
try:
row = conn.execute(text(sql)).fetchone()
finally:
conn.close()
if not row or row[0] is None:
return None
# return {
# "xmin": float(row[0]),
# "ymin": float(row[1]),
# "xmax": float(row[2]),
# "ymax": float(row[3])
# }
return {
"xmin": 110.1372, # west
"ymin": -9.3029, # south
"xmax": 114.5287, # east
"ymax": -5.4819 # north
}
def get_author_metadata(table_name: str):
sql = """
SELECT am.table_title, am.dataset_title, am.dataset_abstract, am.keywords, am.date_created,
am.organization_name, am.contact_person_name, am.created_at,
am.contact_email, am.contact_phone, am.geom_type,
u.organization_id,
o.address AS organization_address,
o.email AS organization_email,
o.phone_number AS organization_phone
FROM backend.author_metadata AS am
LEFT JOIN backend.users u ON am.user_id = u.id
LEFT JOIN backend.organizations o ON u.organization_id = o.id
WHERE am.table_title = :table
LIMIT 1
"""
conn = engine.connect()
try:
row = conn.execute(text(sql), {"table": table_name}).fetchone()
finally:
conn.close()
if not row:
raise Exception(f"Tidak ada metadata untuk tabel: {table_name}")
return dict(row._mapping)
def map_geom_type(gtype):
if gtype is None:
return "surface"
# Jika LIST → ambil elemen pertama
if isinstance(gtype, list):
if len(gtype) > 0:
gtype = gtype[0]
else:
return "surface"
# Setelah pasti string
gtype = str(gtype).lower()
if "polygon" in gtype or "multi" in gtype:
return "surface"
if "line" in gtype:
return "curve"
if "point" in gtype:
return "point"
return "surface"
def generate_metadata_xml(table_name, meta, extent, geoserver_links):
keywords_xml = "".join([
f"""
{kw.strip()}
""" for kw in meta["keywords"].split(",")
])
geom_type_code = map_geom_type(meta["geom_type"])
print('type', geom_type_code)
uuid = str(uuid4())
return f"""
{uuid}
{meta['contact_person_name']}
{meta['organization_name']}
{meta['organization_phone']}
{meta['organization_phone']}
{meta['organization_address']}
Surabaya
Jawa Timur
Indonesia
{meta['organization_email']}
08.00-16.00
{datetime.utcnow().isoformat()}+07:00
ISO 19115:2003/19139
1.0
38
4326
EPSG
{meta['dataset_title']}
{meta['created_at'].isoformat()}+07:00
{meta['date_created'].year}
{meta['contact_person_name']}
{meta['organization_name']}
{meta['organization_phone']}
{meta['organization_phone']}
{meta['organization_address']}
Surabaya
Indonesia
{meta['organization_email']}
08.00-16.00
Timezone: UTC+7 (Asia/Jakarta)
{meta['dataset_abstract']}
{meta['dataset_abstract']}
Lab AI Polinema
Lab AI Polinema
{meta['organization_phone']}
{meta['organization_phone']}
{meta['organization_address']}
Surabaya
Jawa Timur
Indonesia
{meta['organization_email']}
{keywords_xml}
Penggunaan data harus mencantumkan sumber: {meta['organization_name']}.
25000
{extent['xmin']}
{extent['xmax']}
{extent['ymin']}
{extent['ymax']}
true
{meta['dataset_title']}
{meta['created_at'].isoformat()}+07:00
{meta['date_created'].year}
{geoserver_links["wms_url"]}
DB:POSTGIS
{meta["dataset_title"]}
{meta["dataset_title"]}
{geoserver_links["wms_url"]}
WWW:LINK-1.0-http--link
{meta["dataset_title"]}
{meta["dataset_title"]}
{geoserver_links["wms_url"]}
OGC:WMS
{meta["dataset_title"]}
{geoserver_links["wfs_url"]}
OGC:WFS
{meta["dataset_title"]}
Data dihasilkan dari digitasi peta dasar skala 1:25000 menggunakan QGIS.
"""
# Geonetwork version 4.4.9.0
def upload_metadata_to_geonetwork(xml_metadata: str):
# session = requests.Session()
# session.auth = (GEONETWORK_USER, GEONETWORK_PASS)
# # 1. Get XSRF token
# try:
# info_url = f"{GEONETWORK_URL}/srv/eng/info?type=me"
# session.get(info_url)
# except requests.exceptions.RequestException as e:
# raise HTTPException(status_code=503, detail=f"Failed to connect to GeoNetwork: {e}")
# xsrf_token = session.cookies.get('XSRF-TOKEN')
# if not xsrf_token:
# raise HTTPException(status_code=500, detail="Could not retrieve XSRF-TOKEN from GeoNetwork.")
session, xsrf_token = create_gn_session()
headers = {
'X-XSRF-TOKEN': xsrf_token,
'Accept': 'application/json'
}
GN_API_RECORDS_URL = f"{GEONETWORK_URL}/srv/api/records"
# 2. GeoNetwork requires a multipart/form-data upload
files = {
'file': ('metadata.xml', xml_metadata, 'application/xml')
}
params = {
"ownerGroup": 1, # all
"ownerUser": 1 # admin
}
response = session.post(
GN_API_RECORDS_URL,
params=params,
files=files,
headers=headers,
cookies=session.cookies.get_dict()
)
metadata_infos = response.json().get("metadataInfos", {})
uuid = None
for records in metadata_infos.values():
if records and isinstance(records, list):
uuid = records[0].get("uuid")
break
if not uuid:
raise ValueError("UUID not found in GeoNetwork response")
publish_record(session, uuid)
# print("response", response.json())
return uuid
def publish_metadata(table_name: str, geoserver_links: dict):
extent = get_extent(table_name)
meta = get_author_metadata(table_name)
xml = generate_metadata_xml(
table_name=meta["dataset_title"],
meta=meta,
extent=extent,
geoserver_links=geoserver_links
)
xml_clean = fix_xml_urls(xml)
uuid = upload_metadata_to_geonetwork(xml_clean)
print(f"[GeoNetwork] Metadata uploaded. UUID = {uuid}")
return uuid
def publish_record(session, uuid):
print('[uuid]', uuid)
xsrf_token = session.cookies.get('XSRF-TOKEN')
headers = {
"X-XSRF-TOKEN": xsrf_token,
"Accept": "application/json",
"Content-Type": "application/json"
}
url = f"{GEONETWORK_URL}/srv/api/records/{uuid}/sharing"
payload = {
"clear": True,
"privileges": [
{
"group": 1,
"operations": {
"view": True
}
}
]
}
response = session.put(url, json=payload, headers=headers)
response.raise_for_status()
# single stand func
# def publish_record(uuid):
# session, xsrf_token = create_gn_session()
# headers = {
# "X-XSRF-TOKEN": xsrf_token,
# "Content-Type": "application/json"
# }
# url = f"{GEONETWORK_URL}/srv/api/records/{uuid}/sharing"
# payload = {
# "clear": True,
# "privileges": [
# {"group": 1, "operations": {"view": True}}
# ]
# }
# resp = session.put(url, json=payload, headers=headers)
# resp.raise_for_status()