Ecommerce Template
Next.js·React 등으로 만든 최신 전자상거래 템플릿
amirelhalabi/Liratek
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A comprehensive, enterprise-grade Point of Sale (POS) and inventory management system designed specifically for mobile phone and electronics retail shops.
LiraTek ships as two products from one codebase:
• an offline-first Electron desktop app for a single shop, unlocked by a license key;
• a multi-tenant web app where each shop gets its own subdomain and a subscription managed from a super-admin control plane.
Both run the same business logic — the only difference is the transport (IPC vs REST) and where entitlement comes from. See Two Products, One Codebase.README 전문 보기 ↗A comprehensive, enterprise-grade Point of Sale (POS) and inventory management system designed specifically for mobile phone and electronics retail shops.
LiraTek ships as two products from one codebase:
Both run the same business logic — the only difference is the transport (IPC vs REST) and where entitlement comes from. See Two Products, One Codebase.
tenant_id, scoped through AsyncLocalStorage and enforced by a fail-closed context plus a CI linter (yarn check:tenant-scoping).<slug>.<domain>; the tenant is resolved from the request Host.active → grace → read_only lifecycle that never locks a shop out of its own history.@liratek/core — every repository and service, used by both transportstenant_id-scoped rows)| Desktop (Electron) | Web (multi-tenant) | |
|---|---|---|
| Who it's for | One shop, on its own machine | Many shops on one deployment |
| Transport | IPC (window.api.*) | REST (/api/*) |
| Database | Local SQLite file, offline-first | One SQLite file, rows scoped by tenant_id |
| Tenant | Fixed at boot (initFixedTenantContext) | Resolved from the request Host |
| Entitlement | License key | Subscription row |
| Works offline? | Yes — this is the point | No |
The rule that keeps them honest: both transports call the same @liratek/core service, which calls the same repository. No business logic lives in an IPC handler or a REST route. A feature is not finished until it works in both — see rule 19 in CLAUDE.md.
Why the desktop app stays on local SQLite: shops in Lebanon lose power and internet routinely. A till that stops working when the connection does is not a till. That constraint is also why the shared core is synchronous, and why a hosted Postgres was rejected — see docs/DEPLOYMENT.md.
Every tenant-owned table carries a tenant_id. Scoping is applied automatically through AsyncLocalStorage (runWithTenant / runWithoutTenant), getCurrentTenantId() is fail-closed — a query with no tenant context throws rather than returning everyone's rows — and yarn check:tenant-scoping fails CI on an unscoped query.
Set APP_BASE_DOMAIN to switch this on. Left unset, the app behaves exactly as a single-tenant deployment.
| Host | Realm | Who may sign in |
|---|---|---|
<slug>.<domain> | that tenant | that tenant's users only |
<domain>, www., admin. | platform | super admins only |
| an unrecognised subdomain | none | nobody |
| any host off the base domain | disabled | behaves as single-tenant |
A shop's credentials work only on that shop's own hostname, and the platform host signs in staff. Every refusal returns the same generic error, so subdomains cannot be probed — which is why the platform login page carries a notice telling shop staff where they should be instead.
POST /api/auth/signup is public but requires a single-use invite token from an emailed link (a super admin sends invites; visitors can request one themselves only when email and Turnstile are both configured). There is no shared code. It feeds the same TenantProvisioningService.provisionTenant() a super admin uses, so a self-served shop is indistinguishable from a hand-made one: registry row, full per-tenant config seed, and first admin user, in one transaction. It issues no token — the new shop signs in on its own subdomain.
Provisioning a tenant creates its DNS record and registers the hostname with the CDN, so onboarding needs no manual DNS step. It is off unless every credential is configured, idempotent (an existing record is success), and it never throws — a DNS hiccup must not fail a sign-up that already committed.
One table, tenant_subscriptions (migration v173), backs both products.
active → grace (7 days) → read_only
A lapsed shop is never locked out. It keeps full read access to its own history and reporting; only writes are refused (HTTP 402 / SUBSCRIPTION_READ_ONLY). Losing access to your own sales records because an invoice is late is not an acceptable failure mode, so it is not one the system can produce.
Each tenant carries entitled_modules — an explicit list of module keys (NULL means everything). There are no fixed bundles, because real shops run visibly different module sets and fixed tiers force a new tier the first time someone asks for "the basics plus Recharge". Named plans can later be presets that fill this column in.
Two details that matter:
tenant_subscriptions, not in the modules table — that table is writable by the tenant's own admin, who could otherwise grant themselves the upgrade. What a shop actually gets is the intersection: entitled AND enabled.dashboard, settings, audit and closing are never gated. A shop must always be able to see its own state and close its day.The desktop app holds a license key and is the one place it calls out: GET /api/subscription/by-key returns that key's current subscription state, which is cached in the local database and refreshed periodically. Every failure path fails open — no network, a bad response, a corrupt cache: the till keeps working. A licensing server outage must never stop a shop from taking money.
corepack enable)xcode-select --install# Clone the repository
git clone https://github.com/amirelhalabi/Liratek.git
cd Liratek
# Enable Corepack (for Yarn 4)
corepack enable
# Install dependencies
yarn install
npm run dev
This starts:
npm run dev:web
This starts:
npm run dev:docker
adminadmin123| Command | Description |
|---|---|
npm run dev | Desktop app (Electron + Vite) |
npm run dev:web | Browser mode (Backend + Frontend) |
npm run dev:frontend | Frontend only (port 5173) |
npm run dev:backend | Backend API only (port 3000) |
npm run build | Build everything (core + frontend + electron) |
npm run build:core | Build shared @liratek/core package |
npm test | Run all tests |
npm run lint | Lint all code |
npm run typecheck | TypeScript type checking |
Use Desktop Mode (Electron) when:
Use Browser Mode when:
Developing REST API
Testing without Electron
Cloud deployment testing
Need backend-only features
Backend: http://localhost:3000
Frontend: http://localhost:5173
docker compose up --build
LiraTek is a monorepo with multiple runnable projects. Each project has its own environment configuration.
Automatic (Recommended):
# Setup all projects at once
cd backend && npm run env:setup
cd ../frontend && npm run env:setup
cd ../electron-app && npm run env:setup
Manual:
# Backend
cp backend/.env.dev backend/.env
# Frontend
cp frontend/.env.dev frontend/.env
# Electron App
cp electron-app/.env.dev electron-app/.env
Each project has npm scripts for managing environments:
# Switch to development mode
npm run env:dev
# Switch to production mode
npm run env:prod
# Setup .env if it doesn't exist (safe to run multiple times)
npm run env:setup
Each project has three environment files:
.env.example - Full documentation of all available variables.env.dev - Safe development defaults (✅ committed to git).env.prod - Production template with placeholders (✅ committed to git).env - Your actual configuration (❌ gitignored, never commit)Edit the .env files with your values
Start development - Variables are auto-loaded
Create backend/.env:
# Environment
NODE_ENV=development # development | production | test
# Server
PORT=3000 # API server port
HOST=0.0.0.0 # Bind address (0.0.0.0 for all interfaces)
CORS_ORIGIN=http://localhost:5173 # Frontend URL for CORS
# Database
DATABASE_PATH=/path/to/liratek.db # Absolute path to SQLite database
DATABASE_KEY=your-32-char-secret # Optional: SQLCipher encryption key
# Authentication
JWT_SECRET=your-secret-key-min-32-chars # REQUIRED in production
JWT_EXPIRES_IN=7d # Token expiration (7d, 24h, etc.)
# Logging
LOG_LEVEL=info # trace | debug | info | warn | error | fatal
LOG_DIR=/var/log/liratek # Optional: Log file directory (production)
# ── Multi-tenancy (web deployment only) ──────────────────────────────────────
# Leave EVERYTHING below unset for a single-tenant/desktop-style deployment.
# Each block is off until it is fully configured — a half-configured feature
# stays off rather than half-working.
APP_BASE_DOMAIN=liratek.shop # Turns on host-based tenancy: <slug>.<domain>
# is a tenant, the apex/www/admin is the platform
# realm (super admins only). Unset = single-tenant.
SUPER_ADMIN_USERNAME= # Bootstraps the platform account on first boot,
SUPER_ADMIN_PASSWORD= # and ONLY if no active super admin exists yet.
# It never updates an existing one's password.
TENANT_HOST_HEADER_OVERRIDE= # Dev/test only: lets an X-Tenant-Slug header
# stand in for the Host. NEVER enable in prod —
# it is client-controlled.
# Automatic subdomain DNS on tenant provisioning. Off unless all of these are
# set; safe to leave blank, in which case add each tenant's hostname manually.
CLOUDFLARE_API_TOKEN= # Needs DNS:Edit on the zone
CLOUDFLARE_ZONE_ID=
VERCEL_TOKEN=
VERCEL_PROJECT_ID=
VERCEL_DNS_TARGET= # Defaults to cname.vercel-dns.com; override only
# if your project was given a different target
VERCEL_TEAM_ID= # Optional: only for team-scoped projects
Behind a proxy? The backend trusts one hop of
X-Forwarded-*so thatreq.hostnamereflects the original Host. Tenant resolution depends on it: iftrust proxyis removed or a proxy stops sendingX-Forwarded-Host, every tenant login fails at once with a generic "invalid credentials" — a symptom that points nowhere near the cause. Seedocs/DEPLOYMENT.md.
Create electron-app/.env:
# Development
ELECTRON_RENDERER_URL=http://localhost:5173 # Vite dev server URL
# Database (optional - usually auto-detected)
DATABASE_PATH=/path/to/liratek.db
DATABASE_KEY=your-32-char-secret
# Logging
LOG_LEVEL=debug
| Variable | Required | Default | Description |
|---|---|---|---|
| NODE_ENV | No | development | Application environment |
| PORT | No | 3000 | Backend API port |
| HOST | No | 0.0.0.0 | Backend bind address |
| CORS_ORIGIN | No | http://localhost:5173 | Allowed CORS origin |
| DATABASE_PATH | No | Auto-detected | SQLite database file path |
| DATABASE_KEY | No | None | SQLCipher encryption key |
| JWT_SECRET | Production | Random | JWT signing secret (32+ chars) |
| JWT_EXPIRES_IN | No | 7d | JWT token expiration |
| LOG_LEVEL | No | Environment-based | Logging verbosity |
| LOG_DIR | No | None | Log file directory |
| ELECTRON_RENDERER_URL | Dev only | http://localhost:5173 | Vite dev server URL |
LiraTek automatically resolves the database path in this order:
DATABASE_PATH environment variable (highest priority)db-path.txt file in project root~/Library/Application Support/liratek/phone_shop.db%APPDATA%/liratek/phone_shop.db~/.local/share/liratek/phone_shop.db# backend/.env
NODE_ENV=development
LOG_LEVEL=debug
DATABASE_PATH=~/Documents/LiraTek/liratek.db
JWT_SECRET=dev-secret-key-change-in-production-min-32-chars
# backend/.env
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
CORS_ORIGIN=https://yourdomain.com
DATABASE_PATH=/var/lib/liratek/liratek.db
DATABASE_KEY=<strong-32-char-encryption-key>
JWT_SECRET=<strong-random-secret-min-32-chars>
JWT_EXPIRES_IN=7d
LOG_LEVEL=info
LOG_DIR=/var/log/liratek
Environment variables are validated at startup using Zod schemas:
// Automatic validation on import
import env from "@liratek/core";
console.log(env.PORT); // Type-safe, validated
console.log(env.JWT_SECRET); // TypeScript knows the shape
Invalid configuration will fail fast with clear error messages:
❌ Environment variable validation failed:
{
JWT_SECRET: {
_errors: ['String must contain at least 32 character(s)']
}
}
✅ DO:
DATABASE_KEY for production (SQLCipher encryption)NODE_ENV=production in production.env files (gitignored)❌ DON'T:
.env files to Git (use .env.example instead)Problem: "Invalid environment configuration"
Solution: Check your .env file against .env.example and ensure all required fields are valid.
Problem: "Database not found"
Solution: Either:
DATABASE_PATH in .envdb-path.txt with absolute pathProblem: "JWT validation failed"
Solution: Ensure JWT_SECRET is at least 32 characters in production.
See also:
LiraTek is built as a dual-mode application that can run as both a desktop app (Electron) and a web app (Browser), sharing the same business logic through the @liratek/core package.
liratek/
├── packages/core/ # 🔥 Shared business logic (@liratek/core)
│ ├── repositories/ # Database access layer
│ ├── services/ # Business logic
│ └── utils/ # Crypto, logging, errors, barcode
├── electron-app/ # Desktop Electron wrapper
│ ├── main.ts # Main process (Node.js)
│ ├── preload.ts # IPC bridge (contextBridge)
│ └── handlers/ # IPC handlers (uses @liratek/core)
├── frontend/ # React UI (works in both modes)
│ └── src/
│ ├── features/ # Feature modules (POS, Inventory, etc.)
│ └── api/ # API abstraction (window.api vs REST)
├── backend/ # REST API server (for browser mode)
│ └── src/
│ └── api/ # Express routes (uses @liratek/core)
└── docs/ # Documentation
Frontend (React)
→ window.api (preload.ts contextBridge)
→ ipcMain handlers (electron-app/handlers/)
→ Services (@liratek/core/services)
→ Repositories (@liratek/core/repositories)
→ SQLite (better-sqlite3)
Frontend (React)
→ HTTP REST API (backend/src/api/)
→ Services (@liratek/core/services)
→ Repositories (@liratek/core/repositories)
→ SQLite (better-sqlite3)
@liratek/core Package: All business logic (services, repositories, utilities) is shared between Electron and Web backends, eliminating ~9,336 lines of duplicate code.
Frontend Abstraction: The React frontend detects its environment and uses either window.api (Electron) or fetch (Browser) transparently.
Single Database: Both modes can point to the same SQLite database using environment variables or config files.
Session Storage: Encrypted JSON files for session tokens (scrypt-derived AES-256-GCM)
electron-app/ (registers IPC handlers that call @liratek/core).frontend/.electron-app/preload.ts using contextBridge.Note: Desktop mode does not require the REST backend to be running. In desktop mode, API calls must route to
window.api(IPC), not HTTP.
liratek/
├── packages/core/ # Shared business logic (services/repositories/utils)
├── electron-app/ # Electron main process + preload + IPC handlers
├── backend/ # Express REST API + WebSocket server (web mode)
├── frontend/ # React UI (runs in Electron renderer and in browser)
│ └── src/api/ # `backendApi.ts` dual-mode facade (IPC vs HTTP)
└── docs/ # Documentation
Important: in Desktop (Electron) mode the renderer must route API calls via window.api (IPC). In Web mode the renderer routes via HTTP to http://localhost:3000/api.
LiraTek uses SQLite for low-latency, localized data storage.
products: Inventory with barcode and IMEI trackingsales / sale_items: Transaction records and snapshotsclients / debt_ledger: Customer profiling and dual-currency debt tracking with smart roundingpayments: Cash movement tracking linked to sales, debts, expenses, and servicesdrawer_balances: Running totals for all payment method drawers (General, OMT, Whish, Binance, IPEC, Katsh, Wish App, MTC, Alfa)financial_services: OMT, Whish, IPEC, Katsh, and Wish App transaction logsdaily_closings: Multi-drawer end-of-day audits and variance trackingactivity_logs: Comprehensive JSON-based audit trailrecharges: MTC/Alfa recharge tracking with virtual stockmaintenance_jobs: Device repair tracking with status workflowsupplier_ledger: Dual-currency supplier debt management~/Library/Application Support/liratek/liratek.db (macOS)DATABASE_PATH environment variable or ./liratek.db/data/liratek.db (persisted via volume)Repositories (electron/database/repositories/):
Services (electron/services/):
Handlers (electron/handlers/):
Unit Tests (Jest):
npm test # Run all tests
npm run test:coverage # With coverage report
npm run test:watch # Watch mode
E2E Tests (Playwright):
cd frontend
npm run test:e2e # Run E2E tests
npm run test:e2e:ui # Run with UI
# Development
npm run dev # Start Electron app
npm run dev:vite # Start Vite only
npm run dev:electron # Start Electron only
# Testing
npm test # Run unit tests
npm run test:e2e # Run E2E tests
# Building
npm run build # Build for current platform
npm run build:win:x64 # Windows installer
npm run build:mac:x64 # macOS Intel
npm run build:mac:arm64 # macOS ARM
npm run build:all # All platforms
# Utilities
npm run rebuild # Rebuild native modules
npm run type-check # TypeScript validation
📖 The full money-rules reference lives in docs/FEATURE_GUIDE.md — transaction types and IN/OUT semantics, payment legs (split/change), client propagation, void/refund rules, supplier & partner ledgers, profits, sessions, and the checklist every new feature must satisfy. This section covers only selected highlights.
LiraTek implements intelligent debt repayment logic to handle real-world cash transactions while maintaining accounting accuracy.
When a debt of $262.95 is displayed as "$262 + 85,000 LBP" and a customer pays only 85,000 LBP:
Smart Debt Reduction Algorithm:
Example Flow:
Total Debt: $262.95
├─ Integer Part: $262
└─ Fractional Part: $0.95 = 84,550 LBP
└─ Rounded for Display: 85,000 LBP
Customer Pays: 85,000 LBP
├─ Drawer Receives: 85,000 LBP ✓
├─ Debt Reduced By: $0.95 ✓
└─ Shop Gain: 450 LBP ≈ $0.0051
Remaining Debt: $262.00 ✓ (not $261.99)
Implementation:
Frontend logic detects when paying the fractional portion:
const integerDebt = Math.floor(totalDebt);
const fractionalDebt = totalDebt - integerDebt;
const fractionalLBP = fractionalDebt * EXCHANGE_RATE;
if (paidLBP > 0) {
const roundedFractionalLBP = Math.ceil(fractionalLBP / 5000) * 5000;
// Check if paying the rounded fractional portion
if (Math.abs(paidLBP - roundedFractionalLBP) < 1000) {
// Reduce debt by actual fractional amount
debtReductionUSD += fractionalDebt;
} else {
// Use exact LBP conversion
debtReductionUSD += paidLBP / EXCHANGE_RATE;
}
}
Backend separates payment recording from debt reduction:
// Record debt reduction in debt_ledger
INSERT INTO debt_ledger (amount_usd, amount_lbp)
VALUES (-0.95, 0); // Actual debt reduction
// Record payment in payments table
INSERT INTO payments (amount)
VALUES (85000); // Actual payment received
// Update drawer balance
UPDATE drawer_balances
SET balance = balance + 85000; // Cash received
Benefits:
Exchange rate management with configurable rates:
// src/config/constants.ts
export const EXCHANGE_RATE = 89000; // 1 USD = 89,000 LBP
Supported currencies: USD, LBP, EUR (via exchange module)
shop_base_system) → its system drawerOMT_App, Whish_App, Binance drawers (SEND: wallet −, General + · RECEIVE: wallet +, General −)CUSTOMER_ACCOUNT (open debt or prepaid credit) and GIFT_CARD never touch a drawerEach drawer tracks balances independently with opening/closing audit workflow and a checkpoint timeline. A payment can be split into multiple legs (each its own method + currency), plus change/return (OUT) legs — see docs/FEATURE_GUIDE.md for the leg rules.
LiraTek provides comprehensive health check endpoints for monitoring and orchestration.
# Basic health check (fast, no dependencies)
GET /health
# Returns: { status: "ok", uptime: 123.45, version: "1.0.0" }
# Detailed diagnostics (database, memory, system)
GET /health/detailed
# Returns: Full system health including database latency, memory usage, CPU load
# Kubernetes readiness probe
GET /health/ready
# Returns 200 if ready to serve traffic, 503 if not
# Kubernetes liveness probe
GET /health/live
# Returns 200 if process is alive (for restart decisions)
# Start the backend
cd backend && npm run dev
# Test health endpoints (in another terminal)
curl http://localhost:3000/health
curl http://localhost:3000/health/detailed
/health for fast checks/health/ready and /health/live probesdocker-compose.yml/health/detailed for metricsSee Health Checks Documentation for complete details.
Unit Tests:
npm test # All tests
npm test -- ClientService.test.ts # Specific test
npm run test:coverage # With coverage
E2E Tests:
cd frontend
npm run test:e2e # All E2E tests
npm run test:e2e -- login.spec.ts # Specific spec
npm run test:e2e:ui # Interactive mode
Repository Test Example:
import Database from "better-sqlite3";
import { ClientRepository } from "../ClientRepository";
describe("ClientRepository", () => {
let db: Database.Database;
let repo: ClientRepository;
beforeEach(() => {
db = new Database(":memory:");
// Create tables...
repo = new ClientRepository(db);
});
afterEach(() => {
db.close();
});
it("should create a client", () => {
const result = repo.create({
full_name: "John Doe",
phone_number: "1234567890",
});
expect(result.success).toBe(true);
});
});
| Platform | Command | Output |
|---|---|---|
| Windows | npm run build:win:x64 | NSIS Installer (.exe) |
| macOS (Intel) | npm run build:mac:x64 | DMG + ZIP |
| macOS (ARM) | npm run build:mac:arm64 | DMG + ZIP |
| All | npm run build:all | All target installers |
If icons need refreshing, use ImageMagick:
magick convert build/icon.png -define icon:auto-resize=256,128,64,48,32,16 build/icon.ico
package.jsonRELEASE_NOTES_v*.mdgit tag v1.0.0git push origin main --tagsPassword Security:
Role-Based Access:
Session Management:
// Sessions encrypted using Electron's safeStorage API
// Leverages native OS keychain:
// - macOS: Keychain
// - Windows: DPAPI
Status: Infrastructure implemented, SQLCipher build required
LiraTek includes built-in support for database encryption using SQLCipher. The encryption system is fully implemented but requires a SQLCipher-enabled build of better-sqlite3.
Key Management (@liratek/core):
Resolution Order:
DATABASE_KEY environment variable~/Documents/LiraTek/db-key.txt fileOption 1: Environment Variable
# Generate a secure key (64 characters recommended)
DATABASE_KEY=$(openssl rand -hex 32)
# Desktop mode
DATABASE_KEY="your-secure-key-here" npm run dev
# Web mode (add to backend/.env)
echo "DATABASE_KEY=your-secure-key-here" >> backend/.env
Option 2: Configuration File (Recommended)
# Generate and save key
openssl rand -hex 32 > ~/Documents/LiraTek/db-key.txt
chmod 600 ~/Documents/LiraTek/db-key.txt
# Application will automatically use this key on next start
npm run dev
To enable actual encryption, you need a SQLCipher-enabled build of better-sqlite3.
⚠️ Note: @journeyapps/sqlcipher is NOT compatible - it uses a completely different async API.
Option 1: Build better-sqlite3 with SQLCipher (Advanced)
This requires SQLCipher development libraries installed on your system:
# macOS (using Homebrew)
brew install sqlcipher
# Build better-sqlite3 with SQLCipher
npm install better-sqlite3 --build-from-source --sqlite3=$(brew --prefix sqlcipher)
# Rebuild for Electron
cd electron-app
npm rebuild better-sqlite3 --runtime=electron --target=31.0.0
Option 2: Use Pre-built Package (If Available)
Search for community packages like better-sqlite3-sqlcipher or pre-compiled builds.
Option 3: Docker/Container with SQLCipher
Build a Docker image with SQLCipher and deploy as containerized application.
To encrypt an existing database:
# Backup current database
cp ~/Library/Application\ Support/liratek/phone_shop.db ~/Desktop/phone_shop_backup.db
# Create migration script
node migrate_to_encrypted.js
migrate_to_encrypted.js:
const Database = require("better-sqlite3"); // Must be SQLCipher-enabled build
const fs = require("fs");
const oldDb = "~/Library/Application Support/liratek/phone_shop.db";
const newDb = "~/Library/Application Support/liratek/phone_shop_encrypted.db";
const key = fs.readFileSync("~/Documents/LiraTek/db-key.txt", "utf8").trim();
// Open unencrypted database
const db = new Database(oldDb);
// Attach encrypted database
db.exec(`ATTACH DATABASE '${newDb}' AS encrypted KEY '${key}';`);
// Export to encrypted database
db.exec('SELECT sqlcipher_export("encrypted");');
// Detach and close
db.exec("DETACH DATABASE encrypted;");
db.close();
console.log("✅ Migration complete. Backup old DB and rename new DB.");
⚠️ Key Loss = Data Loss: If you lose the encryption key, the database cannot be decrypted.
⚠️ Backup Keys Securely: Store keys in a password manager or secure location.
⚠️ Don't Commit Keys: Keys in git history compromise security.
Check encryption status in application logs:
🔐 SQLCipher: keySource=file:db-key.txt, applied=true, supported=true
keySource: Where the key was loaded fromapplied: Whether encryption was appliedsupported: Whether SQLCipher is availableThe UI (frontend/) runs in two modes:
window.api (defined in electron-app/preload.ts).backend/ on port 3000).To keep components clean, we route calls through a single facade:
frontend/src/api/backendApi.tsbackendApi.ts instead of calling window.api directly.http://localhost:3000 (see frontend/src/api/httpClient.ts).We added task T-26 to ensure every exported function in backendApi.ts correctly routes:
window.api.*)requestJson)Electron Mode (desktop) (does not require backend server):
npm run dev
Browser Mode (web):
npm run dev:web
# backend: http://localhost:3000
# frontend: http://localhost:5173
Electron Mode (all features):
npm run dev
Browser Mode (REST API):
cd backend && npm run dev # Terminal 1
cd frontend && npm run dev # Terminal 2
# Visit http://localhost:5173
Database Errors:
# Missing columns
Check electron/db/migrations/ and apply manually
# Foreign key violations
window.api.diagnostics.foreignKeyCheck()
Build Errors:
# Native module issues
npm run rebuild
# or
npx electron-rebuild
# TypeScript errors
Check tsconfig.json and ensure all @types packages are installed
Performance Issues:
# Slow queries - check indexes
See electron/db/create_db.sql
# Memory leaks
Ensure cleanup in React useEffect hooks
Close database connections properly
Docker Issues:
# Reset everything
docker compose down -v
docker compose up --build
Enable detailed logging:
# Set environment variable
DEBUG=liratek:* npm run dev
View logs:
activity_logs tablecd electron-app
npm rebuild better-sqlite3 --runtime=electron --target=31.0.0
npm run build
npm run dev:frontend in separate terminal~/Library/Application Support/@liratek/electron-app/rm ~/Library/Application\ Support/@liratek/electron-app/*.db@liratek/core is built first: npm run build:corenpm run clean && yarn install && npm run buildLiraTek aims to provide Lebanese phone shop owners with a robust, offline-capable management platform that handles the unique complexities of multi-currency markets and telecom services.
Contributions are welcome! Please:
© 2025 LiraTek Team. All rights reserved.
For issues, questions, or feature requests:
docs/marketing/ - Product marketing and promotion guidesdocs/templates/ - Business document templates (quotations, etc.)docs/archive/ - Historical documentation for reference