Skip to content

rithish/mobile-store-reviews

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mobile Store Reviews

A self-hostable, single-binary tool that pulls your app's Google Play and App Store reviews into one place, tags them by type and theme (rules + optional LLM), detects trend spikes, and surfaces everything through a built-in dashboard, Google Chat alerts, and scheduled email digests.

One Go binary, an embedded SQLite database, and an embedded web UI. No external services required to run it.

Contents

Features

  • Ingestion — pulls reviews from Google Play and the Apple App Store. Works out of the box via public scraping/RSS; upgrades automatically to official store APIs when you provide credentials.
  • Tagging — every review gets a type (bug / feature / issue / other) and zero-or-more themes (payment, login, etc). A fast, editable rules engine runs first; an optional LLM classifier (Gemini) only runs on reviews the rules can't confidently resolve.
  • Human corrections — operators can override any tag from the dashboard. Human tags are permanent ground truth: --retag and automated tagging never overwrite them.
  • Trend detection — daily metrics are aggregated per type/theme, then compared against a rolling baseline using both a relative-rise threshold and a robust (median/MAD) z-score, so noisy baselines don't cause false alarms.
  • Alerts — active spikes above a configurable severity post to Google Chat, with de-duplication so you're not paged twice for the same trend.
  • Reports — a periodic HTML + CSV digest (top themes, biggest risers, low ratings) viewable in-app or emailed via SMTP.
  • Dashboard — an embedded web UI for overview stats, trend/spike drill down, a filterable review list with inline tag correction, and report history. No build step, no separate frontend deploy.
  • One artifact — everything above ships in a single Go binary with an embedded SQLite database (WAL mode) and embedded static assets.

How it works

        ┌─────────┐      ┌─────────┐      ┌──────────┐      ┌────────┐      ┌─────────┐
 stores │  sync   │ ───▶ │   tag   │ ───▶ │ analyze  │ ───▶ │ notify │      │ report  │
        └─────────┘      └─────────┘      └──────────┘      └────────┘      └─────────┘
             │                │                 │                │               │
             ▼                ▼                 ▼                ▼               ▼
                          ┌────────────────────────────────────────────────────────────┐
                          │                     SQLite (reviews.db)                    │
                          │ reviews · review_tags · metrics_daily · trends · reports    │
                          └────────────────────────────────────────────────────────────┘
                                                    ▲
                                                    │
                                          ┌────────────────────┐
                                          │  dashboard + API   │  (reads + human corrections)
                                          └────────────────────┘
  • sync fetches new reviews since the last known review per store and upserts them (safe to re-run; --backfill does a one-off historical pull).
  • tag runs the rules engine, falling back to the LLM only when rules don't resolve a concrete type. Tags are cached by review-text hash to avoid redundant LLM calls.
  • analyze rebuilds metrics_daily (using the effective tag per review — human tags win over LLM, which wins over rules) and re-evaluates every type/theme series for spikes, updating trends (active/resolved).
  • notify sends Google Chat alerts for active trends that haven't been alerted yet.
  • report renders an HTML/CSV digest and, if SMTP is configured, emails it.
  • --pipeline runs sync → tag → analyze → notify in one shot — a good fit for a periodic cron job.

Quick start

cp config.toml.sample config.toml
cp rules.toml.sample rules.toml

go run . --install --config config.toml     # create the database schema
go run . --backfill --config config.toml    # first-time historical import
go run . --tag --config config.toml         # tag everything backfilled
go run . --analyze --config config.toml     # build metrics + detect trends

go run . --config config.toml
# open http://127.0.0.1:9000

Nothing above requires an API key or credentials — the scraper-based ingestion and rules-only tagging work with zero config beyond the app identifiers in config.toml.

Configuration

Everything lives in one config.toml (see config.toml.sample for the full annotated reference). The main sections:

Section Purpose
[server] Bind address — must be 127.0.0.1 or localhost (enforced at startup)
[database] Path to the SQLite file
[app] Google Play package name / App Store slug for the app being tracked
[ingest] Backfill window, request pacing
[google_play], [app_store] Optional official API credentials (falls back to scraping/RSS if empty)
[tagging] Path to rules.toml, batch size
[llm] Optional Gemini API key/model for LLM-assisted tagging
[analytics] Spike detection thresholds (recent/baseline window, relative %, z-score, volume floor)
[notify] Google Chat webhook + minimum severity to alert on
[reports] Digest period, recipients, CSV attachment
[smtp] Outgoing mail server for report emails

