Appearance
Google
The Google module is Lubb ERP's SEO and Google Business Profile toolkit: it tracks keyword search-engine rankings for one or more websites, and manages a business's local listing (profile, photos, reviews, structured data) with an optional live sync to Google Business Profile (GBP). It's a native port of a legacy standalone "SEO Panel" app, now installed per-tenant as a Lubb app and used by admin staff to monitor SEO health and manage a business's Google presence — there's no customer-facing view.
Search Console integration is referenced in the code as a planned future phase and is not implemented yet; only rank tracking and Business Profile listings are live today. Every screen requires at least the admin role in the active tenant, and all data (websites, keywords, ranks, listings, reviews, the GBP connection) is scoped per tenant.
What you can do
- Maintain a list of tracked Websites (name + URL) to run SEO against.
- Add Keywords per website, one at a time or in bulk (paste many at once, duplicates skipped case-insensitively), each with its own language and country code.
- Check rank now for selected keywords — a bulk action that queries the current SERP position via the self-hosted OpenSERP scraper or the paid DataForSEO API, and caches each keyword's latest rank.
- View a Rank Tracking list of keywords sorted best-rank-first, and per-keyword rank history over time.
- Maintain one or more Listings (Google Business Profile-style records): business name, category, description, phone, address, city/region/postal/country, Maps URL, opening hours, logo/cover/gallery photos.
- Check metadata for a listing — fetches the listing's website and scores its SEO readiness (title, meta description, Open Graph tags, structured data), flags issues by severity, and auto-fills the business name/description the first time they're blank.
- Open a listing's Advisory tab: prioritised fixes from the last metadata check, a ready-to-paste
LocalBusinessJSON-LD snippet built from the listing's fields, and step-by-step guidance for setting categories correctly in Google Business Profile. - Log and manage Reviews against a listing — enter them manually, or sync live reviews from Google once connected.
- Draft a starter reply to a review (heuristic templates, not AI) and save/post it — replies to Google-sourced reviews post live back to Google; manually-logged reviews just save the reply locally to copy/paste.
- Connect a Google Business Profile account via OAuth, see connection status, list the account's locations, link a listing to a specific GBP location, push listing profile data to Google, and disconnect.
Common tasks
Track keyword rankings for a website Open Websites and add the site's name and URL. Open Keywords, pick the website, and add keywords (use the bulk-add option to paste a list at once). Select the keywords you want checked now and run the "Check rank" bulk action — it uses the tenant's default search engine (created automatically on first use) and records today's rank plus the ranking URL for each keyword.
Review ranking trends Open Rank Tracking to see every active keyword's most recent rank (best rank first, unranked keywords last). Open an individual keyword to see its full rank history over time.
Set up a Business Profile listing Open Listings and create a row with the business's website URL and name. Open the listing to fill in the Profile tab: category, description, contact/address details, opening hours, and upload a logo/cover/gallery images.
Check a listing's on-site SEO health From the Listings grid, select one or more listings and run "Check metadata." Each listing's website is fetched and scored (title, meta description, Open Graph, structured data), with issues ranked by severity. Open the listing's Advisory tab afterward to see the prioritised fixes and a copy-paste JSON-LD snippet for the site's
<head>.Connect to Google Business Profile and sync reviews An admin first saves the platform's Google OAuth client (Remote Connections → gbp — client ID/secret/redirect); each tenant then clicks connect to run its own OAuth consent and link an account/email. Once connected, list the account's locations and link a listing to the matching GBP location, then use "Sync reviews" to pull live reviews from Google into the listing's Reviews tab, or "Push" to send the listing's profile fields to Google.
Handle a review Log a review manually (or sync it from Google), then use "Draft reply" for a starter reply text, edit it as needed, and save/post it — replies to Google-sourced reviews post straight back to Google; manually-logged reviews save the reply locally for you to paste onto the real listing yourself.
Data model
- GoogleWebsite — a website whose keyword rankings are tracked.
- GoogleKeyword — a keyword tracked for a website (language/country), with its latest known rank cached for at-a-glance display.
- GoogleSearchEngine — a SERP engine/provider pair (OpenSERP or DataForSEO) a keyword is ranked against, with its own scan depth (
results, default 100) and active/disabled status; a Google default is seeded per tenant automatically the first time rankings are checked or the Engines list is opened. - GoogleRankResult — one observed SERP position for a keyword/engine/date; one row per keyword/engine/day, the authoritative rank history.
- GoogleDataForSeoTask — tracks a pending/finished DataForSEO async SERP task so a re-run never double-charges a keyword already in flight.
- GoogleListing — a Google Business Profile / local-listing record for one website: the fields a user maintains plus the cached result of the last on-site metadata check (SEO score, detected title/description, issues).
- GoogleReview — a review against a listing, sourced as
manualorgoogle_apiand deduplicated bygoogle_review_id; a 1-5 star rating plus optional reviewer name/photo, comment, reply text and an AI-drafted reply suggestion. Drives the listing's cached rating/review count, recomputed whenever a review is added, edited or removed. - GoogleListingFile — one uploaded image (
logo/cover/gallery) for a listing, stored on disk under a per-listing directory and served through an authenticated streaming endpoint;GoogleListing.logo_url/cover_url/gallery_urlshold the served URLs of the current file row(s). - GoogleGbpConnection — one tenant's Google Business Profile OAuth connection: encrypted refresh token plus the connected account/email (one row per tenant).
Configuration & integrations
- OpenSERP — self-hosted SERP scraper reached via
settings.OPENSERP_URL; free, supports google/bing/yandex/baidu/duckduckgo/ecosia, but Google may CAPTCHA datacenter IPs (a proxy/2captcha should be configured on the OpenSERP side). - DataForSEO — paid SERP API, used only for Google results; disabled unless credentials are set. Credentials resolve through the Remote Connections overlay (provider slug
dataforseo) withDATAFORSEO_LOGIN/DATAFORSEO_PASSWORD/DATAFORSEO_LOCATIONin.envas a fallback. - Google Business Profile OAuth — the OAuth client (client ID/secret/redirect) is one platform-wide credential set via Remote Connections (provider slug
gbp); each tenant does its own OAuth consent (scopes:business.manage,userinfo.email,openid) and keeps its own Fernet-encrypted refresh token. Google gates the Business Profile API and legacy Reviews (v4) API behind per-project quota approval, so an unapproved project shows clear "not connected"/"no access" errors instead of failing silently. - Metadata checker — fetches and analyses a listing's own website (title/meta/Open Graph/structured data) with no external API or credential required.
API reference
Endpoints live under /google and require at least the admin role in the active tenant.
GET /google/websites— list tracked websites with keyword counts.POST /google/websites/PATCH /google/websites/{id}/DELETE /google/websites/{id}— create/update/soft-delete a website.GET /google/keywords?website_id=— list keywords, optionally filtered by website.POST /google/keywords— add a single keyword.POST /google/keywords/bulk— add many keywords to a website at once, skipping case-insensitive duplicates.DELETE /google/keywords/{id}— soft-delete a keyword.GET /google/engines— list the tenant's search engines plus OpenSERP health and whether DataForSEO is enabled.GET /google/ranks?website_id=— latest cached rank per active keyword, best-rank-first.GET /google/keywords/{id}/history— a keyword's full rank history.POST /google/ranks/run— check current rankings now for given keyword IDs on an engine (defaults to the tenant's default).GET /google/listings/{id}/advisory— prioritised SEO fixes, JSON-LD snippet, and GBP category guidance for a listing.PUT /google/listings/{id}/profile— save a listing's profile fields plus logo/cover/gallery uploads (multipart form).GET /google/listings/{id}/files/{file_id}/content— stream an uploaded listing image.GET /google/listings/{id}/reviews— list a listing's reviews.POST /google/listings/{id}/reviews— log a manual review.PATCH /google/reviews/{id}/DELETE /google/reviews/{id}— edit/soft-delete a review (recomputes the listing's rating).POST /google/reviews/{id}/draft-reply— generate a heuristic starter reply.POST /google/reviews/{id}/reply— save a reply, posting it live to Google if the review was synced from Google.POST /google/listings/{id}/reviews/sync— pull and upsert live reviews from Google.POST /google/listings/{id}/push— push the listing's profile data to the linked GBP location.GET /google/gbp/status— the tenant's GBP connection status.POST /google/gbp/credentials— save the platform's Google OAuth client id/secret/redirect.GET /google/gbp/auth-url— build the Google OAuth consent URL to start connecting.GET /google/gbp/callback— OAuth redirect target (unauthenticated, hit directly by Google).POST /google/gbp/disconnect— remove the tenant's GBP connection.GET /google/gbp/locations— list the connected account's GBP locations.POST /google/gbp/link— link a listing to a specific GBP location.
Plain CRUD on Listings themselves (create/edit/delete the row, list/search/sort) is handled by the generic config-driven grid engine rather than a bespoke endpoint — the grid also exposes "Check metadata" (bulk) and "Open" (row) actions on top of that CRUD.