# SpondonIt Package Architecture Guide

Guidelines for maintaining `spondonit/service` (core) and building application-specific packages like `lms-service`.

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│                    Laravel Host Application              │
│  bootstrap/providers.php registers both providers       │
│  composer.json PSR-4 maps both package src/ dirs        │
├──────────────────────┬──────────────────────────────────┤
│  spondonit/service   │   spondonit/{app}-service        │
│  (core package)      │   (app package — 1 per product)  │
│                      │                                  │
│  Contracts           │   Implements both contracts      │
│  Install wizard      │   App config (item ID, user)     │
│  License verify      │   Admin creation logic           │
│  Daily checks        │   Pre-requisite checks           │
│  DB setup            │   App-specific views & routes    │
│  Middleware           │   App-specific translations      │
│  Pre-req checks      │                                  │
│  Helpers             │   Depends on: spondonit/service  │
└──────────────────────┴──────────────────────────────────┘
```

**Key principle:** Core defines *contracts* and *workflow*. App package provides *implementation*.

---

## Core Package Contracts

### AppInitializerInterface

Called by `InstallationGuardMiddleware` on every web request.

```php
interface AppInitializerInterface
{
    /** Set config('app.item') — called ALWAYS, before install check. */
    public function init(): void;

    /** Boot singletons/config — called ONLY when app is installed. */
    public function config(): void;
}
```

### AppInstallerInterface

Called during the install wizard, after database migration.

```php
interface AppInstallerInterface
{
    /** Full install: create admin, seed, post-install env writes. */
    public function install(array $params): void;

    /** Create the initial admin user. */
    public function makeAdmin(array $params): mixed;

    /** Post-install scripts (env writes, settings, etc.). */
    public function postInstallScript(mixed $admin, array $params): void;
}
```

---

## Install Flow

```
1. GET  /install              → App's InstallController::index()    [welcome]
2. GET  /install/pre-requisite → Core's SpondonController            [server checks]
3. GET  /install/license      → Core's SpondonController            [license form]
4. POST /install/license      → Core validates against license server
5. GET  /install/database     → Core's SpondonController            [DB credentials]
6. POST /install/database     → Core validates + writes .env
7. GET  /install/user         → App's InstallController::user()     [admin form]
8. POST /install/user         → Core migrate → App's install()
9. GET  /install/done         → Core's SpondonController            [success page]
```

---

## App Package Structure (Reference: lms-service)

```
packages/spondonit/lms-service/
├── composer.json                      # Depends on spondonit/service
├── config/
│   ├── lms.php                       # item_id, user_model
│   └── prerequisites.php              # App-specific server checks
├── resources/
│   ├── lang/en/install.php            # Translations
│   └── views/install/
│       ├── welcome.blade.php          # Step 1: Welcome page
│       └── user.blade.php             # Step 7: Admin setup form
├── routes/
│   └── web.php                        # /install, /install/user
└── src/
    ├── SpondonItLmsServiceProvider.php
    ├── Controllers/
    │   └── InstallController.php      # Welcome + user setup
    ├── Initializers/
    │   └── LmsInitializer.php        # implements AppInitializerInterface
    ├── Installers/
    │   └── LmsInstaller.php          # implements AppInstallerInterface
    └── Requests/
        └── UserRequest.php            # Validation for admin form
```

### ServiceProvider Pattern

```php
class SpondonItLmsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // 1. Bind both contracts
        $this->app->bind(AppInstallerInterface::class, LmsInstaller::class);
        $this->app->bind(AppInitializerInterface::class, LmsInitializer::class);

        // 2. Merge app config
        $this->mergeConfigFrom(__DIR__ . '/../config/lms.php', 'lms');

        // 3. Push app pre-requisites into core config
        $prerequisites = require __DIR__ . '/../config/prerequisites.php';
        config([
            'spondonit.pre_requisite' => array_merge(
                config('spondonit.pre_requisite', []),
                $prerequisites['pre_requisite'] ?? [],
            ),
            'spondonit.php_version' => '8.3.0',  // Override if app needs higher
        ]);
    }

    public function boot(): void
    {
        // NO middleware registration — core handles it via InstallationGuardMiddleware
        $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
        $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'lms');
        $this->loadViewsFrom(__DIR__ . '/../resources/views', 'lms');
    }
}
```

---

## Creating a New App Package

### Step 1: Scaffold

```bash
APP=myapp
mkdir -p packages/spondonit/${APP}-service/{config,resources/{lang/en,views/install},routes,src/{Controllers,Initializers,Installers,Requests}}
```

### Step 2: composer.json

```json
{
    "name": "spondonit/{app}-service",
    "description": "Application service package for {AppName}",
    "type": "library",
    "require": { "spondonit/service": "dev-main" },
    "license": "MIT",
    "minimum-stability": "stable",
    "autoload": {
        "psr-4": { "SpondonIt\\{App}Service\\": "src/" }
    },
    "extra": {
        "laravel": {
            "providers": ["SpondonIt\\{App}Service\\SpondonIt{App}ServiceProvider"]
        }
    }
}
```

### Step 3: config/{app}.php

```php
return [
    'item_id' => env('{APP}_ITEM_ID', 'YOUR_CODECANYON_ID'),
    'user_model' => \App\Models\User::class,
];
```

### Step 4: config/prerequisites.php

```php
return [
    'pre_requisite' => [
        ['type' => function_exists('symlink'), 'message' => 'symlink() available.', 'help' => 'Enable symlink().', 'fatal' => true],
        ['type' => function_exists('proc_open'), 'message' => 'proc_open() available.', 'help' => 'Enable proc_open().', 'fatal' => true],
        ['type' => function_exists('putenv'), 'message' => 'putenv() available.', 'help' => 'Enable putenv().', 'fatal' => true],
        // Add app-specific extension checks here
    ],
];
```

### Step 5: Initializer (src/Initializers/{App}Initializer.php)

```php
class {App}Initializer implements AppInitializerInterface
{
    public function init(): void
    {
        config(['app.item' => config('{app}.item_id')]);
    }

    public function config(): void
    {
        // Boot singletons, view composers, etc. — only when installed.
    }
}
```

### Step 6: Installer (src/Installers/{App}Installer.php)

```php
class {App}Installer implements AppInstallerInterface
{
    public function __construct(protected DatabaseService $databaseService) {}

    public function install(array $params): void
    {
        $admin = $this->makeAdmin($params);
        $this->databaseService->seed(!empty($params['seed']));
        $this->postInstallScript($admin, $params);
        Artisan::call('key:generate', ['--force' => true]);
        envu(['APP_ENV' => 'production', 'APP_DEBUG' => 'false']);
    }

