Skip to content
Ali Hassan — home

Work

AI fitness app admin panel

The daily operations console for a calorie-tracking app with 20K+ users: users, subscriptions, food and ingredient databases, exercise data, app configuration and usage analytics.

Role
Full-stack engineer
Status
In production
Client
AI calorie-tracking app
  • Next.js
  • React
  • TypeScript
  • Firebase (Firestore)
  • Chakra UI
Food catalogue with macro-mismatch checks, ingredient nutrition table with quality scores, and the exercise library of a calorie-tracking console.

The problem

Context
The operations console sits directly on the live database of a calorie-tracking app with 20K+ users. It is the only window the team has into users, subscriptions, the food, ingredient and exercise catalogues, app settings, revenue and running costs, and it reads the app's own production data rather than a separate reporting copy.
Constraints
The database bills per document read, and the biggest collections (food logs and subscription events) are large. The same records had been written over time by the mobile app, a billing webhook and backfills, in different shapes. Some capabilities, like deploying indexes or enabling the billing export, depend on switches only the app owner controls.
What was at stake
A careless view can spend tens of thousands of reads in one click. A wrong assumption about data shape makes a number quietly lie, such as active trials showing as zero. And because server actions can be called directly, a missing role check is a real privilege-escalation path.

What I built

Cost control

  • Read cost shown before spending it

    The food-trends view estimates its cost with cheap counts first and labels its button with the number of reads before anything runs. Results are kept for a day, a forced rebuild is limited to once every five minutes, and the cheaper indexed path is used automatically once the owner deploys that index.

  • Expensive counts cached

    Counting every user took about two seconds and re-ran on each page change, so counts are cached for 60 seconds per query, and audience-tier counts for 12 hours.

  • Ranges that cannot run away

    Date ranges sent from the browser are snapped to the options the interface offers, capped at a year, and cached per calendar month, with bounded caches, so nobody can trigger unbounded billable reads by changing a parameter.

Numbers you can trust

  • Unknown is shown as unknown, never zero

    Usage counters were added to the app later, so older accounts do not have them. A missing counter, or a read that failed, is shown as unknown rather than zero, so the console never claims someone never used a feature.

  • Meals counted from the right source

    Free users who logged meals by search appeared to have logged nothing, because the summary counters only moved on other paths. Meal counts now come from the per-day logs themselves.

  • Subscription dates in two formats

    The mobile app and the billing webhook stored dates in two different formats, and a database range query silently skipped one of them, which is how active trials once read as zero. Subscriptions are now read and judged in memory.

  • One source for revenue

    Revenue is calculated from the app's stored billing events, with one shared list feeding both totals and charts so they cannot disagree. A failed read shows no data instead of a misleading $0.00.

Access control

  • A role check in every action

    Every server action checks the caller's role itself, using identity rebuilt from a verified token on each request. Middleware alone would let an editor call an admin-only action from any page.

  • Hardened sign-in

    Admins sign in with bcrypt-hashed passwords and receive a token whose algorithm is pinned on both signing and checking. Every failed sign-in returns the same message, so admin addresses cannot be discovered.

Operations

  • Every bill on one page

    A costs page brings together model, cloud, advertising and analytics spend next to revenue. Each source that cannot answer says why (for example, an export not switched on) instead of reporting zero, and all integrations are read-only by design.

  • Account deletion that spares shared data

    Deleting an account removes the user's data and their own files, but never the shared food and exercise images every user relies on.

  • Indexes kept as a superset

    The index file holds more than 170 composite indexes, kept as a superset of production, because the deploy tool offers to delete any index missing from the file.

Tell me what you’re building and where it’s stuck.

I’ll tell you the cleanest path forward, including if it’s “don’t build that.”

Or write tocontact@alihassan.dev

Ali Hassan in a dark winter jacket, looking off to one side, standing in a stone courtyard with a minaret and cloudy sky behind him.