Skip to content

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 CSS
  • css[] — global app CSS
  • plugins[] — client-only plugins
  • modules[]@nuxtjs/axios, @nuxtjs/auth, bootstrap-vue/nuxt, cookie-universal-nuxt
  • buildModules[]@nuxtjs/dotenv, etc.
  • axios.baseURL = process.env.API_URL
  • auth.strategies.local — login endpoint webauthenticate, user-fetch endpoint me, logout disabled
  • router.middleware = ['redirect', 'axios', 'validUrl', 'appendUTM', 'appendUrl'] — the global middleware chain
  • router.extendRoutes(...) — any non-file-system routes
  • serverMiddleware[] — the small Express endpoints in api/
  • build.transpile[] — packages that need Babel transpile (swr is 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:

Browser  →  Nuxt router  →  middleware chain  →  page component  →  Vuex action  →  axios  →  backend

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.md before 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:

  1. The page file in pages/_accid/<feature>/...
  2. The Vuex module(s) the page dispatches against
  3. store/api.js — see if the backend call is already wrapped there
  4. middleware/redirect.js if the feature has any post-login or auth-state implication
  5. 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.