rules.toml is a separate, hot-editable file (no restart needed before --retag) — see Tagging rules.

Tagging rules

rules.toml maps regex patterns over the review title+body to a type and/or theme:

[[rule]]
name = "payment_terms"
pattern = "(?i)payment|upi|card declined|transaction failed|refund failed|money debited"
theme = "payment"

[[rule]]
name = "crash_terms"
pattern = "(?i)crash|force close|app closes|anr|keeps stopping"
type = "bug"

Edit freely, then run --retag to re-classify existing reviews. Human corrections are never touched by this process.

CLI commands

Flag Description
--install Create the database schema and exit
--upgrade Apply pending migrations and exit
--sync Fetch new reviews since the last sync and exit
--backfill One-off historical import and exit
--tag Tag reviews that have no tags yet, and exit
--retag Re-tag all reviews with rules/LLM (keeps human corrections), and exit
--analyze Rebuild metrics_daily, detect trends, send pending alerts, and exit
--notify Send Google Chat alerts for pending active trends, and exit
--report Generate a digest report (and email it if SMTP is configured), and exit
--pipeline Run sync → tag → analyze → notify in sequence, and exit
(no flag) Start the HTTP server + dashboard

Run any of these on a schedule (cron, systemd timer, CI schedule) for periodic operation — there is currently no built-in scheduler (see Roadmap).

API

The dashboard is a thin client over a small JSON API:

Method Path Purpose
GET /api/health Liveness check
GET /api/overview Summary stats (volume, rating, active spikes)
GET /api/reviews Filterable review list (store, type, theme, limit, offset)
PUT /api/reviews/{id}/tags Set human-corrected type/themes for a review
GET /api/labels Valid type/theme label taxonomy
GET /api/metrics/daily Daily aggregates, optionally filtered by dimension
GET /api/trends Detected trends, optionally filtered by status
GET /api/reports Report history
GET /api/reports/{id} A single report (add ?format=html to view rendered)

Dashboard

The embedded UI (no build step, served via go:embed) has four pages:

  • Overview — headline stats and currently active spikes.
  • Trends — every detected trend with severity, % change, and z-score.
  • Reviews — the filterable review list with a Correct action to fix wrong or ambiguous tags inline.
  • Reports — history of generated digests, viewable in-browser.

Querying the database directly

The SQLite file is a first-class integration point — read it with any tool while the app keeps running (WAL mode keeps reads and writes from blocking each other):

sqlite3 reviews.db "SELECT COUNT(*) FROM reviews;"
sqlite3 reviews.db "SELECT dimension, label, COUNT(*) FROM review_tags GROUP BY 1, 2;"

Prefer file:reviews.db?mode=ro from external tools for read-only, WAL-safe access.

Development

go test ./...
go vet ./...

Before cutting a release, work through plans/BUILD-CHECKLIST.md in the repo root, which includes a manual SMTP email send test, Google Chat alert re-test, and a --pipeline smoke test.

Security notes

  • The HTTP server must bind to 127.0.0.1 or localhost — any other [server].address is rejected at startup. There is no authentication wired in yet (internal/auth defines the seam, but main.go does not enforce it), so loopback binding is the primary access control until auth or a trusted reverse proxy is added.
  • PUT /api/reviews/{id}/tags mutates tagging ground truth and should be treated as an admin-only action once auth exists.
  • Secrets (config.toml, App Store .p8 keys, rules.toml if it contains anything sensitive) are gitignored — only commit the .sample templates.

Roadmap

  • In-process scheduler so periodic runs don't need external cron.
  • React/Vite dashboard (current UI is server-rendered vanilla JS/HTML/CSS).
  • Authentication / SSO in front of the dashboard and API.
  • Prebuilt release binaries and an optional docker-compose.yml.

Contributing

Issues and pull requests are welcome. Before implementing a non-trivial change, please open an issue or discussion describing the problem first — this keeps the type/theme taxonomy and analytics thresholds consistent across contributions. Run go test ./... && go vet ./... before submitting.

License

Released under the MIT License. Copyright (c) 2026 redBus.

About

A self-hostable, single-binary tool that pulls your app's Google Play and App Store reviews into one place.

Resources

License

Stars

3 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors