Technology Stack

Ananas GDS is a Django-backed REST API served to a Vue 3 single-page application. The platform runs on Mochahost shared hosting with a MariaDB database in production and SQLite for local development.

Layer Technology Notes
Backend Django 5.2 + Django REST Framework Python 3.11, REST API
Frontend Vue 3 + Vuetify 3 + Pinia Single-page application
Database MariaDB (production) / SQLite (local dev) utf8mb4 charset required on all tables
Async tasks Celery CELERY_TASK_ALWAYS_EAGER = True in dev and test environments
Auth library Djoser (partially replaced) Custom session auth layer built on top
Image CDN ImageKit Avatar and property logo storage and delivery
Payments Stripe Subscription billing, webhooks, Stripe.js frontend
Hosting Mochahost (cPanel / LiteSpeed) Shared hosting, no SSH git access

Django Apps

The backend is organized into focused Django apps. Each app owns its models, serializers, views, and URL routes.

App Purpose
authapp Custom User model, DRF token authentication, session management, sub-user system, permission flags, IP ban logic
info Property management, fact sheets (12+ categories), property photos, surveys, fact publishing and distribution
stop_sale Stop sale calendar events, allotment periods, date-range management per property
partners Partner contract lifecycle, connection requests, contract-level permission scoping between accommodation and TO accounts
touroperator Tour operator dashboard logic, hotel and room mapping configuration, fact override management
reports Activity log, API statistics (APIStats model), pre-aggregated summary model (APIStatsSummary), Celery analytics tasks
payments Stripe subscription management, billing plan enforcement, FeatureAccess gating, Stripe webhook handler
invoicing B2B invoice creation, invoice lifecycle (draft, sent, paid, overdue), PDF generation
settings User and company settings store, get_setting() utility function for consistent settings lookup
dev Public API v1 endpoints (facts, photos, stop-sale), integrations, webhook dispatch, data export
external Reference data endpoints: countries, states, cities, currencies, property widget, application option lists
notification In-app notifications and platform-wide announcements
mcp_server MCP protocol integration for structured tool-based data access

Facts View Pattern

The info app uses factory functions to avoid repetitive view boilerplate across the 12+ fact sheet categories. Each fact category endpoint is generated by one of two factory functions:

PYTHON
# Single-value facts (one record per property)
_facts_view(field, model, serializer)

# Array facts (multiple records per property, e.g. room types)
_array_facts_view(field, model, serializer)

# Both handle GET + POST on a single endpoint
# GET returns current fact data for the property
# POST creates or updates the fact record

API URL Structure

All API routes are prefixed with /api/. The following table maps URL prefixes to their corresponding Django app.

TEXT
api/auth/           -> authapp        (login, logout, session, sub-users)
api/facts/          -> info           (properties, fact sheets, photos)
api/partners/       -> partners       (contracts, connection requests)
api/stop-sale/      -> stop_sale      (calendar events, allotments)
api/tour-operator/  -> touroperator   (mapping, override facts)
api/reports/        -> reports        (statistics, activity logs)
api/payments/       -> payments       (subscriptions, Stripe)
api/invoicing/      -> invoicing      (B2B invoices)
api/settings/       -> settings       (user and company settings)
api/v1/             -> dev            (public API, integrations, webhooks)
api/external/       -> external       (countries, currencies, widget, options)
api/                -> notification   (in-app notifications)
mcp/                -> mcp_server     (MCP protocol endpoints)

Public API (v1)

The api/v1/ namespace is the externally-facing API consumed by tour operator integration systems. It uses static API tokens passed in the URL path rather than session tokens. There is no authentication header required — the token itself is the credential:

TEXT
GET /api/v1/facts/{token}/
GET /api/v1/photos/{token}/
GET /api/v1/stop-sale/{token}/

Frontend Architecture

The frontend is a Vue 3 single-page application served separately from the Django backend. Vuetify 3 provides the UI component library; Pinia manages global state.

Routing and Role Guards

Vue Router handles all client-side navigation. Route guards in plugins/1.router/guards.js enforce:

  • Authentication gate — Unauthenticated users are redirected to /login
  • Role-based access — Accommodation routes are inaccessible to TO accounts and vice versa
  • Permission flag checks — Sub-users attempting to access a module they lack permission for are redirected

Pinia Stores

Application state is managed through a set of Pinia stores under @stores/. Key stores include:

