290 lines
10 KiB
Markdown
290 lines
10 KiB
Markdown
# Siti Plugin Repo
|
||
|
||
Deze repository bevat een Vite/React frontend en een Express backend die samen een centrale licentie- en pluginbeheeromgeving vormen. Je beheert hier WordPress-plugins, gekoppelde licenties en gebruikers (inclusief admin-rollen), en biedt een HTTP API waar je eigen plugins tegenaan kunnen praten voor licentievalidatie en update-informatie.
|
||
|
||
## Inhoud
|
||
|
||
1. [Architectuuroverzicht](#architectuuroverzicht)
|
||
2. [Installatie & configuratie](#installatie--configuratie)
|
||
3. [Gebruikers & rollen](#gebruikers--rollen)
|
||
4. [API-overzicht](#api-overzicht)
|
||
5. [Integratie met je WordPress-plugin](#integratie-met-je-wordpress-plugin)
|
||
6. [Handige tips](#handige-tips)
|
||
|
||
---
|
||
|
||
## Architectuuroverzicht
|
||
|
||
| Component | Omschrijving |
|
||
| --- | --- |
|
||
| `server/` | Express-app die licenties, gebruikers en plugin metadata serveert. Praat met MariaDB en Git providers. |
|
||
| `src/` | React frontend voor licentiebeheer, inclusief admin flows en in-app authenticatie. |
|
||
| MariaDB | Externe database met tabellen `users`, `licenses`, `license_hostnames`. Licentiegegevens staan **niet** meer in JSON-bestanden. |
|
||
| Git providers | Manifest-/release-/commitdata wordt opgehaald bij GitHub of Gitea, afhankelijk van entries in `server/repos.json`. |
|
||
|
||
Belangrijke eigenschappen:
|
||
|
||
- Vanaf de eerste run wordt het schema automatisch gecreëerd in MariaDB.
|
||
- De **allereerste gebruiker** die zich registreert wordt automatisch admin.
|
||
- Admins kunnen gebruikers aanmaken en licenties namens andere gebruikers genereren.
|
||
- Een licentie hoort bij de **eerste hostname** die zich meldt; elke volgende hostname wordt geweigerd.
|
||
|
||
## Installatie & configuratie
|
||
|
||
### Vereisten
|
||
|
||
- Node.js 18+
|
||
- MariaDB 10.6+ (extern of lokaal)
|
||
- Optioneel Docker (voor containerized deployment)
|
||
|
||
### Stap 1 – `.env`
|
||
|
||
Gebruik `.env.example` als startpunt en zet hem om naar `.env`:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Stel daarna minimaal de volgende variabelen in:
|
||
|
||
```dotenv
|
||
DB_HOST=127.0.0.1
|
||
DB_PORT=3306
|
||
DB_USER=sitiapp
|
||
DB_PASSWORD=supergeheim
|
||
DB_NAME=siti_plugin_repo
|
||
JWT_SECRET=een-lang-en-random-geheim
|
||
JWT_EXPIRES_IN=7d
|
||
```
|
||
|
||
> Let op: geef de backend toegang tot een **bestaande database**. Het schema wordt automatisch ingericht, maar de database zelf moet bestaan.
|
||
|
||
### Stap 2 – Dependencies installeren
|
||
|
||
```bash
|
||
npm install
|
||
```
|
||
|
||
### Stap 3 – Ontwikkelservers starten
|
||
|
||
```bash
|
||
# Backend API + licentiebeheer UI
|
||
npm run dev:server
|
||
|
||
# Frontend (Vite dev server)
|
||
npm run dev
|
||
```
|
||
|
||
Vite proxy’t `/api/*` naar `localhost:3001`, dus beide processen moeten draaien.
|
||
|
||
### Docker Compose
|
||
|
||
Wil je alles containerizen, gebruik dan `docker-compose.yml`. De Compose configuratie bevat alleen de applicatiecontainer; MariaDB blijft een externe service. Voorbeeld:
|
||
|
||
```bash
|
||
DB_HOST=database.internal
|
||
DB_PORT=3306
|
||
DB_USER=sitiapp
|
||
DB_PASSWORD=my-secret
|
||
DB_NAME=siti_plugin_repo
|
||
JWT_SECRET=prod-secret
|
||
docker compose up --build
|
||
```
|
||
|
||
## Gebruikers & rollen
|
||
|
||
- **Eerste gebruiker** → admin (`is_admin = 1`).
|
||
- Admins kunnen:
|
||
- Overzicht van alle gebruikers downloaden (`GET /api/admin/users`).
|
||
- Nieuwe gebruikers aanmaken (`POST /api/admin/users`).
|
||
- Licenties koppelen aan andere gebruikers door `userId` mee te geven aan `POST /api/licenses`.
|
||
- Niet-admins:
|
||
- Kunnen alleen hun eigen licenties zien en aanmaken.
|
||
- Kunnen geen andere gebruikers zien of licenties voor anderen beheren.
|
||
|
||
Authenticatie:
|
||
|
||
- JWT’s worden uitgegeven door `/api/auth/login` of `/api/auth/register`.
|
||
- Het frontend bewaart de token in `localStorage` en stuurt `Authorization: Bearer …`.
|
||
- `requireAdmin` middleware blokkeert niet-admin gebruikers automatisch voor admin-routes.
|
||
|
||
## API-overzicht
|
||
|
||
| Methode & pad | Rol | Beschrijving |
|
||
| --- | --- | --- |
|
||
| `POST /api/auth/register` | Publiek | Maakt gebruiker aan (eerste gebruiker wordt admin). |
|
||
| `POST /api/auth/login` | Publiek | Verstrekt JWT. |
|
||
| `GET /api/auth/me` | Auth | Retourneert huidige gebruiker. |
|
||
| `GET /api/admin/users` | Admin | Geeft lijst met alle gebruikers. |
|
||
| `POST /api/admin/users` | Admin | Maakt gebruiker aan (met optionele admin-flag). |
|
||
| `GET /api/plugins` | Publiek | Lijst met plugins uit `server/repos.json`. |
|
||
| `GET /api/plugins/:owner/:repo` | Publiek | Details van één plugin. |
|
||
| `GET /api/licenses` | Auth | Licenties van ingelogde gebruiker. Admins kunnen `?userId=…` meegeven. |
|
||
| `POST /api/licenses` | Auth | Licentie aanmaken. Admins: `userId` om een andere eigenaar te kiezen. |
|
||
| `POST /api/licenses/verify` | Publiek | Valideert licentiekey + hostname en geeft huidige versie terug. |
|
||
| `POST /api/licenses/download` | Publiek (met geldige licentie) | Verifieert licentie + hostname en streamt een ZIP (versie of latest). |
|
||
|
||
**`POST /api/licenses/verify`** is het belangrijkste eindpunt voor WordPress-plugins. Request body:
|
||
|
||
```json
|
||
{
|
||
"key": "SITI-XXXX-XXXX",
|
||
"hostname": "example.com"
|
||
}
|
||
```
|
||
|
||
Response (succes):
|
||
|
||
```json
|
||
{
|
||
"valid": true,
|
||
"hostname": "example.com",
|
||
"boundNow": false,
|
||
"license": {
|
||
"pluginName": "Mijn Plugin",
|
||
"pluginVersion": "1.2.3",
|
||
"primaryHostname": "example.com",
|
||
"key": "SITI-XXXX-XXXX",
|
||
"hostnames": [
|
||
{ "hostname": "example.com", "hits": 12, "lastSeenAt": "2026-02-01T11:22:33.000Z" }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
Als de hostname nog niet eerder gekoppeld was, wordt `boundNow: true`. Als een andere site al gekoppeld is, krijg je HTTP 403 met de melding `Licentie hoort bij …`.
|
||
|
||
## Integratie met je WordPress-plugin
|
||
|
||
### 1. Bewaar licentie en API-basis-URL
|
||
|
||
Maak in je plugin een settingspagina waar de klant:
|
||
|
||
- De licentiecode invult (bijv. `SITI-ABCD-1234`).
|
||
- De URL van je licentie-API configureert (`https://repo.jouwdomein.nl`).
|
||
|
||
Sla dit op in `wp_options`, bijvoorbeeld:
|
||
|
||
```php
|
||
update_option( 'siti_license_key', sanitize_text_field( $_POST['siti_license_key'] ) );
|
||
update_option( 'siti_license_api', esc_url_raw( $_POST['siti_license_api'] ) );
|
||
```
|
||
|
||
### 2. License check helper
|
||
|
||
```php
|
||
function siti_verify_license() {
|
||
$license = get_option( 'siti_license_key' );
|
||
$api = untrailingslashit( get_option( 'siti_license_api' ) );
|
||
|
||
if ( empty( $license ) || empty( $api ) ) {
|
||
return new WP_Error( 'missing', 'Geen licentie ingesteld.' );
|
||
}
|
||
|
||
$hostname = parse_url( home_url(), PHP_URL_HOST );
|
||
$response = wp_remote_post( "{$api}/api/licenses/verify", array(
|
||
'headers' => array( 'Content-Type' => 'application/json' ),
|
||
'body' => wp_json_encode( array(
|
||
'key' => $license,
|
||
'hostname' => $hostname,
|
||
) ),
|
||
'timeout' => 15,
|
||
) );
|
||
|
||
if ( is_wp_error( $response ) ) {
|
||
return $response;
|
||
}
|
||
|
||
$code = wp_remote_retrieve_response_code( $response );
|
||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||
|
||
if ( 200 !== $code || empty( $body['valid'] ) ) {
|
||
return new WP_Error( 'invalid', $body['error'] ?? 'Licentie ongeldig.' );
|
||
}
|
||
|
||
update_option( 'siti_license_last_check', current_time( 'mysql' ) );
|
||
update_option( 'siti_license_version', $body['license']['pluginVersion'] ?? null );
|
||
|
||
return $body['license'];
|
||
}
|
||
```
|
||
|
||
Gebruik deze helper in:
|
||
|
||
- `admin_init` (om bij het openen van de plugin-instellingen te valideren).
|
||
- Een WP-Cron job (bijv. dagelijks) om de licentie actief te houden.
|
||
|
||
### 3. Updates / downloads doorgeven aan WordPress
|
||
|
||
Omdat je backend bij `verify` de `pluginVersion` retourneert, kun je dat gebruiken om WP te vertellen dat er een update is. Een simpele aanpak:
|
||
|
||
```php
|
||
add_filter( 'pre_set_site_transient_update_plugins', function ( $transient ) {
|
||
$license = siti_verify_license();
|
||
if ( is_wp_error( $license ) ) {
|
||
return $transient;
|
||
}
|
||
|
||
$current_version = '1.2.0'; // huidige pluginversie
|
||
$remote_version = $license['pluginVersion'];
|
||
|
||
if ( version_compare( $remote_version, $current_version, '>' ) ) {
|
||
$transient->response['jouw-plugin/jouw-plugin.php'] = (object) array(
|
||
'slug' => 'jouw-plugin',
|
||
'new_version' => $remote_version,
|
||
'package' => 'https://jouw-download-url/zip', // eigen distributie
|
||
'tested' => get_bloginfo( 'version' ),
|
||
'url' => 'https://jouwwebsite.nl/plugin',
|
||
);
|
||
}
|
||
|
||
return $transient;
|
||
});
|
||
```
|
||
|
||
> De API levert geen ZIP-bestand; je blijft zelf verantwoordelijk voor het aanbieden van downloads (bijv. via GitHub releases of Gitea). Gebruik `package` hierboven om naar de daadwerkelijke download te verwijzen.
|
||
|
||
#### Download endpoint gebruiken
|
||
|
||
Wanneer je liever via jouw eigen endpoint distribueert, roep dan `POST /api/licenses/download` aan vanaf je plugin of update-server. Body:
|
||
|
||
```json
|
||
{
|
||
"key": "SITI-XXXX-XXXX",
|
||
"hostname": "example.com",
|
||
"version": "v1.2.3" // of "latest"
|
||
}
|
||
```
|
||
|
||
Bij succes streamt de API direct een ZIP-bestand. Tip:
|
||
|
||
1. Laat WordPress jouw eigen mini-proxy aanspreken (omdat WP_UPgrader meestal een URL verwacht). Je proxy kan de POST doen, headers controleren en de stream doorgeven.
|
||
2. Of gebruik `download_url()` in WordPress met `wp_remote_post` en sla de response body tijdelijk op in upload-dir.
|
||
3. Versie `latest` pakt de nieuwste release (via GitHub/Gitea). Specifieke tags vallen terug op `refs/tags/{tag}`. Als een tag ontbreekt, wordt de branch/commitnaam gebruikt.
|
||
|
||
### 4. Fouten tonen
|
||
|
||
Laat admins weten wanneer een licentie ongeldig is:
|
||
|
||
```php
|
||
add_action( 'admin_notices', function () {
|
||
$license = siti_verify_license();
|
||
if ( is_wp_error( $license ) ) {
|
||
printf(
|
||
'<div class="notice notice-error"><p>%s</p></div>',
|
||
esc_html( $license->get_error_message() )
|
||
);
|
||
}
|
||
} );
|
||
```
|
||
|
||
## Handige tips
|
||
|
||
- Houd `server/repos.json` up-to-date met alle Git repositories waarvan je manifest wilt tonen.
|
||
- Wanneer je licenties migreert vanuit `server/licenses.json`, schrijf een kleine scriptje dat records in de nieuwe MariaDB-tabellen importeert.
|
||
- Gebruik HTTPS op productie; licentiekeys en hostnames gaan over het netwerk.
|
||
- Voor staging of development kun je `JWT_SECRET` kort houden, maar in productie moet het lang en random zijn.
|
||
- Log server-output (`server.log`) in een centralized logging stack om snel te zien wanneer licenties worden geweigerd.
|
||
|
||
Veel succes met het integreren! Mocht je verdere automatisering willen (bijvoorbeeld automatische updateserver voor ZIP’s), breid dit project dan uit met een downloadendpoint dat controleert of een licentie geldig is voordat hij een ZIP serveert.
|