# 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