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
| Subsystem | File Pattern | Lifecycle | Content & Purpose |
|---|---|---|---|
| Dealflow Deals | dealflow/data/deal_{timestamp}.json | Permanent | Commercial intake records, client contact information, estimated generation capacity. |
| Dealflow Offers | dealflow/data/offer_{uniqid}_{hex}.json | Permanent | Published interactive offer views presented to prospective cooperative members. |
| Onboarding Tokens | dealflow/data/{32_hex_token}.json | Ephemeral (72h TTL) | Accession questionnaire prefilled data for new cooperative members. Validated against expires_at. |
| Scheduler Calendars | scheduler/data/{uuid_or_id}.json | Permanent | Operating 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:
- The script checks
file_exists($dataFile). - Validates
strtotime($tokenFormData['expires_at']) >= time(). - If expired, an alert (
Ten link wygasł (ważny 72h). Poproś o nowe zaproszenie.) is displayed and form submission is disabled.