    public function makeAdmin(array $params): mixed { /* ... */ }
    public function postInstallScript(mixed $admin, array $params): void { /* ... */ }
}
```

### Step 7: Copy from lms-service

Copy and rename: `InstallController`, `UserRequest`, `routes/web.php`, views, translations.
Replace all `lms` → `{app}`, `Lms` → `{App}`, `LmsService` → `{App}Service`.

### Step 8: Register in host app

**composer.json:**
```json
"autoload": {
    "psr-4": {
        "SpondonIt\\{App}Service\\": "packages/spondonit/{app}-service/src"
    }
}
```

**bootstrap/providers.php:**
```php
SpondonIt\Service\SpondonItServiceProvider::class,
SpondonIt\{App}Service\SpondonIt{App}ServiceProvider::class,
```

---

## Migrating an Existing App Package (Checklist)

Use this to convert any of the 19 existing packages (same pattern as the old lms-service).

### Delete dead files

- [ ] Delete `Helpers/functions.php` (contains `exit()` / InfixLMS references)
- [ ] Delete `Helpers/helper.php` (empty or `isModuleActive()` for nwidart)
- [ ] Delete `src/Middleware/` directory (duplicates core's `InstallationGuardMiddleware`)
- [ ] Delete `src/Repositories/` directory (after creating Initializers + Installers)
- [ ] Remove `autoload.files` entries from `composer.json`

### Clean config/{app}.php

- [ ] Remove: `module_manager_model`, `module_manager_table`, `settings_model`, `module_model`, `settings_table`, `database_file`, `modules`
- [ ] Keep only: `item_id` (use env), `user_model`

### Create config/prerequisites.php

- [ ] Add app-specific checks (symlink, proc_open, putenv, + app extensions)
- [ ] Copy template from lms-service

### Rename & refactor classes

- [ ] `Repositories/InitRepository` → `Initializers/{App}Initializer`
- [ ] `Repositories/InstallRepository` → `Installers/{App}Installer`
- [ ] Installer: inject `DatabaseService` instead of `InstallService`
- [ ] Replace `gv($params, 'key')` → `$params['key'] ?? null`
- [ ] Replace `gv($params, 'key', 'default')` → `$params['key'] ?? 'default'`
- [ ] Replace `gbv($params, 'key')` → `!empty($params['key'])`
- [ ] Replace `$this->installService->rollbackDb()` → `$this->databaseService->wipe()`
- [ ] Replace `$this->installService->seed(...)` → `$this->databaseService->seed(...)`
- [ ] Read item ID from config instead of hardcoding

### Update ServiceProvider

- [ ] Bind contracts to new Initializer/Installer classes
- [ ] Remove middleware registration from `boot()`
- [ ] Add prerequisite config merging in `register()`
- [ ] Override `spondonit.php_version` if needed

### Fix composer.json

- [ ] Remove `autoload.files` (no helper files)
- [ ] Change `minimum-stability` from `dev` to `stable`
- [ ] Fix description if it has extra quotes or wrong app name

### Add return types

- [ ] All methods: explicit return types
- [ ] `UserRequest`: `authorize(): bool`, `rules(): array`, `attributes(): array`, `messages(): array`

---

## Pre-Requisite Check Format

```php
[
    'type' => bool,        // true = pass, false = fail
    'message' => string,   // Shown when check passes
    'help' => string,      // Shown when check fails (instructions to fix)
    'fatal' => bool,       // true = blocks installation, false = warning only
]
```

### Common checks to include

| Check | Code | When to use |
|-------|------|-------------|
| symlink | `function_exists('symlink')` | Always (storage:link) |
| proc_open | `function_exists('proc_open')` | Always (Artisan, queues) |
| putenv | `function_exists('putenv')` | Always (env handling) |
| xml | `extension_loaded('xml')` | Spreadsheet exports |
| dom | `extension_loaded('dom')` | HTML processing, exports |
| gd | `extension_loaded('gd')` | Image processing |
| bcmath | `extension_loaded('bcmath')` | Financial calculations |
| intl | `extension_loaded('intl')` | Currency/locale formatting |
| redis | `extension_loaded('redis')` | Redis caching |
| exif | `extension_loaded('exif')` | Image EXIF data |
| sodium | `extension_loaded('sodium')` | Encryption |

---

## Rules

1. **No middleware in app packages** — core's `InstallationGuardMiddleware` handles init + install guard
2. **No helper files in app packages** — put app helpers in the host app's `app/helpers.php`
3. **No cross-app references** — never reference InfixLMS, MultiVendor, Nwidart\Modules, etc.
4. **No `die()` / `exit()` in any file** — breaks testing and development
5. **No `gv()` / `gbv()` in new code** — use `??` operator and `!empty()`
6. **No hardcoded item IDs** — always read from config
7. **No `minimum-stability: dev`** — use `stable`
8. **Don't duplicate core functionality** — use the core's services directly
9. **Inject `DatabaseService` not `InstallService`** for seed/wipe operations in installers

