Entities
How do they relate?
A workspace owns apps. Each app captures snapshots of its listing and tracks keywords through a join table, because the same keyword row is shared by every app that tracks it. Popularity and difficulty attach to the keyword, while a position attaches to the pair of an app and a keyword on a given date. Action items belong to the workspace, and each one records its lifecycle as events: one row per transition, such as opened, done or confirmed fixed, deleted together with the action.Rules that are easy to get wrong
- An app is one row. Countries live on keyword tracking, not on a second app row. See Countries and markets.
Keywordis unique ontext,storeandcountry. The same phrase tracked in two markets is two keyword rows checked by two searches.- A competitor is an app row too. It carries
isCompetitorand points at the primary app it was added to. - Popularity and difficulty persist in
KeywordMetric. Opportunity does not persist. It is computed in the read layer from the stored popularity and difficulty. See Popularity, difficulty and opportunity. installsis aBigIntand serializes as a JSON number.- All dates are UTC. Daily granularity uses a PostgreSQL
datecolumn, and today means the UTC date. - Every tenant owned row carries
workspaceId, and PostgreSQL enforces it. Each of those tables has atenant_isolationrow level security policy, so a query with no workspace in scope returns nothing rather than everything. Service code does not filter by workspace in awhereclause.Keyword,KeywordMetricandSerpEntryare deliberately global and carry no policy, because one search for a phrase serves every workspace tracking it.ScreenshotTextis global for the same reason: a screenshot is public store data, so one read serves every workspace that tracks the app. What is tenant owned is the tracking relation and the recorded position.