03 — someli-platform architecture¶
A guided tour so you can place a new component, page, or store module in the right folder and trace a navigation from URL to API call.
Folder map¶
someli-platform/
├── nuxt.config.js ← THE single source of truth: head scripts, middleware order,
│ auth strategy, axios baseURL, plugins, modules
├── package.json
├── start.sh ← Docker / EFS wait-for-node_modules entry script
├── nginx.conf ← reverse-proxy in front of the SPA
├── Dockerfile
├── Jenkinsfile
├── polotno-bundle.js / .css ← BUILT artefact of polotno-editor/ — committed
│
├── pages/ ← file-system routing (Nuxt)
│ ├── index.vue
│ ├── login.vue
│ ├── _accid/ ← dynamic route segment; holds most authenticated routes
│ │ ├── contentplanner/
│ │ ├── dashboard/
│ │ ├── billing/
│ │ └── ...
│ └── ...
│
├── components/ ← Vue components (auto-registered)
│ ├── Calender/ ← (sic) calendar widgets
│ ├── Charts/ ← chart wrappers
│ ├── Dashboard/
│ ├── Buttons/, Cards/, Sidebars/
│ └── ...
│
├── layouts/
│ ├── default.vue, error.vue, onBoard.vue, someliNetwork.vue, specialOffer.vue, fourOhOne.vue
│
├── store/ ← Vuex 3 (non-strict)
│ ├── index.js ← root
│ ├── api.js ← cache-with-TTL layer in front of ~12 backend endpoints
│ ├── post/, dashboard/, chat/, common/, plan/, reach/, onboarding/, leaderboard/
│ └── modules/sitemap/
│
├── middleware/ ← Nuxt router middleware (runs on every navigation)
│ ├── redirect.js ← post-login routing
│ ├── axios.js ← axios interceptor (adds token / accountId / apptype headers)
│ ├── validUrl.js ← records last-known-good path
│ ├── appendUTM.js ← decorates links with UTM
│ └── appendUrl.js ← rewrites unprefixed URLs to /<accountId>/...
│
├── api/ ← Nuxt serverMiddleware (small Express helpers)
│ ├── index.js ← only live route is GET /api/youtube-videos
│ ├── twitter-middleware.js, test-middleware.js
│
├── plugins/ ← client-side singletons
│ ├── gtm.js, hotjar.js, fullstory.js, clarity.js, salesforceiq.js, vue-snotify.js, …
│
├── helpers/
│ ├── helper.js ← post-status state machine (renderPostStatus, calStatus)
│ │ + billing-plan filter (getPlanList) ← VERY important
│ ├── circleNetworkHelper.js ← Someli Network helpers
│ ├── mixins/ ← Vue mixins
│ └── models/ ← client-side model classes
│
├── constants/
│ ├── constants.js ← PROVIDER, PLAN, PATH, INVALID_RESPONSE, etc.
│ └── authProviders.js
│
├── static/ ← served verbatim — /owl.carousel.min.js, /common.js, /identity.js, /universal.tag.js
├── assets/ ← processed by build — CSS, SCSS, images
│
└── polotno-editor/ ← independent React 18 + Polotno + MobX-State-Tree project
├── package.json ← separate deps
├── yarn.lock
├── node_modules/ ← separate ~600 MB
├── index.html / index.js / App.js
└── (the editor UI: panels, sections, helpers, …)
Entry point — nuxt.config.js¶
Read this file end to end before doing anything else. It defines:
ssr: false(SPA mode)head.script[]— the CDN script tags (jQuery, Bootstrap, Stripe, Paddle, Clarity, …)head.link[]— fonts and external CSScss[]— global app CSSplugins[]— client-only pluginsmodules[]—@nuxtjs/axios,@nuxtjs/auth,bootstrap-vue/nuxt,cookie-universal-nuxtbuildModules[]—@nuxtjs/dotenv, etc.axios.baseURL = process.env.API_URLauth.strategies.local— login endpointwebauthenticate, user-fetch endpointme, logout disabledrouter.middleware = ['redirect', 'axios', 'validUrl', 'appendUTM', 'appendUrl']— the global middleware chainrouter.extendRoutes(...)— any non-file-system routesserverMiddleware[]— the small Express endpoints inapi/build.transpile[]— packages that need Babel transpile (swris in the list)
Junior gotcha: if you add a new global dependency (a CDN script, a plugin, a Vue extension), the change goes into
nuxt.config.js, not into a page or component. That config is the central registry.
Request lifecycle (authenticated page navigation)¶
A logged-in user navigates to /<accountId>/contentplanner:
Step by step:
1. Browser sends GET /<accountId>/contentplanner
2. Nuxt router resolves to pages/_accid/contentplanner/<something>.vue
3. Router middleware chain runs (in order from nuxt.config.js → router.middleware):
a. middleware/redirect.js — decides where the user belongs (could 302 elsewhere)
b. middleware/axios.js — registers an axios interceptor for this navigation
c. middleware/validUrl.js — stores this URL as "last good"
d. middleware/appendUTM.js — decorates outbound links
e. middleware/appendUrl.js — rewrites unprefixed URLs (no-op here since URL is prefixed)
4. Page component mounts.
5. Component-level data fetch:
store.dispatch('api/fetchSubscription')
store.dispatch('api/fetchAccountDetails')
this.$axios.get('/auth/getMyListPosts')
6. axios interceptor (from middleware/axios.js):
- Adds `token` header (from cookie) for URLs starting with /auth/
- Adds `accountId` header (from cookie.usedetail) for URLs starting with /auth/
- Adds base64(`apptype`) header on EVERY request
7. Request leaves the SPA, hits process.env.API_URL — typically the dev backend.
The axios interceptor only adds auth headers for
/auth/...URLs. If you call an endpoint outside that prefix, your request is unauthenticated. This is non-obvious — read../../audit/someli-platform/03-auth-and-sessions.mdbefore doubting it.
Where state actually lives¶
| Concern | Storage | Notes |
|---|---|---|
| Auth token | Cookie | Set by @nuxtjs/auth's local strategy |
accountId (current account) |
Cookie usedetail |
Read by the axios interceptor |
| API response cache | Vuex store/api.js |
5-min TTL, ~12 endpoints |
| User profile | Vuex store/index.js |
Mirrors API but cookie is source-of-truth for identity |
| Post lifecycle state | Vuex store/post/ |
Status transitions |
| UI ephemeral state | Component-local data() |
The default |
The store/api.js cache layer (very important)¶
store/api.js wraps ~12 commonly-hit backend endpoints in cache-with-TTL Vuex actions:
// Conceptual shape — see actual file for full list
{
fetchSubscription: { ttl: 5*60*1000, url: '/auth/getSubscription' },
fetchUserDetails: { ttl: 5*60*1000, url: '/auth/getUserDetails' },
fetchSettings: { ttl: 5*60*1000, url: '/auth/getSettings' },
fetchPosts(page): { ttl: 5*60*1000, url: '/auth/getMyListPosts?page=' + page },
...
}
Components dispatch store.dispatch('api/fetchSubscription') and the action returns the cached value if still valid, otherwise re-fetches.
Junior gotcha: if you bypass the cache by calling
this.$axios.get('/auth/getSubscription')directly, you'll get a fresh response — but you'll also trigger redundant backend load and cause UI flicker on subsequent uses of the cached data elsewhere. Use the store action.
See ../../audit/someli-platform/05-state-management.md.
Routing — file-system, account-scoped¶
| Path on disk | URL |
|---|---|
pages/index.vue |
/ |
pages/login.vue |
/login |
pages/_accid/contentplanner.vue |
/<accountId>/contentplanner |
pages/_accid/dashboard/index.vue |
/<accountId>/dashboard |
pages/_accid/dashboard/overview.vue |
/<accountId>/dashboard/overview |
pages/_accid/... is the dynamic account-id segment that wraps most authenticated routes.
Unprefixed bare URLs (/contentplanner, /dashboard/*, /billing, /userSetting, …) are intentionally allowed and rewritten by middleware/appendUrl.js to insert the accountId.
See ../../audit/someli-platform/04-routing.md.
The Polotno editor mount¶
Editor pages in the Nuxt app mount the Polotno bundle:
<template>
<div id="polotno-mount"></div>
</template>
<script>
export default {
middleware: 'auth', // page-level middleware opt-in
async mounted() {
const { createEditor } = await import('../../../../polotno-bundle');
createEditor(document.getElementById('polotno-mount'), { /* options */ });
}
};
</script>
The editor is a black box from the Nuxt app's perspective. Communication is by window.postMessage or shared MobX store (depending on the panel).
See ../../audit/someli-platform/06-polotno-integration.md.
Where to put a new file¶
| Adding | Put it in |
|---|---|
| A new page route | pages/_accid/<feature>/<name>.vue (or pages/<name>.vue for unauthenticated routes) |
| A new Vue component | components/<Group>/<Name>.vue — picks group by topic |
| A new global mixin | helpers/mixins/<name>.js |
| A new Vuex module | store/<feature>/ with state.js, getters.js, mutations.js, actions.js |
| A new cached API endpoint | Extend store/api.js — match the existing pattern |
| A new global constant | constants/constants.js |
| A new CDN script | nuxt.config.js → head.script — but consider whether package.json is more correct |
| A new plugin (singleton) | plugins/<name>.js, then register in nuxt.config.js → plugins (with ssr: false because we don't SSR) |
| A new middleware hook | middleware/<name>.js, then register in nuxt.config.js → router.middleware |
| Editor changes | polotno-editor/<panel-or-section>.js — and rebuild the bundle (yarn build inside polotno-editor/) |
Where to start when you have a feature to build¶
Open files in this order:
- The page file in
pages/_accid/<feature>/... - The Vuex module(s) the page dispatches against
store/api.js— see if the backend call is already wrapped theremiddleware/redirect.jsif the feature has any post-login or auth-state implication- The relevant component subdirectory in
components/
When in doubt, grep for an analogous existing feature and copy its structure rather than inventing a new one.
Next¶
→ 04-getting-started.md — ship your first feature.