Store Owns
auth Session token, login/logout actions, session heartbeat timer
account Current user profile, account type, permission flags
facts Fact sheet data per category, dirty state, save status
stopSale Calendar events, allotment records, selected date range
partners Contract list, pending requests, connection status
notifications In-app notification list, unread count

App Initialization

On every page load, @core/initApp.js runs before the router renders the first view. It:

  1. Reads the accessToken cookie — If absent, the user is considered logged out.
  2. Calls GET /api/auth/session/ — Validates the token server-side, returns the current user profile and permission flags.
  3. Populates the auth and account stores — Sets the user object, account type, and permission flags.
  4. Starts the heartbeat timer — Schedules the session keep-alive ping every 10 minutes.
  5. Resolves routing — Vue Router proceeds to render the requested route once init completes.

Request Interceptor

All outbound API requests from the frontend are intercepted by jwtInterceptor.js, which attaches the Authorization: Token <key> header automatically. There is no need to manually set authorization headers in any store or component — the interceptor handles it globally.

Authentication Model

Despite the filename jwtInterceptor.js, Ananas GDS does not use JWT tokens. The authentication mechanism is DRF's built-in TokenAuthentication — opaque string tokens stored in the database.

On top of DRF's token auth, a custom SessionTokenAuthentication class adds:

  • Inactivity-based session expiry (30 minutes standard, 7 days extended)
  • Concurrent session enforcement (max 2 per account)
  • Session metadata (IP address, device info, last active timestamp)

For the full authentication reference including login flow, per-request validation, and the IP ban system, see Authentication.

Data Flow

The core value of Ananas GDS is the pipeline from hotel data entry to tour operator API consumption. The following diagram shows how data moves through the system:

Hotel (accommodation user)
  |
  +-- Creates Property
  |
  +-- Fills Fact Sheets (12+ categories)
  |
  +-- Uploads Photos
  |
  +-- Sets Stop Sales / Allotments
  |
  +-- Issues API tokens to partners
  |
  v
Tour Operator (API consumer)
  |
  +-- GET /api/v1/facts/{token}/
  |
  +-- GET /api/v1/photos/{token}/
  |
  +-- GET /api/v1/stop-sale/{token}/
  |
  +-- Configures hotel/room mappings (internal ID mapping)
  |
  +-- Applies fact overrides (local edits for own catalogue)

Each API token is scoped to a specific partner contract. A hotel controls exactly which partners receive tokens, and tokens can be revoked at any time from the Dev Tools panel.

Analytics Pipeline

Every public API call is logged asynchronously via Celery tasks to the APIStats model. Stats are keyed on (user, token, endpoint, date) and track:

  • hits_ok and hits_error — separate success and failure counters
  • bytes_transferred — payload size for bandwidth tracking

A nightly Celery rollup task aggregates daily stats into the APIStatsSummary pre-aggregated model, which powers the Dev Tools statistics dashboard without hitting raw log tables on every page load.

Deployment Environment

The platform runs on Mochahost shared hosting. This is a constrained environment with specific limitations that affect development practices.

Component Detail
Web server LiteSpeed via cPanel
Application server WSGI (Python 3.11 virtualenv)
Database MariaDB with innodb_strict_mode=0
Cache File-based (no Redis, no Memcached)
Git Not available on server — deploy by manual file upload
SSH git push Not available — changes go up file-by-file or via zip upload
Async workers Celery tasks run eagerly in dev/test; production uses scheduled tasks

ASCII-Only Python Files

The cPanel/LiteSpeed WSGI process uses ASCII encoding. Any emoji, special character, or non-ASCII byte in any .py file will silently crash all requests to that file. This applies to comments as well as code. There are no non-ASCII characters anywhere in the Python codebase — keep it that way.

MariaDB Constraints

The production database has several constraints that must be respected during development and migration authoring:

  • utf8mb4 charset required on all tables — charset mismatches cause silent migration failures
  • innodb_strict_mode=0 is set as a workaround for Row size too large errors
  • FK constraint ordering in migrations matters — always verify dependency order before deploying migrations
  • Every app with models must have a migrations/__init__.py file or it is silently skipped by the migration runner

Pre-Deployment Checklist

  1. No non-ASCII characters in any .py file added or modified in this change
  2. Run makemigrations locally and verify migration files are clean before upload
  3. Check FK dependency ordering in any new migration files
  4. New app added? Ensure migrations/__init__.py exists
  5. Upload files via cPanel File Manager
  6. Run migrations on the server via cPanel Python app terminal
  7. New pip dependency? Set PKG_CONFIG_PATH before install; source ~/.bashrc first