Skip to content

Data Persistence & Storage Architecture

The Planovi Microservices utilize a fast, deterministic file-based JSON persistence engine (data/*.json). This architecture delivers extreme read speeds, zero database connection pooling overhead for public viewers, and isolated tenant storage.


1. Storage Architecture Overview

Rather than connecting to an external relational database for every public inquiry, offer view, or appointment slot render, each configuration entity is serialized to an individual JSON record on the local VPS SSD storage.

graph TD
    Client["Client / Mobile App / Webhook"]
    
    subgraph DealflowStore["dealflow/data/ Storage"]
        DealConfig["deal_{timestamp}.json
• Lead data
• PV sizing & energy specs
• Pricing breakdown"] OfferConfig["offer_{uniqid}.json
• Formatted commercial offer
• Tenant ID & branding"] TokenOnboarding["{token_hex_32}.json
• Member onboarding declaration
• 72-hour TTL expiration"] end subgraph SchedulerStore["scheduler/data/ Storage"] SchedConfig["{cal_id}.json (e.g. default.json)
• Available hours & days
• Slot duration (e.g. 30 min)
• Google OAuth Refresh Token
• Supabase company/user ID"] end Client -->|"POST /api/create_offer.php"| OfferConfig Client -->|"POST /api/save_dealflow.php"| DealConfig Client -->|"POST /backend/save_scheduler.php"| SchedConfig Client -->|"GET /offer.php?id=..."| OfferConfig Client -->|"GET /offer.php?token=..."| TokenOnboarding Client -->|"GET /index.php?id=..."| SchedConfig

2. File Naming Conventions & Record Types

SubsystemFile PatternLifecycleContent & Purpose
Dealflow Dealsdealflow/data/deal_{timestamp}.jsonPermanentCommercial intake records, client contact information, estimated generation capacity.
Dealflow Offersdealflow/data/offer_{uniqid}_{hex}.jsonPermanentPublished interactive offer views presented to prospective cooperative members.
Onboarding Tokensdealflow/data/{32_hex_token}.jsonEphemeral (72h TTL)Accession questionnaire prefilled data for new cooperative members. Validated against expires_at.
Scheduler Calendarsscheduler/data/{uuid_or_id}.jsonPermanentOperating calendar preferences, host timezone, meeting buffer, custom brand colors, and OAuth credentials.

3. Atomic Writes & Data Integrity

To prevent partial or corrupted file writes caused by simultaneous write requests or server interruptions, records are written using transactional atomic write operations:

// Safe atomic write pattern in microservices
$data_dir = __DIR__ . '/../data';
if (!is_dir($data_dir)) {
mkdir($data_dir, 0755, true);
}
$save_path = $data_dir . '/' . $offer_id . '.json';
$encoded_json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if (file_put_contents($save_path, $encoded_json, LOCK_EX)) {
// Successfully written with exclusive lock
}
  • LOCK_EX: Acquires an exclusive lock on the file while writing to avoid race conditions.
  • JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES: Preserves human readability for debugging on the VPS while preventing URL mangling.

4. Wrapper Normalization (json_data vs json)

Because data can be authored either by the Flutter client, Supabase Edge Functions, or direct REST API posts, the microservices unwrap nested payloads automatically:

// Transparent unwrapping of Supabase / Flutter serialized objects
$config = $decoded;
if (isset($decoded['json_data'])) {
$config = is_string($decoded['json_data'])
? json_decode($decoded['json_data'], true)
: $decoded['json_data'];
} elseif (isset($decoded['json'])) {
$config = is_string($decoded['json'])
? json_decode($decoded['json'], true)
: $decoded['json'];
}

This flexibility ensures backwards-compatibility across multiple generations of client schemas without requiring database migrations.


5. Token Lifecycle & Expiration Engine

For member accession tokens (dealflow/data/{token}.json), each record contains an expires_at timestamp set upon invitation generation:

{
"token": "7feb7caf0a4a48ffb748dc7226f87464",
"created_at": "2026-09-18 10:00:00",
"expires_at": "2026-09-21 10:00:00",
"customer_name": "Jan Kowalski",
"customer_email": "jan.kowalski@example.pl",
"deal_id": "deal_1771006118990"
}

When accessed via offer.php?token=7feb7caf0a4a48ffb748dc7226f87464:

  1. The script checks file_exists($dataFile).
  2. Validates strtotime($tokenFormData['expires_at']) >= time().
  3. If expired, an alert (Ten link wygasł (ważny 72h). Poproś o nowe zaproszenie.) is displayed and form submission is disabled.