# 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( '

%s

', 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.