Skip to content
UA.
All projects
LiveSaaSJan 2025 — Present

Revio

Customer feedback from a tap on the counter — no app, no sign-up.

The Revio landing page: 'Find out what your customer thinks before they leave the table'.
My role
Solo developer — architecture, API, background jobs, dashboard and deployment.
Status
Live

The problem

A café or a hairdresser finds out a visit went badly when the one-star review is already public. Asking at the counter rarely works either: the honest answer is the one people will not give to your face, and every alternative — find the listing, sign in, write something — loses almost everyone before the first tap.

What I built

A multi-tenant SaaS. Each business gets NFC cards for its counter and tables; the customer holds a phone to one, rates the visit in a few seconds and can leave a note, with nothing installed and no account created. Behind the tap is a .NET 8 API in a clean architecture — CQRS through MediatR, EF Core over MySQL, Redis on the hot path and Hangfire pulling Google Business Profile reviews in overnight — and a React dashboard where scores, notes and Google reviews meet, split by table, till and staff member.

What it looks like

  • Three steps: the customer taps the card, rates the visit, the owner watches the panel.
    Three steps, no setup — the whole product in one screen.
  • The Google Business Profile section explaining read-only access and revocable permission.
    Google reviews land in the same panel — read-only, and revocable from Google's own settings.

Architecture

  1. Physical layer

    Each card encodes a URL carrying its own business and placement, so a score arrives already knowing which table it came from.

    • NFC tags
    • QR fallback
  2. Scan endpoint

    The hot path. Card and business are read from cache, the scan is logged out of band, and the customer is redirected — this is the one request in the system that is allowed to be slow for nobody.

    • ASP.NET Core
    • Redis
  3. API

    Domain, Application, Infrastructure and Api as separate projects with dependencies pointing inward only; the domain project references no packages at all.

    • .NET 8
    • MediatR (CQRS)
    • FluentValidation
    • EF Core 8
  4. Background jobs

    Overnight review sync, queued as one job per business so a single failing account cannot block the rest, and idempotent on Google's own review id.

    • Hangfire
    • Google Business Profile API
  5. Dashboard

    Score distribution, busy hours and recurring complaints, with Google's reviews beside the business's own.

    • React
    • Vite
    • TanStack Query
    • Recharts

Decisions & trade-offs

  • The star filter is an ordering, not a gate

    Routing only happy customers to Google is called review gating, and Google's own policy forbids selectively soliciting positive reviews — a business caught doing it can lose its reviews outright. So the rating decides which screen comes first, not which doors exist: someone who rates one star still sees the Google option, they just see the "tell the business directly" form before it. The legitimate way to have fewer bad reviews is to fix the visit while the customer is still in the room.

  • One platform Google account, added as a manager

    The first design asked each business to paste its own authorisation into the panel. Google's access tokens expire after an hour, so the nightly sync broke by the next morning; working with refresh tokens instead needs a client id and secret per business, which is not something you can ask a hairdresser to generate. Now the owner adds the platform's support address as a manager on their own profile — no token, no password, revocable from Google's settings in one click.

  • Tenant isolation as a query filter, not a WHERE clause

    Every tenant-scoped entity carries a global query filter, and the check that a location belongs to the asking tenant runs on the server rather than in the panel. Filtering in application code is one forgotten clause away from showing a café another café's reviews; hiding the option in the UI only stops the people who use the UI.

  • Compute first, ask a model second

    Review insights are calculated deterministically wherever the arithmetic is enough, and only the parts that need language go to a model — Gemini first, Claude as a fallback when Gemini fails, under a daily cap, and the feature degrades quietly when no key is configured. A summary that costs money and can hallucinate should be the exception, not the pipeline.

Outcome

An unhappy customer reaches the owner before they reach Google — while they are still standing in the shop, and in time for someone to do something about it.

Measured

Automated tests
878 across 111 files
Packages referenced by the domain layer
0