Skip to content

Geo - Technical Map

Bám sát code thực tế của module geo và backend geo-api / Hasura metadata.

Scope kỹ thuật

LớpFile chínhVai trò
FE widgetsdiva-admin/src/modules/geo/components/**Nhập/xuất địa chỉ, province/ward select, Google Map autocomplete
FE logicdiva-admin/src/modules/geo/compositions/useGeo.tsFetch country, cache vào store, local filter
FE statediva-admin/src/modules/geo/stores/useGeoStore.tsCache quốc gia dùng chung
FE typesdiva-admin/src/modules/geo/types.tsGeoJSON, Address, LatLng
FE GraphQLdiva-admin/src/modules/geo/graphql/geo.graphqlQuery country / province / district / ward
Backend SQLdiva-backend/services/controller/migrations/geo/**Schema, functions, triggers, seed data
Backend action servicediva-backend/services/geo-api/action/place.goSearch place, fallback Google Maps, normalize address

FE surface

Public exports

ExportFileGhi chú
AddressInputcomponents/index.tsExport chính từ module
AddressDisplaycomponents/index.tsExport chính từ module
ProvinceSelectcomponents/address/ProvinceSelect.tsxDùng ở nhiều module ngoài geo
DistrictSelectcomponents/address/DistrictSelect.tsxCó implementation thật nhưng không được AddressInput dùng mặc định
WardSelectcomponents/address/WardSelect.tsxCó cache localStorage + mapping ward cũ
GMapAutocompletecomponents/map/GMapAutocomplete.tsxWrapper GoogleMap + XInput
useGeo()compositions/useGeo.tsQuery quốc gia và filter local
useGeoStorestores/useGeoStore.tsPinia store geo

FE components theo chức năng

NhómFileVai trò
Address composeAddressInput.tsxGhép ProvinceSelect + WardSelect + street input; emit Address object
Address displayAddressDisplay.tsxRender địa chỉ 2 dòng
Province selectProvinceSelect.tsxQuery province, filter theo keywordsprovince_old
District selectDistrictSelect.tsxQuery district theo provinceCode, reset khi province đổi
Ward selectWardSelect.tsxQuery ward theo provinceCode, cache localStorage, hỗ trợ ward mapping
Google autocompleteGMapAutocomplete.tsxBọc Google Places Autocomplete vào XInput + GoogleMap

GraphQL operations

File: diva-admin/src/modules/geo/graphql/geo.graphql

OperationInputReturn fields chínhDùng ở
GetWardsward_bool_exp, ward_order_by, offset, limitcode, country_code, name, province_code, district_code, keywords, ward_mappingsWardSelect
GetDistrictsdistrict_bool_exp, district_order_by, offset, limitcode, country_code, name, province_code, keywordsDistrictSelect
GetProvincesprovince_bool_exp, province_order_by, offset, limitcode, country_code, name, keywords, province_oldProvinceSelect
GetCountrycountry_bool_exp, country_order_by, offset, limitiso, name, phone_code, flag_urluseGeo() / XInputPhone

FE state & caching

useGeoStore

ItemMô tảReference
Store idgeouseGeoStore.ts
Statecountries: Partial<Country>[]useGeoStore.ts
Seed mặc định1 country VNuseGeoStore.ts
ActionreplaceCountry(items)useGeoStore.ts

useGeo

LogicChi tiết
queryCountry()Gọi GraphQL query GetCountryDocument, requestPolicy = "network-only", rồi replace store nếu có data
getLocalCountry()Lấy cache từ store và lọc theo keywords
getCountry()Nếu cache rỗng hoặc refresh = true thì query lại trước khi lọc

Backend data model

Core tables / views

ObjectLoạiVai trò
countrytableQuốc gia, mã phone, flag, keywords, boundary
provincetableTỉnh/thành phố hiện tại
province_oldtableDữ liệu địa giới cũ, map sang province mới bằng new_province_code
districttableQuận/huyện hiện tại
wardtablePhường/xã hiện tại
ward_oldtableDữ liệu ward cũ, map sang ward mới
ward_mappingstableBảng mapping old/new ward
placetableBản ghi place đã normalize từ Google Maps hoặc nhập tay
search_place_resultviewShape trả về của search_place
search_subdivision_resultviewShape trả về của search_subdivision
user_position_statustableLưu online + vị trí user
user_position_distance_statusviewShape trả về của search_user_position_status
timezonetableDanh mục timezone seed

Columns quan trọng

ObjectCột nổi bật
countryiso, iso3, name, phone_code, flag_url, boundary, keywords
provincecountry_code, code, name, name_i18n, population, boundary, keywords, zip_code, disabled, code_1
districtcountry_code, province_code, code, name, name_i18n, boundary, keywords, zip_code, disabled
wardcountry_code, province_code, district_code, code, name, name_i18n, boundary, keywords, zip_code, disabled
placeid, country_code, province_code, district_code, ward_code, name, house_number, street, position, external_place_id, postal_code, tags, tsv
user_position_statusid, role, online, position, updated_at

Backend functions

Hasura metadata

FileNội dung
metadata/databases/geo/functions/functions.yamlInclude 3 function metadata files
public_search_place.yamlExpose function search_place
public_search_subdivision.yamlExpose function search_subdivision
public_search_user_position_status.yamlExpose function search_user_position_status

SQL function behavior

FunctionInputOutputGhi chú kỹ thuật
search_placeaddress, longitude, latitude, max_distance, current_longitude, current_latitudeSETOF search_place_resultSearch place.tsv, join province/district/ward, tính ST_DistanceSphere và lọc ST_DWithin
search_subdivisionlongitude, latitude, ward_keywords, district_keywords, province_keywords, country_isoSETOF search_subdivision_resultMatch keyword hoặc boundary contains, rồi ghép ward/district/province
search_user_position_statusonline, longitude, latitude, from_time, to_time, max_distance, has_position_onlySETOF user_position_distance_statusLọc theo status + time window + khoảng cách

geo-api action service

searchPlace

StepLogic
1Parse payload JSON vào searchPlaceInput
2Validate: phải có address hoặc cặp latitude/longitude
3Set default limit = 10, max_distance = 100 nếu không truyền
4Query Hasura function search_place trước bằng admin client
5Nếu DB không có kết quả và MapClient != nil, fallback sang Google Places Autocomplete + Place Details
6Chuẩn hóa address_component sang place_insert_input rồi gọi insert vào place

Parse địa chỉ từ Google Maps

Thành phần GoogleMap vào field
street_numberHouseNumber
routeStreet
administrative_area_level_1ProvinceCode
administrative_area_level_2 / localityDistrictCode
administrative_area_level_3 / sublocality / sublocality_level_1WardCode
countryCountryCode
postal_codePostalCode

Notes về insert place

LogicMô tả
NormalizeDùng NewGeoPoint(longitude, latitude) cho position
Refill subdivisionNếu đã có province code thì gọi lại search_subdivision để backfill ward/district/province theo tọa độ + keyword
PersistTạo insert_place mutation qua admin client với các object đã normalize

Permissions

Hasura select/insert/update

ObjectRoleQuyền
countryanonymous, customer, userselect
provinceanonymous, customer, userselect
province_oldanonymous, customer, userselect
districtanonymous, customer, userselect
wardanonymous, customer, userselect
ward_mappingsanonymous, customer, userselect
ward_oldanonymous, customer, userselect
user_position_statususerinsert, select, update theo chính X-Hasura-User-Id
user_position_distance_statususerselect theo chính X-Hasura-User-Id
placechưa thấy permission tách riêng trong file metadata đã đọccần kiểm tra thêm nếu muốn expose trực tiếp

Rủi ro / Findings kỹ thuật

#Ghi chú
1public_place.yaml chỉ register table place, chưa thấy permission chi tiết trong file metadata hiện tại.
2search_subdivision có điều kiện AND d.code IS NULL OR d.code = w.district_code dễ gây precedence bug nếu không bọc ngoặc.
3useGeo.ts filter local country chưa dùng biến formattedKeywords, đây là mismatch rõ giữa ý định sanitize và điều kiện includes().
4WardSelect lưu cache JSON vào localStorage mà không gắn version/dataset timestamp.
5geo-api/action/place.go là một lớp normalize khá nặng: vừa query DB, vừa fallback Google Maps, vừa insert place. Nếu fail ở giữa chừng, caller cần xử lý partial result.