Skip to content

Tenancy Domain

Manages platform-level configuration and the multi-tenant hierarchy for the PlayTelly platform: a single general record holds ticketing tenant settings — site branding, legal content pages (privacy policy, T&Cs, FAQ, contact us, refund policy), third-party API credentials (Zoo API for QR scanning, JohorPay for payments), and email server configuration — while Organizations and Workspaces provide the platform-wide tenant, membership, and app-access model used by CoreAPI itself. Only SYSADMIN can write ticketing general settings; most content endpoints are public-readable for the customer app.

Handles:

  • General settings — contact email, timezone, environment config
  • Site logo — public image retrieval
  • Legal content pages — privacy policy, T&C, FAQ, contact us, refund policy (multilingual: BM/EN/CN)
  • Integration settings — Zoo API and JohorPay credentials, email server config
  • Organizations — tenant creation, membership, roles, app access
  • Workspaces — per-org sub-units for membership, app access, spaces, and channels
  • Webhook configuration (planned)

Two tenancy models

This domain covers two independent config surfaces that happen to share a package: the legacy ticketing general record documented below (single-row, ticketing-specific), and the platform-wide organization/workspace hierarchy documented on their own pages. They don't reference each other.


Architecture

graph LR
    CustomerApp["Customer App (public reads)"]
    AdminPortal["Admin Portal (SYSADMIN writes)"]
    CoreAPI["CoreAPI (Tenancy handlers)"]
    DB["Ticketing DB\n(general — single row)"]

    CustomerApp -->|GET /api/settings/privacyPolicy\n/termsOfService\n/faq etc.| CoreAPI
    AdminPortal -->|GET /PUT /api/settings/general| CoreAPI
    AdminPortal -->|PUT /api/settings/integration| CoreAPI
    CoreAPI --> DB

The general table is a single-row config store. All endpoints read from or write to this one record.


Data Model

erDiagram
    General {
        uint generalId
        string contactEmail
        string timeZone
        string envKey
        string envValue
        string privacyPolicyContentEn
        string privacyPolicyContentBm
        string privacyPolicyContentCn
        string termsOfPurchaseContentEn
        string termsOfServiceContentEn
        string faqContentEn
        string contactUsContentEn
        string refundPolicyContentEn
        string zooApiBaseUrl
        string jpGatewayUrl
        string emailHost
        int emailPort
    }

Endpoints

Public (no auth required)

Returns the tenant's site logo image file.

Response 200 — image file with appropriate Content-Type.


GET /api/settings/privacyPolicy

Response 200

{
  "success": true,
  "data": {
    "contentEn": "<html>...",
    "contentBm": "<html>...",
    "contentCn": "<html>...",
    "lastUpdatedDate": "01-06-2026"
  }
}


GET /api/settings/termsOfPurchase

Same shape as privacy policy — terms of purchase multilingual content.


GET /api/settings/termsOfService

Terms of service multilingual content.


GET /api/settings/faq

FAQ multilingual content.


GET /api/settings/contactUs

Contact us multilingual content.


GET /api/settings/refundPolicy

Refund policy multilingual content.


Admin — requires SYSADMIN role

GET /api/settings/general

Get the full general settings record including all API credentials and email config.

Sensitive fields

This response includes jpApiKey, jpAgToken, emailPassword, emailClientSecret, and emailRefreshToken. Never expose this endpoint to non-admin clients.

Response 200 — full General object.


PUT /api/settings/general

Update general settings (non-integration fields: contact email, timezone, logo image upload).

Requestmultipart/form-data with optional logo file + JSON fields:

{
  "contactEmail": "support@example.com",
  "timeZone": "Asia/Kuala_Lumpur"
}


PUT /api/settings/privacyPolicy

Update privacy policy content in all three languages.

Request

{
  "contentEn": "<html>Updated privacy policy...</html>",
  "contentBm": "<html>Dasar privasi dikemaskini...</html>",
  "contentCn": "<html>更新的隐私政策...</html>",
  "lastUpdatedDate": "23-06-2026"
}

Same shape applies for PUT /termsOfPurchase, PUT /termsOfService, PUT /faq, PUT /contactUs, PUT /refundPolicy.


PUT /api/settings/integration

Update third-party API credentials and email server config.

Request

{
  "zooApiBaseUrl": "https://zoo-api.example.com",
  "zooQrEndpoint": "/qr/scan",
  "zooTokenEndpoint": "/auth/token",
  "zooTicketEndpoint": "/tickets/validate",
  "zooApiUsername": "zoo_user",
  "zooApiPassword": "zoo_pass",
  "jpGatewayUrl": "https://jp.gateway.com",
  "jpPaymentEndpoint": "/payment",
  "jpRedflowEndpoint": "/redflow",
  "jpBankListEndpoint": "/banks",
  "jpApiKey": "<key>",
  "jpAgToken": "<token>",
  "emailHost": "smtp.gmail.com",
  "emailPort": 587,
  "emailUsername": "noreply@example.com",
  "emailPassword": "<password>",
  "emailFrom": "noreply@example.com",
  "emailUseSsl": true,
  "emailClientId": "<oauth-client-id>",
  "emailClientSecret": "<oauth-client-secret>",
  "emailRefreshToken": "<oauth-refresh-token>"
}


Content Pages

All legal content pages follow the same pattern:

Endpoint GET PUT (SYSADMIN)
/api/settings/privacyPolicy Public read Update content
/api/settings/termsOfPurchase Public read Update content
/api/settings/termsOfService Public read Update content
/api/settings/faq Public read Update content
/api/settings/contactUs Public read Update content
/api/settings/refundPolicy Public read Update content

All content is stored as raw HTML strings and served verbatim. The customer app renders them in a WebView.