Compare commits
40 Commits
f76f30e88f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1db80e76fe | |||
| 51e3876ed9 | |||
| 483342602f | |||
| ccbd200464 | |||
| 834908df9f | |||
| 434f58d5aa | |||
| 4c085ac7eb | |||
| bbefdce290 | |||
| 5b8d1cda7d | |||
| ea2160132a | |||
| 2ac1264b93 | |||
| 0f0de43da5 | |||
| 1ee60405ce | |||
| 78704190bc | |||
| f109b1f764 | |||
| af2f76d9db | |||
| 6f11cae3ca | |||
| 16b2f5bbb7 | |||
| 6e437be6d1 | |||
| 4f4f40ac99 | |||
| be6a578f5e | |||
| 4d8d22f40a | |||
| 8a324b3082 | |||
| f7fe86efeb | |||
| 36887244e6 | |||
| 9b5c655c68 | |||
| b2bb1811d0 | |||
| 4b6872e5e0 | |||
| 66cbd46f37 | |||
| 889cec60e9 | |||
| 8ce3fcca85 | |||
| b8a15c8412 | |||
| 82d00ad11d | |||
| c468040792 | |||
| 4487084614 | |||
| e96e2a0fd3 | |||
| 4378144f45 | |||
| 59188a5f2c | |||
| ff364992ca | |||
| 2997486662 |
@@ -1,118 +0,0 @@
|
||||
# AtomCMS Environment Configuration
|
||||
# Works on Linux (Nginx/Apache) and Windows (XAMPP/IIS)
|
||||
# Copy to .env and adjust values for your setup
|
||||
|
||||
# ==========================================
|
||||
# APPLICATION
|
||||
# ==========================================
|
||||
APP_NAME="AtomCMS"
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
# ==========================================
|
||||
# DATABASE - Works on Linux & Windows
|
||||
# ==========================================
|
||||
DB_CONNECTION=mariadb
|
||||
DB_HOST=127.0.0.1 # Linux: 127.0.0.1, Windows XAMPP: localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=habbo
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
# Windows IIS (SQL Server) - uncomment if using IIS/SQL Server:
|
||||
# DB_CONNECTION=sqlsrv
|
||||
# DB_HOST=(local)
|
||||
# DB_DATABASE=habbo
|
||||
# DB_USERNAME=sa
|
||||
# DB_PASSWORD=your_password
|
||||
|
||||
# ==========================================
|
||||
# SESSION & CACHE (Windows compatible)
|
||||
# ==========================================
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
# ==========================================
|
||||
# MAIL (Windows compatible)
|
||||
# ==========================================
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS="noreply@yourhotel.nl"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# ==========================================
|
||||
# REAL-TIME & PUSHER
|
||||
# ==========================================
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
# ==========================================
|
||||
# SECURITY
|
||||
# ==========================================
|
||||
# Cloudflare Turnstile (optional)
|
||||
TURNSTILE_SITE_KEY=
|
||||
TURNSTILE_SECRET_KEY=
|
||||
|
||||
# Google Recaptcha (optional)
|
||||
NOCAPTCHA_SECRET=
|
||||
NOCAPTCHA_SITEKEY=
|
||||
|
||||
# ==========================================
|
||||
# WEBHOOKS
|
||||
# ==========================================
|
||||
DISCORD_WEBHOOK_URL=
|
||||
|
||||
# ==========================================
|
||||
# NITRO CLIENT PATHS
|
||||
# ==========================================
|
||||
# Linux: /var/www/atomcms/nitro-client
|
||||
# Windows: C:\path\to\atomcms\nitro-client
|
||||
NITRO_CLIENT_PATH=/var/www/atomcms/nitro-client
|
||||
NITRO_RENDERER_PATH=/var/www/atomcms/nitro-renderer
|
||||
NITRO_BUILD_PATH=/var/www/atomcms/nitro-client/dist
|
||||
NITRO_WEBROOT=/Client
|
||||
GAMEDATA_PATH=/var/Gamedata
|
||||
|
||||
# ==========================================
|
||||
# LOGGING
|
||||
# ==========================================
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# ==========================================
|
||||
# BROADCAST & FILESYSTEMS
|
||||
# ==========================================
|
||||
BROADCAST_DRIVER=log
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
# ==========================================
|
||||
# LOCALIZATION
|
||||
# ==========================================
|
||||
APP_LOCALE=nl
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=nl_NL
|
||||
|
||||
# ==========================================
|
||||
# CORS
|
||||
# ==========================================
|
||||
CORS_ALLOWED_ORIGINS=
|
||||
|
||||
# ==========================================
|
||||
# SECURITY SETTINGS
|
||||
# ==========================================
|
||||
APP_SECURE=true
|
||||
SESSION_DOMAIN=localhost
|
||||
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
|
||||
@@ -0,0 +1,95 @@
|
||||
APP_NAME="YourHotel"
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://yourhotel.nl
|
||||
|
||||
# --- LOGGING ---
|
||||
LOG_CHANNEL=daily
|
||||
LOG_MAX_FILES=14
|
||||
LOG_LEVEL=error
|
||||
|
||||
# --- DATABASE ---
|
||||
DB_CONNECTION=mariadb
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=habbo
|
||||
DB_USERNAME=cms
|
||||
DB_PASSWORD=your_db_password
|
||||
DB_STRICT_MODE=false
|
||||
DB_ENGINE=InnoDB
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
DB_DUMP_BINARY_PATH=/usr/bin/
|
||||
|
||||
# --- CACHE & SESSION (Redis recommended on Linux) ---
|
||||
BROADCAST_DRIVER=redis
|
||||
CACHE_STORE=redis
|
||||
CACHE_DRIVER=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=
|
||||
REDIS_PORT=6379
|
||||
|
||||
REDIS_DB=0
|
||||
REDIS_CACHE_DB=1
|
||||
REDIS_QUEUE_DB=2
|
||||
|
||||
REDIS_PERSISTENT=true
|
||||
REDIS_READ_TIMEOUT=1.0
|
||||
REDIS_CONNECT_TIMEOUT=1.0
|
||||
|
||||
# --- FILAMENT ---
|
||||
FILAMENT_FILESYSTEM_DISK=public
|
||||
FILAMENT_AUTH_GUARD=web
|
||||
|
||||
# --- MAIL ---
|
||||
MAIL_MAILER=log
|
||||
MAIL_FROM_ADDRESS="noreply@yourhotel.nl"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# --- SESSION SECURITY ---
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_DOMAIN=.yourhotel.nl
|
||||
SESSION_SECURE_COOKIE=true
|
||||
SESSION_HTTP_ONLY=true
|
||||
SESSION_SAME_SITE=lax
|
||||
SESSION_COOKIE=__secure_session
|
||||
|
||||
# --- SANCTUM ---
|
||||
SANCTUM_STATEFUL_DOMAINS=yourhotel.nl,www.yourhotel.nl,localhost:3000
|
||||
|
||||
# --- EMULATOR ---
|
||||
TURNSTILE_SITE_KEY=
|
||||
TURNSTILE_SECRET_KEY=
|
||||
RCON_HOST=127.0.0.1
|
||||
RCON_PORT=3001
|
||||
EMULATOR_IP=127.0.0.1
|
||||
EMULATOR_PORT=3000
|
||||
|
||||
# --- NITRO UPDATE (for update-Nitrov3.sh) ---
|
||||
NITRO_EMULATOR_PATH=/var/www/emulator
|
||||
NITRO_EMULATOR_SERVICE=emulator
|
||||
NITRO_DB_HOST=127.0.0.1
|
||||
NITRO_DB_PORT=3306
|
||||
NITRO_DB_NAME=habbo
|
||||
NITRO_DB_USER=root
|
||||
NITRO_DB_PASS=
|
||||
NITRO_SQL_DIR=/var/www/emulator/Database Updates
|
||||
NITRO_BACKUP_DIR=/var/www/emulator/Database Updates/backups
|
||||
NITRO_GAMEDATA_DIR=/var/www/Gamedata/config
|
||||
NITRO_CLIENT_DIR=/var/www/Nitro-V3/public/configuration
|
||||
NITRO_CLIENT_SRC=/var/www/Nitro-V3
|
||||
NITRO_RENDERER_SRC=/var/www/Nitro_Render_V3
|
||||
|
||||
# --- CORS ---
|
||||
CORS_ALLOWED_ORIGINS=https://yourhotel.nl,https://www.yourhotel.nl,http://localhost:3000
|
||||
|
||||
# --- LOCALIZATION ---
|
||||
APP_LOCALE=nl
|
||||
FORCE_HTTPS=true
|
||||
PASSWORD_RESET_TOKEN_TIME=15
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
APP_NAME="YourHotel"
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
|
||||
# --- LOGGING ---
|
||||
LOG_CHANNEL=daily
|
||||
LOG_MAX_FILES=14
|
||||
LOG_LEVEL=error
|
||||
|
||||
# --- DATABASE ---
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=habbo
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
DB_STRICT_MODE=false
|
||||
DB_ENGINE=InnoDB
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
|
||||
# --- CACHE & SESSION (File-based for Windows compat) ---
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_STORE=file
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
# --- MAIL ---
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS="noreply@yourhotel.nl"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# --- SESSION SECURITY ---
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_DOMAIN=localhost
|
||||
SESSION_SECURE_COOKIE=false
|
||||
SESSION_HTTP_ONLY=true
|
||||
SESSION_SAME_SITE=lax
|
||||
SESSION_COOKIE=__secure_session
|
||||
|
||||
# --- EMULATOR ---
|
||||
TURNSTILE_SITE_KEY=
|
||||
TURNSTILE_SECRET_KEY=
|
||||
RCON_HOST=127.0.0.1
|
||||
RCON_PORT=3001
|
||||
EMULATOR_IP=127.0.0.1
|
||||
EMULATOR_PORT=3000
|
||||
|
||||
# --- CORS ---
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:3000
|
||||
|
||||
# --- LOCALIZATION ---
|
||||
APP_LOCALE=nl
|
||||
FORCE_HTTPS=false
|
||||
PASSWORD_RESET_TOKEN_TIME=15
|
||||
|
||||
# --- NITRO PATHS (Windows) ---
|
||||
NITRO_CLIENT_PATH=C:\Nitro-V3
|
||||
NITRO_RENDERER_PATH=C:\Nitro_Render_V3
|
||||
NITRO_BUILD_PATH=C:\Nitro-V3\dist
|
||||
NITRO_WEBROOT=/Client
|
||||
GAMEDATA_PATH=C:\Gamedata
|
||||
@@ -1,64 +0,0 @@
|
||||
APP_NAME="Epicnabbo Hotel"
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:YOUR_APP_KEY_HERE
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://epicnabbo.nl
|
||||
|
||||
# --- LOGGING ---
|
||||
LOG_CHANNEL=daily
|
||||
LOG_MAX_FILES=14
|
||||
LOG_LEVEL=error
|
||||
|
||||
# --- DATABASE ---
|
||||
DB_CONNECTION=mariadb
|
||||
DB_HOST=YOUR_DB_HOST
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=YOUR_DB_NAME
|
||||
DB_USERNAME=YOUR_DB_USER
|
||||
DB_PASSWORD=YOUR_DB_PASSWORD
|
||||
DB_STRICT_MODE=true
|
||||
DB_ENGINE=InnoDB
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
DB_DUMP_BINARY_PATH=/usr/bin/
|
||||
|
||||
# --- REDIS TURBO ---
|
||||
BROADCAST_DRIVER=redis
|
||||
CACHE_STORE=redis
|
||||
CACHE_DRIVER=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
REDIS_DB=0
|
||||
REDIS_CACHE_DB=1
|
||||
REDIS_QUEUE_DB=2
|
||||
|
||||
REDIS_PERSISTENT=true
|
||||
REDIS_READ_TIMEOUT=1.0
|
||||
REDIS_CONNECT_TIMEOUT=1.0
|
||||
|
||||
# --- SESSION SECURITY ---
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_DOMAIN=.epicnabbo.nl
|
||||
SESSION_SECURE_COOKIE=true
|
||||
SESSION_HTTP_ONLY=true
|
||||
SESSION_SAME_SITE=lax
|
||||
SESSION_COOKIE=__epicnabbo_secure_session
|
||||
|
||||
# --- BEVEILIGING & EMULATOR ---
|
||||
TURNSTILE_SITE_KEY=YOUR_TURNSTILE_SITE_KEY
|
||||
TURNSTILE_SECRET_KEY=YOUR_TURNSTILE_SECRET_KEY
|
||||
RCON_HOST=127.0.0.1
|
||||
RCON_PORT=3001
|
||||
EMULATOR_IP=127.0.0.1
|
||||
EMULATOR_PORT=3000
|
||||
|
||||
# --- EXTRA HARDENING ---
|
||||
APP_LOCALE=nl
|
||||
FORCE_HTTPS=true
|
||||
PASSWORD_RESET_TOKEN_TIME=15
|
||||
@@ -1,87 +0,0 @@
|
||||
# Windows/XAMPP/IIS .env configuration
|
||||
# Copy this to .env and adjust the values for your Windows setup
|
||||
|
||||
# Application
|
||||
APP_NAME="Epicnabbo"
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:EXAMPLE_KEY_CHANGE_ME
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
# Database - Windows/XAMPP Settings
|
||||
# For XAMPP use: DB_HOST=localhost
|
||||
# For IIS use: DB_HOST=127.0.0.1 or localhost
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=habbo
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
# For Windows IIS with named pipes, use:
|
||||
# DB_CONNECTION=sqlsrv
|
||||
# DB_HOST=(local)
|
||||
# DB_DATABASE=habbo
|
||||
# DB_USERNAME=sa
|
||||
# DB_PASSWORD=your_password
|
||||
|
||||
# Session (Windows compatible)
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
# Cache (Windows compatible)
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
# Mail (Windows compatible - use Mailtrap for testing)
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS="noreply@epicnabbo.nl"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# Pusher (for real-time features)
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
# Cloudflare Turnstile (optional)
|
||||
TURNSTILE_SITE_KEY=
|
||||
TURNSTILE_SECRET_KEY=
|
||||
|
||||
# Google Recaptcha (optional)
|
||||
NOCAPTCHA_SECRET=
|
||||
NOCAPTCHA_SITEKEY=
|
||||
|
||||
# Discord Webhook (optional)
|
||||
DISCORD_WEBHOOK_URL=
|
||||
|
||||
# Nitro Client Path (Windows paths use backslashes or forward slashes)
|
||||
NITRO_CLIENT_PATH=C:\path\to\nitro-client
|
||||
NITRO_RENDERER_PATH=C:\path\to\nitro-renderer
|
||||
|
||||
# Logging
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# Broadcast & Filesystems (Windows compatible)
|
||||
BROADCAST_DRIVER=log
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
# Advanced (usually no changes needed)
|
||||
APP_LOCALE=nl
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_LOCALE=nl
|
||||
APP_FAKER_LOCALE=nl_NL
|
||||
|
||||
# Security
|
||||
APP_SECURE=true
|
||||
SESSION_DOMAIN=localhost
|
||||
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
|
||||
@@ -1,42 +1,49 @@
|
||||
# Negeer wachtwoorden en database-instellingen
|
||||
# --- Environment ---
|
||||
.env
|
||||
.env.backup
|
||||
.env.testing
|
||||
config.php
|
||||
wp-config.php
|
||||
/uploads/
|
||||
/temp/
|
||||
*.log
|
||||
.env.production
|
||||
|
||||
# Geen zware afhankelijkheden pushen
|
||||
# --- Dependencies ---
|
||||
node_modules/
|
||||
vendor/
|
||||
|
||||
# UITZONDERING: Vertaalbestanden in lang/vendor wel pushen
|
||||
/lang/vendor/
|
||||
# --- Build artifacts (hashed filenames) ---
|
||||
public/build/assets/
|
||||
public/build/hot
|
||||
|
||||
# Systeembestanden (Mac/Windows troep)
|
||||
# --- Storage (uploaded files, cache, logs, sessions) ---
|
||||
storage/app/livewire-tmp/
|
||||
storage/app/public/
|
||||
storage/clockwork/
|
||||
storage/debugbar/
|
||||
storage/framework/views/
|
||||
storage/framework/cache/
|
||||
storage/framework/sessions/
|
||||
storage/framework/testing/
|
||||
storage/logs/
|
||||
|
||||
# --- IDE & OS ---
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# VPS automation scripts (niet pushen naar GitLab)
|
||||
watch.sh
|
||||
check-updates.sh
|
||||
|
||||
# Cache bestanden (niet pushen naar GitLab)
|
||||
/storage/framework/views/
|
||||
/storage/framework/cache/
|
||||
/storage/framework/sessions/
|
||||
/storage/logs/
|
||||
/storage/debugbar/rr
|
||||
# --- Config (no default .env push) ---
|
||||
config.php
|
||||
.rr.yaml
|
||||
|
||||
# Lockfiles (kies 1 package manager)
|
||||
package-lock.json
|
||||
|
||||
# Overgebleven test/temp bestanden
|
||||
# --- Tests & Temp ---
|
||||
ci_test.txt
|
||||
cookies.txt
|
||||
.phpunit.result.cache
|
||||
|
||||
# GitHub workflows (pushen naar GitLab)
|
||||
!/.github/workflows/
|
||||
# --- Lock files (yarn.lock is tracked, package-lock.json is not) ---
|
||||
package-lock.json
|
||||
|
||||
# --- Scripts (local-only) ---
|
||||
watch.sh
|
||||
check-updates.sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright 2023 ObjectRetros
|
||||
Copyright 2026 Remco (Epicnabbo)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,537 +1,329 @@
|
||||
# AtomCMS — Remco Epicnabbo Edition
|
||||
|
||||
<div align="center">
|
||||
<img src="https://i.imgur.com/9ePNdJ4.png" alt="Atom CMS" width="200"/>
|
||||
[](https://discord.gg/pP6HyZedAj)
|
||||
[](https://laravel.com)
|
||||
[](https://php.net)
|
||||
[](#)
|
||||
|
||||
### The Ultimate Retro Hotel CMS
|
||||
A modern Habbo retro CMS powered by Laravel 13, Filament 5, React 19, and Nitro. Forked and maintained by Remco (Epicnabbo).
|
||||
|
||||
**Modern • Fast • Self-Repairing**
|
||||
---
|
||||
|
||||
[](https://discord.gg/pP6HyZedAj)
|
||||
[](https://laravel.com)
|
||||
[](https://php.net)
|
||||
[](https://github.com/atom-retros/atomcms)
|
||||
## What's New in V3
|
||||
|
||||
</div>
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Commandocentrum** | Central admin dashboard with Nitro, emulator & hotel monitoring |
|
||||
| **Nitro V3 Update System** | Auto-update emulator, Nitro client & renderer via CLI (Linux `.env`) |
|
||||
| **Configurable Paths** | 13 paths fully adjustable via `.env` (no database needed) |
|
||||
| **Emulator Control** | Start, stop, restart & check status from the admin panel |
|
||||
| **Live Monitoring** | Online users, emulator status, DB status, server load, diagnostics |
|
||||
| **Hotel Alerts** | Send messages to all online users in real-time |
|
||||
| **Emulator Log Viewer** | Live logs directly in the browser |
|
||||
| **Clothing Sync** | Sync catalog clothing from FigureMap with one click |
|
||||
| **Social Login** | OAuth login via Google, Discord & GitHub |
|
||||
| **Notification Settings** | Email & Discord webhook alerts with rank filtering |
|
||||
| **Staff Activity Log** | Full audit trail of all housekeeping actions |
|
||||
| **Bulletproof Installation** | 12-step guide for Ubuntu 26.04 with Redis, SSL, firewall & PHP tuning |
|
||||
| **PHP 8.5 + Ubuntu 26.04** | Fully compatible with the latest PHP and Ubuntu LTS |
|
||||
| **Dual .env System** | Separate configs for Linux (Redis) and Windows (file-based) |
|
||||
| **XAMPP Blocked** | Explicitly unsupported — we prioritise security |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://git.your-server.com/remco/Atomcms-edit.git /var/www/atomcms
|
||||
cd /var/www/atomcms
|
||||
cp .env.example.linux .env
|
||||
php artisan key:generate
|
||||
# Edit .env with your DB credentials, then:
|
||||
composer install --no-dev --optimize-autoloader
|
||||
php artisan migrate --seed
|
||||
yarn install && yarn build:all
|
||||
```
|
||||
|
||||
> **Full installation guide** → `docs/INSTALL.md` or scroll down to [Installation](#installation-ubuntu-2604)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Radio Station
|
||||
- DJ applications & rank system
|
||||
- Live DJ sessions with schedule
|
||||
- Song requests & voting
|
||||
- Shoutbox
|
||||
- Listener points & leaderboard
|
||||
- Contests & giveaways
|
||||
- Radio banners & history
|
||||
| Module | What it does |
|
||||
|--------|-------------|
|
||||
| **Commandocentrum** | Nitro V3 one-click updater, emulator start/stop/restart, hotel alerts, live monitoring, log viewer, clothing sync, social login (Google/Discord/GitHub) |
|
||||
| **Radio** | DJ apps, live sessions, song requests, shoutbox, leaderboard, contests |
|
||||
| **Shop** | Product catalog, virtual currency, vouchers, PayPal |
|
||||
| **Community** | Articles, photo gallery, leaderboard, teams, rare values, badge lottery |
|
||||
| **Users** | Public profiles, 2FA, referrals, session logs |
|
||||
| **Help** | Ticket system, FAQ, rules |
|
||||
| **Filament Admin** | Users, bans, radio, shop, articles, emulator settings/texts/catalog, chatlogs, word filters, permissions, navigation |
|
||||
| **Themes** | Atom (light) & Dusk (dark) |
|
||||
|
||||
### Shop
|
||||
- Product catalog with categories
|
||||
- Virtual currency purchases
|
||||
- Voucher/promo codes
|
||||
- PayPal integration
|
||||
- Order history
|
||||
---
|
||||
|
||||
### Community
|
||||
- Articles with comments, reactions & tags
|
||||
- Photo gallery
|
||||
- Leaderboard
|
||||
- Teams & team applications
|
||||
- Staff page & staff applications
|
||||
- Rare values tracker
|
||||
- Badge draw/lottery system
|
||||
## Nitro V3 Update (Linux-only)
|
||||
|
||||
### Hotel Client
|
||||
- Nitro (HTML5) client support
|
||||
- Flash client support
|
||||
- FindRetros integration
|
||||
- VPN/proxy checker
|
||||
> ⚠️ **CLI only.** The web UI button has been removed. The script is configured via `.env` variables.
|
||||
|
||||
### User System
|
||||
- Public profiles with guestbook
|
||||
- Two-Factor Authentication (2FA)
|
||||
- Referral system with rewards
|
||||
- Account & password settings
|
||||
- Session logs
|
||||
**What it does:** `git pull` emulator → DB backup → SQL imports → Maven build → `git pull` Nitro_Render_V3 + Nitro-V3 → `yarn build` → sync Gamedata → cleanup → restart emulator.
|
||||
|
||||
### Help Center
|
||||
- Support ticket system with replies & status management
|
||||
- Website rules
|
||||
- FAQ with categories
|
||||
**Usage:**
|
||||
|
||||
### Themes
|
||||
- **Atom** — Default light theme
|
||||
- **Dusk** — Dark theme
|
||||
```bash
|
||||
# Make sure .env contains all NITRO_* variables (see .env.example.linux)
|
||||
cd /var/www/atomcms
|
||||
bash update-Nitrov3.sh
|
||||
```
|
||||
|
||||
### Filament Admin Panel
|
||||
- User & ban management
|
||||
- Radio management (applications, schedules, ranks, shouts, history, banners)
|
||||
- Shop & order management with charts
|
||||
- Article & tag management
|
||||
- Emulator settings, texts & catalog editors
|
||||
- Chatlog viewer (rooms & private messages)
|
||||
- Word filters & moderation tools
|
||||
- Housekeeping permissions
|
||||
- Website navigation & settings
|
||||
- Camera/photo management
|
||||
**Configurable via `.env`:**
|
||||
|
||||
### API Endpoints
|
||||
- `GET /api/user/{username}` — Fetch user data
|
||||
- `GET /api/online-users` — Online users list
|
||||
- `GET /api/online-count` — Online user count
|
||||
- `GET /api/radio/current-dj` — Current DJ info
|
||||
- `GET /api/radio/now-playing` — Current song
|
||||
- `GET /api/radio/listeners` — Listener count
|
||||
- `GET /api/radio/shouts` — Recent shouts
|
||||
- `GET /api/radio/points` — Points data
|
||||
- `GET /api/radio/points/leaderboard` — Points leaderboard
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `NITRO_EMULATOR_PATH` | `/var/www/emulator` | Emulator root directory |
|
||||
| `NITRO_EMULATOR_SERVICE` | `emulator` | Systemd service name |
|
||||
| `NITRO_DB_HOST` | `127.0.0.1` | Database host |
|
||||
| `NITRO_DB_PORT` | `3306` | Database port |
|
||||
| `NITRO_DB_NAME` | `habbo` | Database name |
|
||||
| `NITRO_DB_USER` | `root` | Database user |
|
||||
| `NITRO_DB_PASS` | — | Database password |
|
||||
| `NITRO_SQL_DIR` | `{emulator}/Database Updates` | SQL updates directory |
|
||||
| `NITRO_BACKUP_DIR` | `{emulator}/Database Updates/backups` | Backup directory |
|
||||
| `NITRO_GAMEDATA_DIR` | `/var/www/Gamedata/config` | Gamedata config directory |
|
||||
| `NITRO_CLIENT_DIR` | `{nitro}/public/configuration` | Nitro client config directory |
|
||||
| `NITRO_CLIENT_SRC` | `/var/www/Nitro-V3` | Nitro-V3 source directory |
|
||||
| `NITRO_RENDERER_SRC` | `/var/www/Nitro_Render_V3` | Nitro Render V3 source directory |
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
| Component | Requirement |
|
||||
| ------------- | ------------------------------- |
|
||||
| **PHP** | 8.1 or higher |
|
||||
| **Database** | MariaDB 10.6+ or MySQL 8.0+ |
|
||||
| **Web Server**| Apache (mod_rewrite) or Nginx |
|
||||
| **Node.js** | 20 or higher |
|
||||
| **Yarn** | 1.22+ or 4.x (Berry) |
|
||||
| **Composer** | 2.x |
|
||||
| Component | Version |
|
||||
|-----------|---------|
|
||||
| **PHP** | 8.5+ |
|
||||
| **Database** | MariaDB 10.6+ or MySQL 8.0+ |
|
||||
| **Web Server** | Nginx or Apache |
|
||||
| **Node.js** | 20+ |
|
||||
| **Yarn** | 1.22+ |
|
||||
| **Composer** | 2.x |
|
||||
| **Redis** | Recommended (Linux) |
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
## Environment Files
|
||||
|
||||
### Linux (Ubuntu / Debian)
|
||||
| File | Use | Cache | DB |
|
||||
|------|-----|-------|----|
|
||||
| `docs/INSTALL.md` | Step-by-step setup guide | — | — |
|
||||
| `.env.example.linux` | Linux production | Redis | MariaDB |
|
||||
| `.env.example.windows` | Windows development | File | MySQL |
|
||||
|
||||
```bash
|
||||
cp .env.example.linux .env
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
> ⚠️ **XAMPP is not supported.** Extremely unsafe for production.
|
||||
|
||||
---
|
||||
|
||||
## Installation (Ubuntu 26.04)
|
||||
|
||||
```bash
|
||||
# 1. System dependencies
|
||||
sudo apt update
|
||||
sudo apt install -y git curl wget unzip nginx mariadb-server \
|
||||
php8.3 php8.3-cli php8.3-fpm php8.3-mysql php8.3-xml \
|
||||
php8.3-mbstring php8.3-curl php8.3-zip php8.3-bcmath \
|
||||
php8.3-gd php8.3-sockets php8.3-intl
|
||||
sudo apt install -y git curl wget unzip nginx mariadb-server redis-server \
|
||||
php8.5 php8.5-{cli,fpm,mysql,xml,mbstring,curl,zip,bcmath,gd,sockets,intl} \
|
||||
build-essential
|
||||
|
||||
# 2. Install Composer
|
||||
# 2. Composer
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# 3. Install Node.js & Yarn
|
||||
# 3. Node.js + Yarn
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
sudo corepack enable
|
||||
corepack install -g yarn@latest
|
||||
|
||||
# 4. Clone the project
|
||||
git clone https://gitlab.epicnabbo.nl/RemcoEpic/atomcms-edit.git /var/www/atomcms
|
||||
# 4. Secure MariaDB
|
||||
sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'your_root_password'; FLUSH PRIVILEGES;"
|
||||
|
||||
# 5. Clone
|
||||
git clone https://git.your-server.com/remco/Atomcms-edit.git /var/www/atomcms
|
||||
cd /var/www/atomcms
|
||||
|
||||
# 5. Configure environment
|
||||
cp .env.example .env
|
||||
# 6. Configure
|
||||
cp .env.example.linux .env
|
||||
# EDIT .env first: set DB_PASSWORD, APP_URL, SESSION_DOMAIN
|
||||
nano .env
|
||||
php artisan key:generate
|
||||
# Edit .env with your database credentials:
|
||||
# DB_DATABASE=habbo
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=yourpassword
|
||||
|
||||
# 6. Install PHP dependencies
|
||||
# 7. Create database + user
|
||||
sudo mysql -e "CREATE DATABASE IF NOT EXISTS habbo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
sudo mysql -e "CREATE USER IF NOT EXISTS 'cms'@'localhost' IDENTIFIED BY 'your_db_password';"
|
||||
sudo mysql -e "GRANT ALL ON habbo.* TO 'cms'@'localhost'; FLUSH PRIVILEGES;"
|
||||
|
||||
# 8. Install PHP & JS deps
|
||||
composer install --no-dev --optimize-autoloader
|
||||
|
||||
# 7. Install frontend dependencies
|
||||
yarn install
|
||||
|
||||
# 8. Create database
|
||||
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS habbo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
|
||||
# 9. Run migrations & seeders (or use the auto-repair tool)
|
||||
# 9. Migrate, seed & cache
|
||||
php artisan migrate --seed
|
||||
# Or use the interactive repair tool:
|
||||
# php artisan atom:check --fix
|
||||
php artisan optimize
|
||||
php artisan filament:optimize
|
||||
|
||||
# 10. Build frontend assets
|
||||
# 10. Build frontend
|
||||
yarn build:all
|
||||
|
||||
# 11. Set permissions
|
||||
# 11. Permissions
|
||||
sudo chown -R www-data:www-data storage bootstrap/cache public/build
|
||||
sudo chmod -R 775 storage bootstrap/cache
|
||||
|
||||
# 12. Configure web server
|
||||
# 12. Sudoers (for update-Nitrov3.sh — sudo chown + systemctl)
|
||||
sudo tee /etc/sudoers.d/www-data << 'EOF'
|
||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart emulator
|
||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl status emulator
|
||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/chown -R www-data\:www-data /var/www/*
|
||||
EOF
|
||||
sudo chmod 440 /etc/sudoers.d/www-data
|
||||
|
||||
# --- Nginx ---
|
||||
sudo nano /etc/nginx/sites-available/atomcms
|
||||
# 13. Start services
|
||||
sudo systemctl enable --now redis-server
|
||||
|
||||
# 14. PHP tuning
|
||||
sudo sed -i 's/upload_max_filesize = .*/upload_max_filesize = 64M/' /etc/php/8.5/fpm/php.ini
|
||||
sudo sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php/8.5/fpm/php.ini
|
||||
sudo sed -i 's/memory_limit = .*/memory_limit = 256M/' /etc/php/8.5/fpm/php.ini
|
||||
sudo sed -i 's/max_execution_time = .*/max_execution_time = 300/' /etc/php/8.5/fpm/php.ini
|
||||
|
||||
# 16. Restart & verify
|
||||
sudo systemctl restart php8.5-fpm redis-server nginx
|
||||
php artisan about # should show green "Application" line
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
root /var/www/atomcms/public;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
error_page 404 /index.php;
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml;
|
||||
gzip_vary on;
|
||||
|
||||
location / { try_files $uri $uri/ /index.php?$query_string; }
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
|
||||
fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
location ~ /\.(?!well-known).* { deny all; }
|
||||
location ~ /(\.env|\.git|composer\.(json|lock)) { deny all; }
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo ln -sf /etc/nginx/sites-available/atomcms /etc/nginx/sites-enabled/
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
# --- Apache ---
|
||||
sudo a2enmod rewrite
|
||||
sudo nano /etc/apache2/sites-available/atomcms.conf
|
||||
sudo systemctl restart php8.5-fpm redis-server
|
||||
sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw --force enable
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName your-domain.com
|
||||
DocumentRoot /var/www/atomcms/public
|
||||
|
||||
<Directory /var/www/atomcms/public>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
### SSL (recommended)
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/atomcms-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/atomcms-access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
```bash
|
||||
sudo a2ensite atomcms
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
# 13. Restart PHP-FPM to clear opcache
|
||||
sudo systemctl restart php8.3-fpm
|
||||
|
||||
# 14. Visit http://your-domain.com in your browser
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Windows (XAMPP / WampServer)
|
||||
|
||||
#### Prerequisites
|
||||
- [XAMPP](https://www.apachefriends.org/) (PHP 8.1+, Apache, MariaDB) or [WampServer](https://www.wampserver.com/)
|
||||
- [Node.js](https://nodejs.org/) (20 LTS or higher)
|
||||
- [Yarn](https://yarnpkg.com/getting-started/install) (`npm install -g yarn` after Node.js)
|
||||
- [Composer](https://getcomposer.org/download/) (Windows installer)
|
||||
- [Git for Windows](https://git-scm.com/download/win)
|
||||
|
||||
#### Steps
|
||||
|
||||
```powershell
|
||||
# 1. Open PowerShell or CMD as Administrator
|
||||
|
||||
# 2. Clone the project
|
||||
git clone https://gitlab.epicnabbo.nl/RemcoEpic/atomcms-edit.git C:\xampp\htdocs\atomcms
|
||||
cd C:\xampp\htdocs\atomcms
|
||||
|
||||
# 3. Configure environment
|
||||
copy .env.example .env
|
||||
php artisan key:generate
|
||||
|
||||
# Edit .env with your XAMPP database credentials:
|
||||
# DB_CONNECTION=mariadb
|
||||
# DB_HOST=localhost
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=habbo
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
# 4. Start XAMPP services
|
||||
# Open XAMPP Control Panel → Start Apache & MySQL
|
||||
|
||||
# 5. Create database
|
||||
# Open phpMyAdmin (http://localhost/phpmyadmin)
|
||||
# → New → Database name: habbo → Charset: utf8mb4_unicode_ci → Create
|
||||
|
||||
# 6. Install PHP dependencies
|
||||
composer install --no-dev --optimize-autoloader
|
||||
|
||||
# 7. Install frontend dependencies
|
||||
yarn install
|
||||
|
||||
# 8. Run migrations & seeders
|
||||
php artisan migrate --seed
|
||||
# Or use the interactive repair tool:
|
||||
# php artisan atom:check --fix
|
||||
|
||||
# 9. Build frontend assets
|
||||
yarn build:all
|
||||
|
||||
# 10. Set permissions (required for storage & cache)
|
||||
# Right-click storage\ and bootstrap\cache\ folders
|
||||
# → Properties → Security → Edit → Add "Everyone" → Full Control
|
||||
# Or run in PowerShell as Admin:
|
||||
icacls storage /grant Everyone:F /T
|
||||
icacls bootstrap/cache /grant Everyone:F /T
|
||||
icacls public/build /grant Everyone:F /T
|
||||
|
||||
# 11. Enable Apache mod_rewrite
|
||||
# Open XAMPP Control Panel → Apache → Config → httpd.conf
|
||||
# Uncomment: LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
# 12. Visit http://localhost/atomcms/public in your browser
|
||||
```
|
||||
|
||||
> **Note:** If you use IIS on Windows, the repair tool supports auto-detection:
|
||||
> `php artisan atom:check --fix`
|
||||
|
||||
---
|
||||
|
||||
## Quick Start (Repair Tool)
|
||||
## Yarn Scripts
|
||||
|
||||
```bash
|
||||
# Interactive mode (asks for confirmation before each step)
|
||||
php artisan atom:check --fix
|
||||
|
||||
# Auto mode (fixes everything automatically without asking)
|
||||
php artisan atom:check --auto
|
||||
|
||||
# Force platform detection
|
||||
php artisan atom:check --platform=nginx
|
||||
|
||||
# Dutch language output
|
||||
php artisan atom:check --fix --lang=nl
|
||||
yarn build:all # Build all themes
|
||||
yarn build:atom # Atom theme only
|
||||
yarn build:dusk # Dusk theme only
|
||||
yarn dev # Vite dev server
|
||||
yarn lint # Lint JS/Vue
|
||||
yarn format # Format code
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Check only (no changes)
|
||||
php artisan atom:check
|
||||
|
||||
# Interactive fix (asks before each step)
|
||||
php artisan atom:check --fix
|
||||
|
||||
# Automatic fix (no questions)
|
||||
php artisan atom:check --auto
|
||||
|
||||
# Fix gamedata symlinks for bundled assets
|
||||
php artisan atom:fix-gamedata-symlinks
|
||||
|
||||
# Preview symlinks (dry-run)
|
||||
php artisan atom:fix-gamedata-symlinks --dry-run
|
||||
```
|
||||
|
||||
### Yarn Scripts
|
||||
|
||||
```bash
|
||||
# Build all themes
|
||||
yarn build:all
|
||||
|
||||
# Build single theme
|
||||
yarn build:atom # Atom theme
|
||||
yarn build:dusk # Dusk theme
|
||||
|
||||
# Development
|
||||
yarn dev # Start Vite dev server
|
||||
yarn dev:atom # Dev server for Atom theme
|
||||
|
||||
# Linting & Formatting
|
||||
yarn lint # Check JS/Vue
|
||||
yarn lint:fix # Fix JS/Vue
|
||||
yarn lint:css # Check CSS
|
||||
yarn lint:css:fix # Fix CSS
|
||||
yarn format # Format everything
|
||||
yarn format:check # Check formatting
|
||||
|
||||
# Full check
|
||||
yarn check # Lint + format check
|
||||
yarn check:php # PHP syntax check
|
||||
yarn check:security # Composer & npm audit
|
||||
yarn check:deps # Check outdated packages
|
||||
|
||||
# Cache
|
||||
yarn clean # Clear Vite cache
|
||||
yarn rebuild # Clean + install + build
|
||||
```
|
||||
|
||||
### PHP Commands
|
||||
|
||||
```bash
|
||||
# Auto-fix code style
|
||||
php artisan atom:fix-code
|
||||
|
||||
# Static analysis (PHPStan Level 3)
|
||||
./vendor/bin/phpstan analyse
|
||||
|
||||
# Build frontend assets
|
||||
yarn build:atom
|
||||
yarn build:dusk
|
||||
|
||||
# Development mode
|
||||
yarn dev:atom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repair Tool Details
|
||||
|
||||
The `atom:check` command performs over **100 deep system checks** and offers to fix them automatically.
|
||||
|
||||
### Diagnostic Checks
|
||||
|
||||
| Section | What it Checks |
|
||||
| ------------- | ------------------------------------------------------------- |
|
||||
| **Environment** | .env, APP_KEY, Debug mode, Composer security |
|
||||
| **Database** | Tables, columns, migrations, seeders, settings, radio, admin |
|
||||
| **PHP Stack** | Extensions, php.ini, config cache, session |
|
||||
| **Web Server**| Apache/Nginx/IIS config, SSL |
|
||||
| **System** | Permissions, firewall ports |
|
||||
| **Assets** | Frontend, Redis, Cron, Queue, Supervisor, Vite |
|
||||
| **HTTP Errors** | 400, 401, 403, 404, 419, 429, 500, 502, 503, 504 |
|
||||
|
||||
### Auto-Fix Steps
|
||||
|
||||
| Step | Action |
|
||||
| ---- | ---------------------------------- |
|
||||
| 1 | Environment (.env, APP_KEY) |
|
||||
| 2 | Clear all caches |
|
||||
| 3 | Fix permissions |
|
||||
| 4 | Run migrations |
|
||||
| 5 | Run seeders |
|
||||
| 6 | Fix storage (symlink, directories) |
|
||||
| 6b | Fix Radio tables |
|
||||
| 7 | Fix Gamedata symlinks |
|
||||
| 8 | Create admin user |
|
||||
| 9 | Web server config (.htaccess) |
|
||||
| 10 | PHP config & extensions |
|
||||
| 11 | Build assets (npm) |
|
||||
| 12 | Fix HTTP errors |
|
||||
|
||||
Auto-detects: **Linux, Windows IIS, XAMPP, WAMP, Apache, Nginx**
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 500 Error After `php artisan optimize`
|
||||
|
||||
If you get a 500 error after running `php artisan optimize`, PHP OPcache is likely caching stale files. Fix:
|
||||
|
||||
```bash
|
||||
# Disable OPcache in your FPM configuration:
|
||||
# Edit /etc/php/8.x/fpm/conf.d/99-opcache.ini
|
||||
opcache.enable=0
|
||||
|
||||
# Then restart PHP-FPM:
|
||||
sudo systemctl restart php8.x-fpm
|
||||
```
|
||||
|
||||
Alternatively, keep OPcache enabled but restart PHP-FPM after every `php artisan optimize`:
|
||||
|
||||
```bash
|
||||
php artisan optimize
|
||||
sudo systemctl restart php8.x-fpm
|
||||
```
|
||||
|
||||
### Filament Translations Not Working
|
||||
|
||||
If Filament navigation labels or resource names aren't translating (e.g., Dashboard shows in English instead of Dutch):
|
||||
|
||||
1. Add the missing key to `lang/vendor/filament/{locale}/resources.php` (navigations section) or `lang/{locale}.json`
|
||||
2. Clear the cache: `php artisan optimize:clear` (then restart PHP-FPM if OPcache is on)
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
| Optimization | Description |
|
||||
| ------------------- | -------------------------------- |
|
||||
| **Vite 8** | Fastest build tool |
|
||||
| **esbuild** | Faster minification |
|
||||
| **Better chunking** | Optimal code splitting |
|
||||
| **Gzip + Brotli** | Compression (~70% smaller) |
|
||||
| **Resource hints** | DNS prefetch, preconnect, preload|
|
||||
| **HTTP/2** | Faster network requests |
|
||||
| **Console removed** | Smaller JS bundles |
|
||||
| **Caching** | Vite cache + optimizedeps |
|
||||
|
||||
### Gamedata Symlinks
|
||||
|
||||
The `atom:fix-gamedata-symlinks` command creates symlinks in the Gamedata directory to point to optimized bundled assets, significantly improving game client load times.
|
||||
|
||||
| Symlink | Target | Purpose |
|
||||
| ---------------- | ------------------- | ---------------- |
|
||||
| `effect` | `bundled/effect` | Avatar effects |
|
||||
| `furniture` | `bundled/furniture` | Furniture assets |
|
||||
| `generic` | `bundled/generic` | Generic assets |
|
||||
| `pet` | `bundled/pet` | Pet assets |
|
||||
| `figure` | `bundled/figure` | Avatar figures |
|
||||
| `generic_custom` | `bundled/generic` | Custom generic |
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Backend:** Laravel 13
|
||||
- **Frontend:** React 19 + Alpine.js
|
||||
- **Build:** Vite 8
|
||||
- **CSS:** TailwindCSS 4
|
||||
- **Admin Panel:** Filament 5
|
||||
- **Database:** MariaDB / MySQL
|
||||
- **Linting:** ESLint + Stylelint + Prettier
|
||||
**Laravel 13 · React 19 + Alpine.js · Vite 8 · TailwindCSS 4 · Filament 5 · MariaDB/MySQL · Redis**
|
||||
|
||||
---
|
||||
|
||||
## Changelog (May 27, 2026)
|
||||
## Security
|
||||
|
||||
- **Added OPcache troubleshooting** — 500 error after `php artisan optimize` fix in README
|
||||
- **Fixed Dashboard navigation translation** — added `Dashboard` key to Dutch Filament `resources.php`
|
||||
- **Fixed Homepage navigation label** — added `Homepage` translation to `nl.json`
|
||||
- **Disabled OPcache in FPM** — `99-opcache.ini` set to `opcache.enable=0` to prevent stale cache issues
|
||||
AtomCMS is built with security as a priority. Below is what's in place and what you need to configure.
|
||||
|
||||
## Changelog (May 26, 2026)
|
||||
### ✅ Already locked down
|
||||
|
||||
- **Removed auto-recovery** — caused race conditions by running `view:clear` during live traffic, fixed `filemtime(): stat failed` errors
|
||||
- **Fixed debug banner** — now uses `config('app.debug')` with proper `(bool)` cast, no more false positives
|
||||
- **Added `(bool)` casts** to all `env()` calls with boolean defaults across 6 config files (habbo, activitylog, database, filesystems, log-viewer, session)
|
||||
- **Removed dangerous public scripts** — `check_icons.php` and `test_open_basedir.php` (public Laravel bootstrap + DB queries)
|
||||
- **Removed root `index.php`** — duplicate front controller, unsafe if docroot misconfigured
|
||||
- **Cleaned Clockwork debug data** — 42 JSON files with SQL queries, tokens, and paths
|
||||
- **Hardened `.htaccess`** — block `.env`/`.git`/`composer.json` access + security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
|
||||
- **Fixed `robots.txt`** — blocks crawlers from `/admin`, `/filament`, `/log-viewer`
|
||||
- **Disabled Log Viewer by default** — no longer accessible without explicit config
|
||||
- **Disabled Boost browser logs watcher** — stopped logging JS errors from every visitor to disk
|
||||
- **Fixed `REDIS_PASSWORD`** — was literal string `"null"`, now empty
|
||||
- **Fixed Session `same_site`** — now reads from `.env` instead of being hardcoded
|
||||
- **Fixed non-existent model import** — `App\Models\Article` didn't exist, now aliased to `WebsiteArticle`
|
||||
- **Removed unused traits** — `HasNotificationUrl` and `HasCommonScopes` (dead code)
|
||||
- **Restricted CORS headers** — from wildcard `['*']` to specific allowed list
|
||||
- **Rebuilt all caches** — config, views, routes, opcache reset, PHP-FPM restart
|
||||
| Measure | Details |
|
||||
|---------|---------|
|
||||
| **Mass assignment protection** | User model restricted to 21 fillable fields (sensitive fields like `rank`, `credits`, `online` require explicit `forceFill`) |
|
||||
| **API authentication** | Sanctum tokens, Bearer-only (no query-string API keys accepted) |
|
||||
| **PayPal credentials** | Loaded from `env()`, never hardcoded |
|
||||
| **CORS** | Must be explicitly set via `CORS_ALLOWED_ORIGINS` env (no wildcard default) |
|
||||
| **Debug mode** | `APP_DEBUG=false` by default |
|
||||
| **PHP debugging** | No `dd()`, `dump()`, or `var_dump()` in production code |
|
||||
| **Password flashing** | Exception handler excludes passwords from session flash |
|
||||
| **File uploads** | MIME validation (Laravel `image` rule + `finfo` on logos) |
|
||||
| **2FA** | Two-factor authentication available |
|
||||
| **SQL injection** | All queries use parameterized binding or Eloquent ORM |
|
||||
| **Command injection** | All `exec()`/`shell_exec()` calls use `escapeshellarg()` or hardcoded values |
|
||||
| **CSRF** | Sanctum CSRF protection on all stateful routes |
|
||||
| **Insecure deserialization** | No `unserialize()` calls exist |
|
||||
|
||||
### ⚠️ You must configure
|
||||
|
||||
| Item | What to do |
|
||||
|------|------------|
|
||||
| **`.env` file** | Restrict file permissions (`chmod 600 .env`), ensure Nginx blocks access (already in the provided config) |
|
||||
| **`CORS_ALLOWED_ORIGINS`** | Set to your exact frontend domain(s) in `.env` (included in the example files) |
|
||||
| **Database password** | Use a strong, unique password (not `your_db_password`) |
|
||||
| **APP_KEY** | Run `php artisan key:generate` after cloning |
|
||||
| **Session domain** | Set `SESSION_DOMAIN` to your hotel domain in `.env` |
|
||||
| **SSL** | Required for production — use the Certbot instructions above |
|
||||
| **Admin accounts** | Only grant high-rank access to trusted users |
|
||||
| **Log retention** | Check `LOG_MAX_FILES` in `.env` (default 14 days) |
|
||||
|
||||
### 🔒 Sudoers safety
|
||||
|
||||
The `sudoers.d/www-data` configuration grants passwordless `systemctl` and `chown` to `www-data`. This is **safe by design**:
|
||||
|
||||
- Each command is pinned to a specific binary path (`/usr/bin/systemctl`, `/usr/bin/chown`)
|
||||
- `chown` is restricted to `/var/www/*`
|
||||
- No shell (`/bin/sh`, `/bin/bash`) is granted
|
||||
- No arbitrary binaries can be executed
|
||||
- In a worst-case web compromise, the attacker still cannot read `/etc/shadow`, install packages, or run arbitrary commands
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
- **Discord:** [Join our server](https://discord.gg/pP6HyZedAj)
|
||||
- **Issues:** Report bugs via the project issue tracker
|
||||
- **Contributions:** Fork & submit merge requests — all help is welcome!
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
- **Remco (Epicnabbo)** — Core Maintainer, System Architecture
|
||||
- **Kasja** — Design & Themes
|
||||
- **Kani** — RCON & API
|
||||
- **Atom Community** — Testing & Feedback
|
||||
**Remco (Epicnabbo)** — Core Maintainer · **Kasja** — Design & Themes · **Kani** — RCON & API · **Atom Community** — Testing & Feedback
|
||||
|
||||
<div align="center">
|
||||
<i>Made with love for the Retro Community</i>
|
||||
</div>
|
||||
<div align="center"><i>Made with love for the Retro Community</i></div>
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
# AtomCMS (epicnabbo edit) - v1.0 Ultimate Setup Guide
|
||||
|
||||
**AtomCMS** is a powerful Habbo retro CMS. This guide ensures a **100% working installation** on **any system**: Linux (Nginx/Apache), Windows (IIS/XAMPP), and macOS.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
git clone ssh://git@localhost:8422/RemcoEpic/atomcms-edit.git
|
||||
cd atomcms-edit
|
||||
composer install --no-dev
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
# Configure your .env (DB settings below)
|
||||
php artisan migrate --seed
|
||||
php artisan storage:link
|
||||
php artisan serve
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 System Requirements
|
||||
|
||||
### Universal Requirements
|
||||
|
||||
- **PHP**: 8.1, 8.2, or 8.3 (8.2 Recommended)
|
||||
- **Extensions**: `pdo_mysql`, `curl`, `mbstring`, `openssl`, `tokenizer`, `xml`, `zip`
|
||||
- **Database**: MySQL 5.7+ or MariaDB 10.3+
|
||||
- **Composer**: 2.x
|
||||
|
||||
### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y php8.2 php8.2-{mysql,curl,mbstring,xml,zip,fpm} nginx mysql-server
|
||||
```
|
||||
|
||||
### Windows (XAMPP/IIS)
|
||||
|
||||
- Download **XAMPP** with PHP 8.2 or install **PHP 8.2** manually.
|
||||
- Ensure `php.ini` has `extension=pdo_mysql`, `extension=curl`, etc. enabled.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Step 1: Clone the Repository
|
||||
|
||||
```bash
|
||||
# For RemcoEpic epicnabbo edit version:
|
||||
git clone ssh://git@localhost:8422/RemcoEpic/atomcms-edit.git
|
||||
cd atomcms-edit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Step 2: Install Dependencies
|
||||
|
||||
Run composer to install all required packages. The system is optimized to handle missing extensions automatically via the `SystemCheck` middleware.
|
||||
|
||||
```bash
|
||||
composer install --optimize-autoloader
|
||||
```
|
||||
|
||||
_Windows Note: Run CMD/PowerShell as Administrator if you get permission errors._
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Step 3: Environment Configuration
|
||||
|
||||
### 3.1 Create .env
|
||||
|
||||
If `.env` is missing, the system tries to create it automatically. You can also do it manually:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 3.2 Generate App Key
|
||||
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
### 3.3 Database Settings
|
||||
|
||||
Edit `.env` and configure your database connection:
|
||||
|
||||
```env
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=atomcms
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=your_password
|
||||
|
||||
APP_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
_Windows IIS Note: If `127.0.0.1` fails, try `localhost`._
|
||||
|
||||
---
|
||||
|
||||
## 💾 Step 4: Database Setup
|
||||
|
||||
### 4.1 Create Database
|
||||
|
||||
Login to MySQL and create the database:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE atomcms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'atomcms_user'@'localhost' IDENTIFIED BY 'strong_password';
|
||||
GRANT ALL PRIVILEGES ON atomcms.* TO 'atomcms_user'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
```
|
||||
|
||||
### 4.2 Run Migrations
|
||||
|
||||
```bash
|
||||
php artisan migrate --seed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Step 5: Storage Link (Crucial!)
|
||||
|
||||
The system uses a **Bulletproof Middleware (`SystemCheck`)** that attempts to create the storage symlink automatically on the first request.
|
||||
|
||||
**Manual Fallback (if auto-fails):**
|
||||
|
||||
_Linux:_
|
||||
|
||||
```bash
|
||||
php artisan storage:link
|
||||
sudo chmod -R 755 storage bootstrap/cache
|
||||
```
|
||||
|
||||
_Windows (CMD as Admin):_
|
||||
|
||||
```cmd
|
||||
mklink /J "C:\path\to\atomcms-edit\public\storage" "C:\path\to\atomcms-edit\storage\app\public"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Step 6: Web Server Configuration
|
||||
|
||||
### Option A: PHP Built-in (Testing)
|
||||
|
||||
```bash
|
||||
php artisan serve --host=0.0.0.0 --port=8000
|
||||
```
|
||||
|
||||
Visit: `http://localhost:8000`
|
||||
|
||||
### Option B: Nginx (Linux Production)
|
||||
|
||||
Edit `/etc/nginx/sites-available/atomcms`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name epicnabbo.nl; # Your domain
|
||||
root /var/www/atomcms/public;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enable: `sudo ln -s /etc/nginx/sites-available/atomcms /etc/nginx/sites-enabled/ && sudo systemctl restart nginx`
|
||||
|
||||
### Option C: Windows IIS
|
||||
|
||||
1. Open **IIS Manager** -> **Sites** -> **Add Website**.
|
||||
2. Physical Path: `C:\inetpub\wwwroot\atomcms-edit\public`
|
||||
3. Install **URL Rewrite Module**.
|
||||
4. Add `web.config` in `public/`:
|
||||
|
||||
```xml
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Laravel Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions>
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="index.php" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Step 7: Production Optimization
|
||||
|
||||
```bash
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Step 8: Verify & Test
|
||||
|
||||
1. Open `http://your-domain.com` (or `http://localhost:8000`).
|
||||
2. Test **Registration** at `/register`.
|
||||
- _Note:_ v1.0 fixes the "500 Error" on registration (Cloudflare Turnstile & IP fields).
|
||||
3. Check logs if needed: `tail -f storage/logs/laravel.log`
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Troubleshooting (v1.0 Fixes)
|
||||
|
||||
### 500 Error on Registration?
|
||||
|
||||
**Fixed in v1.0!**
|
||||
|
||||
- ✅ Cloudflare Turnstile null-response handled.
|
||||
- ✅ `ip_register` and `ip_current` added to User model.
|
||||
- ✅ MySQL Strict Mode disabled for Windows/IIS/XAMPP compatibility.
|
||||
|
||||
### "Missing .env" or "No Application Key"
|
||||
|
||||
The `SystemCheck` middleware (auto-runs on every request) will attempt to:
|
||||
|
||||
1. Copy `.env.example` to `.env`.
|
||||
2. Generate `APP_KEY` automatically.
|
||||
|
||||
### Storage Link Issues
|
||||
|
||||
The system auto-creates the symlink. If it fails on Windows, ensure you run the server with **Administrator** privileges.
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
1. Check `.env` credentials.
|
||||
2. Ensure MySQL service is running (`systemctl status mysql` or Windows Services).
|
||||
3. Use `127.0.0.1` instead of `localhost` if socket errors occur.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 What's new in v1.0 (epicnabbo edit)?
|
||||
|
||||
This version is **Bulletproof**. It is designed to work immediately after `composer install` on any environment.
|
||||
|
||||
- ✅ **Cross-Platform**: Works on Linux (Nginx/Apache), Windows (IIS/XAMPP).
|
||||
- ✅ **Auto-Recovery**: `SystemCheck` middleware fixes missing `.env`, `APP_KEY`, and storage links automatically.
|
||||
- ✅ **Zero 500 Errors**: Registration bugs (Turnstile & IP fields) fixed.
|
||||
- ✅ **MySQL Strict Mode**: Disabled for maximum compatibility.
|
||||
- ✅ **Windows Path Fix**: `index.php` handles Windows backslash paths.
|
||||
|
||||
---
|
||||
|
||||
**© 2026 RemcoEpic - epicnabbo.nl**
|
||||
@@ -171,13 +171,11 @@ class CreateNewUser implements CreatesNewUsers
|
||||
try {
|
||||
Http::asJson()->post(is_string($discordWebhookUrl) ? $discordWebhookUrl : '', [
|
||||
'username' => sprintf('%s Bot', is_string($hotelNameSetting) ? $hotelNameSetting : 'Hotel'),
|
||||
'content' => "User: {$username} has just registered, with the IP: {$ip} and E-mail: {$email}",
|
||||
'content' => "User: {$username} has just registered.",
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to send Discord webhook notification', [
|
||||
'username' => $username,
|
||||
'ip' => $ip,
|
||||
'email' => $email,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class DisableTwoFactorAuthentication extends \Laravel\Fortify\Actions\DisableTwo
|
||||
$user->forceFill([
|
||||
'two_factor_secret' => null,
|
||||
'two_factor_recovery_codes' => null,
|
||||
'two_factor_confirmed' => false,
|
||||
'two_factor_confirmed_at' => null,
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,68 +228,10 @@ final class Commandocentrum extends Page implements HasForms
|
||||
Section::make(__('commandocentrum.nitro_update'))
|
||||
->description(__('commandocentrum.nitro_update_desc'))
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->afterHeader([
|
||||
Action::make('configure_nitro')
|
||||
->label(__('commandocentrum.configure_nitro'))
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->color('primary')
|
||||
->form([
|
||||
\Filament\Forms\Components\TextInput::make('nitro_emulator_path')
|
||||
->label(__('commandocentrum.nitro_emulator_path'))
|
||||
->default(fn () => $this->getSetting('nitro_emulator_path', '/var/www/emulator')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_emulator_service')
|
||||
->label(__('commandocentrum.nitro_emulator_service'))
|
||||
->default(fn () => $this->getSetting('nitro_emulator_service', 'emulator')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_db_name')
|
||||
->label(__('commandocentrum.nitro_db_name'))
|
||||
->default(fn () => $this->getSetting('nitro_db_name', 'habbo')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_sql_dir')
|
||||
->label(__('commandocentrum.nitro_sql_dir'))
|
||||
->default(fn () => $this->getSetting('nitro_sql_dir', '/var/www/emulator/Database Updates')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_backup_dir')
|
||||
->label(__('commandocentrum.nitro_backup_dir'))
|
||||
->default(fn () => $this->getSetting('nitro_backup_dir', '/var/www/emulator/Database Updates/backups')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_gamedata_dir')
|
||||
->label(__('commandocentrum.nitro_gamedata_dir'))
|
||||
->default(fn () => $this->getSetting('nitro_gamedata_dir', '/var/www/Gamedata/config')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_client_dir')
|
||||
->label(__('commandocentrum.nitro_client_dir'))
|
||||
->default(fn () => $this->getSetting('nitro_client_dir', '/var/www/Nitro-V3/public/configuration')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_client_src')
|
||||
->label(__('commandocentrum.nitro_client_src'))
|
||||
->default(fn () => $this->getSetting('nitro_client_src', '/var/www/Nitro-V3')),
|
||||
\Filament\Forms\Components\TextInput::make('nitro_renderer_src')
|
||||
->label(__('commandocentrum.nitro_renderer_src'))
|
||||
->default(fn () => $this->getSetting('nitro_renderer_src', '/var/www/Nitro_Render_V3')),
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
$settings = app(SettingsService::class);
|
||||
$settings->set('nitro_emulator_path', $data['nitro_emulator_path'] ?? '/var/www/emulator');
|
||||
$settings->set('nitro_emulator_service', $data['nitro_emulator_service'] ?? 'emulator');
|
||||
$settings->set('nitro_db_name', $data['nitro_db_name'] ?? 'habbo');
|
||||
$settings->set('nitro_sql_dir', $data['nitro_sql_dir'] ?? '/var/www/emulator/Database Updates');
|
||||
$settings->set('nitro_backup_dir', $data['nitro_backup_dir'] ?? '/var/www/emulator/Database Updates/backups');
|
||||
$settings->set('nitro_gamedata_dir', $data['nitro_gamedata_dir'] ?? '/var/www/Gamedata/config');
|
||||
$settings->set('nitro_client_dir', $data['nitro_client_dir'] ?? '/var/www/Nitro-V3/public/configuration');
|
||||
$settings->set('nitro_client_src', $data['nitro_client_src'] ?? '/var/www/Nitro-V3');
|
||||
$settings->set('nitro_renderer_src', $data['nitro_renderer_src'] ?? '/var/www/Nitro_Render_V3');
|
||||
$this->fillForm();
|
||||
Notification::make()
|
||||
->title(__('commandocentrum.success'))
|
||||
->body(__('commandocentrum.nitro_config_saved'))
|
||||
->color('success')
|
||||
->send();
|
||||
}),
|
||||
Action::make('run_nitro_update')
|
||||
->label(__('commandocentrum.run_update'))
|
||||
->icon('heroicon-o-play')
|
||||
->color('success')
|
||||
->action('runUpdateNitrov3'),
|
||||
])
|
||||
->schema([
|
||||
Placeholder::make('nitro_config_summary')
|
||||
Placeholder::make('nitro_cli_only')
|
||||
->label('')
|
||||
->content(fn () => view('filament.components.commandocentrum.nitro-config-summary')),
|
||||
->content(__('commandocentrum.nitro_cli_only')),
|
||||
]),
|
||||
|
||||
Section::make(__('commandocentrum.clothing_sync'))
|
||||
@@ -735,64 +677,6 @@ final class Commandocentrum extends Page implements HasForms
|
||||
}
|
||||
}
|
||||
|
||||
public function runUpdateNitrov3(): void
|
||||
{
|
||||
try {
|
||||
$scriptPath = base_path('update-Nitrov3.sh');
|
||||
|
||||
if (! $this->fileExists($scriptPath)) {
|
||||
$this->notify(__('commandocentrum.error'), 'Script niet gevonden: ' . $scriptPath, 'danger');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dbHost = config('database.connections.mysql.host', '127.0.0.1');
|
||||
$dbPort = config('database.connections.mysql.port', '3306');
|
||||
$dbUser = config('database.connections.mysql.username', 'root');
|
||||
$dbPass = config('database.connections.mysql.password', '');
|
||||
|
||||
$env = [
|
||||
'NITRO_EMULATOR_PATH' => $this->data['nitro_emulator_path'] ?? '/var/www/emulator',
|
||||
'NITRO_EMULATOR_SERVICE' => $this->data['nitro_emulator_service'] ?? 'emulator',
|
||||
'NITRO_DB_NAME' => $this->data['nitro_db_name'] ?? 'habbo',
|
||||
'NITRO_DB_HOST' => $dbHost,
|
||||
'NITRO_DB_PORT' => $dbPort,
|
||||
'NITRO_DB_USER' => $dbUser,
|
||||
'NITRO_DB_PASS' => $dbPass,
|
||||
'NITRO_SQL_DIR' => $this->data['nitro_sql_dir'] ?? '/var/www/emulator/Database Updates',
|
||||
'NITRO_BACKUP_DIR' => $this->data['nitro_backup_dir'] ?? '/var/www/emulator/Database Updates/backups',
|
||||
'NITRO_GAMEDATA_DIR' => $this->data['nitro_gamedata_dir'] ?? '/var/www/Gamedata/config',
|
||||
'NITRO_CLIENT_DIR' => $this->data['nitro_client_dir'] ?? '/var/www/Nitro-V3/public/configuration',
|
||||
'NITRO_CLIENT_SRC' => $this->data['nitro_client_src'] ?? '/var/www/Nitro-V3',
|
||||
'NITRO_RENDERER_SRC' => $this->data['nitro_renderer_src'] ?? '/var/www/Nitro_Render_V3',
|
||||
];
|
||||
|
||||
$envExport = '';
|
||||
foreach ($env as $key => $value) {
|
||||
$envExport .= 'export ' . $key . '=' . escapeshellarg($value) . '; ';
|
||||
}
|
||||
|
||||
$result = Process::timeout(600)->run($envExport . 'bash ' . escapeshellarg($scriptPath) . ' 2>&1');
|
||||
$output = $result->output();
|
||||
$exitCode = $result->exitCode();
|
||||
|
||||
$title = $exitCode === 0 ? __('commandocentrum.success') : __('commandocentrum.error');
|
||||
$color = $exitCode === 0 ? 'success' : 'danger';
|
||||
$message = $exitCode === 0 ? '✅ Nitro V3 update voltooid' : '⚠️ Script fout (exit code ' . $exitCode . ')';
|
||||
|
||||
$this->notify($title, $message, $color);
|
||||
|
||||
Notification::make()
|
||||
->title(__('commandocentrum.nitro_update_output'))
|
||||
->body('<pre style="max-height:300px;overflow:auto;font-size:12px;background:#1e293b;color:#e2e8f0;padding:12px;border-radius:8px;">' . e($output) . '</pre>')
|
||||
->color($color)
|
||||
->persistent()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
$this->notify(__('commandocentrum.error'), $e->getMessage(), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
public function saveEmulator(): void
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\RadioApiKey;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
@@ -82,23 +81,19 @@ final class ApiKeys extends Page implements HasTable
|
||||
->label('Aangemaakt')
|
||||
->dateTime('d-m-Y H:i'),
|
||||
])
|
||||
->recordActions(function ($record) {
|
||||
return [
|
||||
ActionGroup::make([
|
||||
Action::make('toggle')
|
||||
->label($record->is_active ? 'Deactiveren' : 'Activeren')
|
||||
->icon($record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
|
||||
->action(fn () => $this->toggleKey($record)),
|
||||
Action::make('delete')
|
||||
->label('Verwijderen')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn () => $record->delete()),
|
||||
]),
|
||||
];
|
||||
})
|
||||
->toolbarActions([
|
||||
->actions([
|
||||
Action::make('toggle')
|
||||
->label(fn ($record) => $record->is_active ? 'Deactiveren' : 'Activeren')
|
||||
->icon(fn ($record) => $record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
|
||||
->action(fn ($record) => $this->toggleKey($record)),
|
||||
Action::make('delete')
|
||||
->label('Verwijderen')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn ($record) => $record->delete()),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('create')
|
||||
->label('Nieuwe API Sleutel')
|
||||
->icon('heroicon-o-plus')
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\RadioAutoDjTrack;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
@@ -74,31 +73,27 @@ final class AutoDjPlaylist extends Page implements HasTable
|
||||
->trueColor('success')
|
||||
->falseColor('danger'),
|
||||
])
|
||||
->recordActions(function ($record) {
|
||||
return [
|
||||
ActionGroup::make([
|
||||
Action::make('toggle_active')
|
||||
->label($record->is_active ? 'Deactiveren' : 'Activeren')
|
||||
->icon($record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
|
||||
->action(fn () => $this->toggleActive($record)),
|
||||
Action::make('move_up')
|
||||
->label('Omhoog')
|
||||
->icon('heroicon-o-chevron-up')
|
||||
->action(fn () => $this->moveUp($record)),
|
||||
Action::make('move_down')
|
||||
->label('Omlaag')
|
||||
->icon('heroicon-o-chevron-down')
|
||||
->action(fn () => $this->moveDown($record)),
|
||||
Action::make('delete')
|
||||
->label('Verwijderen')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn () => $record->delete()),
|
||||
]),
|
||||
];
|
||||
})
|
||||
->toolbarActions([
|
||||
->actions([
|
||||
Action::make('toggle_active')
|
||||
->label(fn ($record) => $record->is_active ? 'Deactiveren' : 'Activeren')
|
||||
->icon(fn ($record) => $record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
|
||||
->action(fn ($record) => $this->toggleActive($record)),
|
||||
Action::make('move_up')
|
||||
->label('Omhoog')
|
||||
->icon('heroicon-o-chevron-up')
|
||||
->action(fn ($record) => $this->moveUp($record)),
|
||||
Action::make('move_down')
|
||||
->label('Omlaag')
|
||||
->icon('heroicon-o-chevron-down')
|
||||
->action(fn ($record) => $this->moveDown($record)),
|
||||
Action::make('delete')
|
||||
->label('Verwijderen')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn ($record) => $record->delete()),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('create')
|
||||
->label('Track Toevoegen')
|
||||
->icon('heroicon-o-plus')
|
||||
|
||||
@@ -50,6 +50,7 @@ final class EmbedCode extends Page implements HasForms
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->statePath('data')
|
||||
->components([
|
||||
Section::make('Embed Configuratie')
|
||||
->description('Pas het uiterlijk van de embed player aan')
|
||||
|
||||
@@ -196,7 +196,7 @@ final class PointsSettings extends Page implements HasForms
|
||||
|
||||
public function resetLeaderboard(): void
|
||||
{
|
||||
User::where('radio_points', '>', 0)->update(['radio_points' => 0]);
|
||||
User::query()->where('radio_points', '>', 0)->each(fn (User $u) => $u->forceFill(['radio_points' => 0])->save());
|
||||
RadioListenerPoint::query()->delete();
|
||||
|
||||
$this->pointsService->clearLeaderboardCache();
|
||||
|
||||
@@ -129,7 +129,7 @@ class StaffApplicationResource extends Resource
|
||||
}
|
||||
|
||||
if ((int) $user->team_id !== (int) $team->id) {
|
||||
$user->update(['team_id' => $team->id]);
|
||||
$user->forceFill(['team_id' => $team->id])->save();
|
||||
}
|
||||
|
||||
$r->update([
|
||||
@@ -177,7 +177,7 @@ class StaffApplicationResource extends Resource
|
||||
}
|
||||
|
||||
if ($r->status === 'approved' && (int) $user->team_id === (int) $team->id) {
|
||||
$user->update(['team_id' => null]);
|
||||
$user->forceFill(['team_id' => null])->save();
|
||||
}
|
||||
|
||||
$r->update([
|
||||
|
||||
@@ -180,7 +180,7 @@ class RadioApplicationResource extends Resource
|
||||
])
|
||||
->label('Status'),
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
Action::make('approve')
|
||||
->label('Goedkeuren')
|
||||
->icon('heroicon-o-check-circle')
|
||||
|
||||
@@ -123,11 +123,11 @@ class RadioBannerResource extends Resource
|
||||
->label('Actief')
|
||||
->boolean(),
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
->headerActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -115,11 +115,11 @@ class RadioHistoryResource extends Resource
|
||||
TextColumn::make('listeners_count')
|
||||
->label('Luisteraars'),
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
->headerActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -120,10 +120,10 @@ class RadioRankResource extends Resource
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
->headerActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -142,11 +142,11 @@ class RadioScheduleResource extends Resource
|
||||
])
|
||||
->label('Dag'),
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
->headerActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class RadioShoutResource extends Resource
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
Action::make('report')
|
||||
->label('Melden')
|
||||
->icon('heroicon-o-flag')
|
||||
|
||||
@@ -76,7 +76,7 @@ class RadioSongPlayResource extends Resource
|
||||
->options(fn () => RadioSongPlay::distinct()->whereNotNull('artist')->pluck('artist', 'artist')->toArray())
|
||||
->searchable(),
|
||||
])
|
||||
->recordActions([
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->label('Verwijderen'),
|
||||
])
|
||||
|
||||
@@ -189,7 +189,7 @@ class EditUser extends EditRecord
|
||||
}
|
||||
|
||||
if (! $user->online) {
|
||||
$user->update(['rank' => $data['rank']]);
|
||||
$user->forceFill(['rank' => $data['rank']])->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\ArticleResource;
|
||||
use App\Models\Articles\WebsiteArticle;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ArticleApiController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$articles = WebsiteArticle::with(['user:id,username,look'])
|
||||
->latest('id')
|
||||
->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => ArticleResource::collection($articles),
|
||||
'meta' => [
|
||||
'current_page' => $articles->currentPage(),
|
||||
'last_page' => $articles->lastPage(),
|
||||
'per_page' => $articles->perPage(),
|
||||
'total' => $articles->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $slug): JsonResponse
|
||||
{
|
||||
$article = WebsiteArticle::with(['user:id,username,look', 'comments.user:id,username,look'])
|
||||
->where('slug', $slug)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json(['data' => new ArticleResource($article)]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Game\Furniture\CatalogItem;
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use App\Models\Game\Guild\Guild;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Services\Community\StaffService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ContentApiController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly StaffService $staffService,
|
||||
) {}
|
||||
|
||||
public function settings(): JsonResponse
|
||||
{
|
||||
$settings = Cache::remember('api_all_settings', 60, fn () => WebsiteSetting::all()->pluck('value', 'key'));
|
||||
|
||||
return response()->json(['data' => $settings]);
|
||||
}
|
||||
|
||||
public function staff(): JsonResponse
|
||||
{
|
||||
return response()->json(['data' => $this->staffService->fetchStaffPositions()]);
|
||||
}
|
||||
|
||||
public function teams(): JsonResponse
|
||||
{
|
||||
$teams = Guild::with('members')->latest('id')->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => $teams->items(),
|
||||
'meta' => [
|
||||
'current_page' => $teams->currentPage(),
|
||||
'last_page' => $teams->lastPage(),
|
||||
'per_page' => $teams->perPage(),
|
||||
'total' => $teams->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rareValues(Request $request): JsonResponse
|
||||
{
|
||||
$query = CatalogItem::query();
|
||||
|
||||
if ($categoryId = $request->query('category')) {
|
||||
$query->where('page_id', $categoryId);
|
||||
}
|
||||
|
||||
$items = $query->with('itemBase')->latest('id')->paginate(24);
|
||||
|
||||
return response()->json([
|
||||
'data' => $items->items(),
|
||||
'meta' => [
|
||||
'current_page' => $items->currentPage(),
|
||||
'last_page' => $items->lastPage(),
|
||||
'per_page' => $items->perPage(),
|
||||
'total' => $items->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rareValuesCategories(): JsonResponse
|
||||
{
|
||||
$categories = CatalogPage::where('catalog_name', '!=', '')
|
||||
->where('visible', 1)
|
||||
->orderBy('order_number')
|
||||
->get(['id', 'catalog_name', 'icon']);
|
||||
|
||||
return response()->json(['data' => $categories]);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Game\Furniture\CatalogItem;
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use App\Models\Game\Furniture\ItemBase;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class FurniEditorController extends Controller
|
||||
@@ -25,10 +29,10 @@ class FurniEditorController extends Controller
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
|
||||
return response()->json(['error' => "{$action} mislukt"], 500);
|
||||
return response()->json(['error' => "{$action} failed"], 500);
|
||||
}
|
||||
|
||||
private function formatItemData(\stdClass $item): array
|
||||
private function formatItemData(ItemBase $item): array
|
||||
{
|
||||
return [
|
||||
'id' => $item->id,
|
||||
@@ -59,17 +63,17 @@ class FurniEditorController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
private function formatCatalogItemData(\stdClass $r): array
|
||||
private function formatCatalogItemData(CatalogItem $item): array
|
||||
{
|
||||
return [
|
||||
'id' => $r->id,
|
||||
'catalogName' => $r->catalog_name,
|
||||
'costCredits' => $r->cost_credits,
|
||||
'costPoints' => $r->cost_points,
|
||||
'pointsType' => $r->points_type,
|
||||
'pageId' => $r->page_id,
|
||||
'pageName' => $r->page_id
|
||||
? DB::table('catalog_pages')->where('id', $r->page_id)->value('caption') ?? ''
|
||||
'id' => $item->id,
|
||||
'catalogName' => $item->catalog_name,
|
||||
'costCredits' => $item->cost_credits,
|
||||
'costPoints' => $item->cost_points,
|
||||
'pointsType' => $item->points_type,
|
||||
'pageId' => $item->page_id,
|
||||
'pageName' => $item->page_id
|
||||
? CatalogPage::where('id', $item->page_id)->value('caption') ?? ''
|
||||
: '',
|
||||
];
|
||||
}
|
||||
@@ -84,7 +88,7 @@ class FurniEditorController extends Controller
|
||||
$page = max(1, (int) $request->input('page', 1));
|
||||
$limit = min(50, max(1, (int) $request->input('limit', 20)));
|
||||
|
||||
$query = DB::table('items_base');
|
||||
$query = ItemBase::query();
|
||||
|
||||
if ($q !== '' && $q !== '0') {
|
||||
$query->where(function ($w) use ($q) {
|
||||
@@ -105,7 +109,7 @@ class FurniEditorController extends Controller
|
||||
->skip(($page - 1) * $limit)
|
||||
->take($limit)
|
||||
->get()
|
||||
->map(fn ($r) => [
|
||||
->map(fn (ItemBase $r) => [
|
||||
'id' => $r->id,
|
||||
'spriteId' => $r->sprite_id,
|
||||
'itemName' => $r->item_name,
|
||||
@@ -128,7 +132,7 @@ class FurniEditorController extends Controller
|
||||
'page' => $page,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Zoeken', $e);
|
||||
return $this->handleApiError('Search', $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,15 +146,14 @@ class FurniEditorController extends Controller
|
||||
return response()->json(['error' => 'Missing id'], 400);
|
||||
}
|
||||
|
||||
$item = DB::table('items_base')->where('id', $id)->first();
|
||||
$item = ItemBase::where('id', $id)->first();
|
||||
if (! $item) {
|
||||
return response()->json(['error' => 'Item not found'], 404);
|
||||
}
|
||||
|
||||
$catalog = DB::table('catalog_items')
|
||||
->whereRaw('FIND_IN_SET(?, item_ids)', [$id])
|
||||
$catalog = CatalogItem::whereRaw('FIND_IN_SET(?, item_ids)', [$id])
|
||||
->get()
|
||||
->map(fn ($r) => $this->formatCatalogItemData($r));
|
||||
->map(fn (CatalogItem $r) => $this->formatCatalogItemData($r));
|
||||
|
||||
return response()->json([
|
||||
'item' => array_merge($this->formatItemData($item), [
|
||||
@@ -161,7 +164,7 @@ class FurniEditorController extends Controller
|
||||
'furniDataEntry' => null,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Item laden', $e);
|
||||
return $this->handleApiError('Load item', $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +185,7 @@ class FurniEditorController extends Controller
|
||||
return response()->json(['error' => 'No fields to update'], 400);
|
||||
}
|
||||
|
||||
DB::table('items_base')->where('id', $id)->update($update);
|
||||
ItemBase::where('id', $id)->update($update);
|
||||
|
||||
Log::info('[Audit] FurniEditor update', [
|
||||
'user_id' => Auth::id(),
|
||||
@@ -192,7 +195,7 @@ class FurniEditorController extends Controller
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Actie', $e);
|
||||
return $this->handleApiError('Update', $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +206,7 @@ class FurniEditorController extends Controller
|
||||
try {
|
||||
$data = $request->json()->all();
|
||||
|
||||
$id = DB::table('items_base')->insertGetId($this->buildInsertData($data));
|
||||
$id = ItemBase::insertGetId($this->buildInsertData($data));
|
||||
|
||||
Log::info('[Audit] FurniEditor create', [
|
||||
'user_id' => Auth::id(),
|
||||
@@ -212,7 +215,7 @@ class FurniEditorController extends Controller
|
||||
|
||||
return response()->json(['id' => $id]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Actie', $e);
|
||||
return $this->handleApiError('Create', $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +229,7 @@ class FurniEditorController extends Controller
|
||||
return response()->json(['error' => 'Missing id'], 400);
|
||||
}
|
||||
|
||||
DB::table('items_base')->where('id', $id)->delete();
|
||||
ItemBase::where('id', $id)->delete();
|
||||
|
||||
Log::warning('[Audit] FurniEditor delete', [
|
||||
'user_id' => Auth::id(),
|
||||
@@ -235,7 +238,7 @@ class FurniEditorController extends Controller
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Actie', $e);
|
||||
return $this->handleApiError('Delete', $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +247,7 @@ class FurniEditorController extends Controller
|
||||
$this->checkAdmin();
|
||||
|
||||
try {
|
||||
$rows = DB::table('items_base')
|
||||
$rows = ItemBase::query()
|
||||
->select('interaction_type')
|
||||
->groupBy('interaction_type')
|
||||
->orderBy('interaction_type')
|
||||
@@ -252,7 +255,7 @@ class FurniEditorController extends Controller
|
||||
|
||||
return response()->json(['interactions' => $rows]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Actie', $e);
|
||||
return $this->handleApiError('Interactions', $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,14 +269,14 @@ class FurniEditorController extends Controller
|
||||
return response()->json(['error' => 'Missing spriteId'], 400);
|
||||
}
|
||||
|
||||
$item = DB::table('items_base')->where('sprite_id', $spriteId)->first();
|
||||
$item = ItemBase::where('sprite_id', $spriteId)->first();
|
||||
if (! $item) {
|
||||
return response()->json(['error' => 'Item not found'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['id' => $item->id]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleApiError('Actie', $e);
|
||||
return $this->handleApiError('By sprite', $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\HelpTicketReplyRequest;
|
||||
use App\Http\Requests\Api\HelpTicketRequest;
|
||||
use App\Http\Resources\Api\HelpTicketResource;
|
||||
use App\Models\Help\WebsiteHelpCenterTicket;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class HelpApiController extends Controller
|
||||
{
|
||||
public function tickets(Request $request): JsonResponse
|
||||
{
|
||||
$tickets = WebsiteHelpCenterTicket::with('user:id,username,look')
|
||||
->when($request->user(), fn ($q) => $q->where('user_id', $request->user()->id))
|
||||
->latest()
|
||||
->paginate(10);
|
||||
|
||||
return response()->json([
|
||||
'data' => HelpTicketResource::collection($tickets),
|
||||
'meta' => [
|
||||
'current_page' => $tickets->currentPage(),
|
||||
'last_page' => $tickets->lastPage(),
|
||||
'total' => $tickets->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
$ticket = WebsiteHelpCenterTicket::with(['user:id,username,look', 'replies.user:id,username,look'])
|
||||
->where('id', $id)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json(['data' => new HelpTicketResource($ticket)]);
|
||||
}
|
||||
|
||||
public function create(HelpTicketRequest $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$ticket = WebsiteHelpCenterTicket::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'subject' => $validated['subject'],
|
||||
'category' => $validated['category'],
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
$ticket->replies()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'message' => $validated['message'],
|
||||
]);
|
||||
|
||||
return response()->json(['data' => new HelpTicketResource($ticket)], 201);
|
||||
}
|
||||
|
||||
public function reply(HelpTicketReplyRequest $request, string $id): JsonResponse
|
||||
{
|
||||
$ticket = WebsiteHelpCenterTicket::where('id', $id)
|
||||
->where('user_id', $request->user()->id)
|
||||
->firstOrFail();
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$reply = $ticket->replies()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'message' => $validated['message'],
|
||||
]);
|
||||
|
||||
return response()->json(['data' => $reply->load('user:id,username,look')], 201);
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\HelpTicketReplyRequest;
|
||||
use App\Http\Requests\Api\HelpTicketRequest;
|
||||
use App\Http\Requests\Api\PhotoUploadRequest;
|
||||
use App\Http\Resources\Api\ArticleResource;
|
||||
use App\Http\Resources\Api\HelpTicketResource;
|
||||
use App\Http\Resources\Api\LeaderboardUserResource;
|
||||
use App\Http\Resources\Api\PhotoResource;
|
||||
use App\Http\Resources\Api\ShopPackageResource;
|
||||
use App\Models\Articles\WebsiteArticle;
|
||||
use App\Models\Game\Furniture\CatalogItem;
|
||||
use App\Models\Game\Furniture\CatalogPage;
|
||||
use App\Models\Game\Guild\Guild;
|
||||
use App\Models\Help\WebsiteHelpCenterTicket;
|
||||
use App\Models\Miscellaneous\CameraWeb;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\Shop\WebsiteShopArticle;
|
||||
use App\Models\User;
|
||||
use App\Services\Community\StaffService;
|
||||
use App\Services\User\UserApiService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class HotelApiController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserApiService $userApiService,
|
||||
private readonly StaffService $staffService,
|
||||
) {}
|
||||
|
||||
public function fetchUser(string $username): JsonResponse
|
||||
{
|
||||
$user = $this->userApiService->fetchUser($username, ['username', 'motto', 'look']);
|
||||
|
||||
return response()->json([
|
||||
'data' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
public function onlineUsers(): JsonResponse
|
||||
{
|
||||
$users = $this->userApiService->onlineUsers(['username', 'motto', 'look'], true);
|
||||
|
||||
return response()->json([
|
||||
'data' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
public function onlineUserCount(): JsonResponse
|
||||
{
|
||||
$count = $this->userApiService->onlineUserCount();
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'onlineCount' => $count,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function articles(): JsonResponse
|
||||
{
|
||||
$articles = WebsiteArticle::with(['user:id,username,look'])
|
||||
->latest('id')
|
||||
->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => ArticleResource::collection($articles),
|
||||
'meta' => [
|
||||
'current_page' => $articles->currentPage(),
|
||||
'last_page' => $articles->lastPage(),
|
||||
'per_page' => $articles->perPage(),
|
||||
'total' => $articles->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function article(string $slug): JsonResponse
|
||||
{
|
||||
$article = WebsiteArticle::with(['user:id,username,look', 'comments.user:id,username,look'])
|
||||
->where('slug', $slug)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json([
|
||||
'data' => new ArticleResource($article),
|
||||
]);
|
||||
}
|
||||
|
||||
public function photos(): JsonResponse
|
||||
{
|
||||
$photos = CameraWeb::query()
|
||||
->where('visible', true)
|
||||
->latest('id')
|
||||
->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => PhotoResource::collection($photos),
|
||||
'meta' => [
|
||||
'current_page' => $photos->currentPage(),
|
||||
'last_page' => $photos->lastPage(),
|
||||
'per_page' => $photos->perPage(),
|
||||
'total' => $photos->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function staff(): JsonResponse
|
||||
{
|
||||
$employees = $this->staffService->fetchStaffPositions();
|
||||
|
||||
return response()->json([
|
||||
'data' => $employees,
|
||||
]);
|
||||
}
|
||||
|
||||
public function shopPackages(Request $request): JsonResponse
|
||||
{
|
||||
$packages = WebsiteShopArticle::latest('id')->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => ShopPackageResource::collection($packages),
|
||||
'meta' => [
|
||||
'current_page' => $packages->currentPage(),
|
||||
'last_page' => $packages->lastPage(),
|
||||
'per_page' => $packages->perPage(),
|
||||
'total' => $packages->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function shopCategories(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => ['furniture', 'badges', 'ranks'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function teams(): JsonResponse
|
||||
{
|
||||
$teams = Guild::with('members')
|
||||
->latest('id')
|
||||
->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => $teams->items(),
|
||||
'meta' => [
|
||||
'current_page' => $teams->currentPage(),
|
||||
'last_page' => $teams->lastPage(),
|
||||
'per_page' => $teams->perPage(),
|
||||
'total' => $teams->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function leaderboard(Request $request): JsonResponse
|
||||
{
|
||||
$type = $request->query('type', 'credits');
|
||||
$limit = (int) $request->query('limit', 100);
|
||||
|
||||
$validTypes = ['credits', 'pixels'];
|
||||
if (! in_array($type, $validTypes)) {
|
||||
$type = 'credits';
|
||||
}
|
||||
|
||||
$users = User::orderByDesc($type)
|
||||
->limit($limit)
|
||||
->get(['id', 'username', 'look', 'motto', 'credits', 'pixels']);
|
||||
|
||||
return response()->json([
|
||||
'data' => LeaderboardUserResource::collection($users),
|
||||
'type' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rareValues(Request $request): JsonResponse
|
||||
{
|
||||
$categoryId = $request->query('category');
|
||||
|
||||
$query = CatalogItem::query();
|
||||
|
||||
if ($categoryId) {
|
||||
$query->where('page_id', $categoryId);
|
||||
}
|
||||
|
||||
$items = $query->with('itemBase')
|
||||
->latest('id')
|
||||
->paginate(24);
|
||||
|
||||
return response()->json([
|
||||
'data' => $items->items(),
|
||||
'meta' => [
|
||||
'current_page' => $items->currentPage(),
|
||||
'last_page' => $items->lastPage(),
|
||||
'per_page' => $items->perPage(),
|
||||
'total' => $items->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rareValuesCategories(): JsonResponse
|
||||
{
|
||||
$categories = CatalogPage::where('catalog_name', '!=', '')
|
||||
->where('visible', 1)
|
||||
->orderBy('order_number')
|
||||
->get(['id', 'catalog_name', 'icon']);
|
||||
|
||||
return response()->json([
|
||||
'data' => $categories,
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(): JsonResponse
|
||||
{
|
||||
$settings = Cache::remember('api_all_settings', 60, fn () => WebsiteSetting::all()->pluck('value', 'key'));
|
||||
|
||||
return response()->json([
|
||||
'data' => $settings,
|
||||
]);
|
||||
}
|
||||
|
||||
public function userProfile(string $username): JsonResponse
|
||||
{
|
||||
$user = User::where('username', $username)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json([
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'look' => $user->look,
|
||||
'motto' => $user->motto,
|
||||
'account_created' => $user->account_created,
|
||||
'online' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function helpTickets(Request $request): JsonResponse
|
||||
{
|
||||
$tickets = WebsiteHelpCenterTicket::with('user:id,username,look')
|
||||
->when($request->user(), fn ($q) => $q->where('user_id', $request->user()->id))
|
||||
->latest()
|
||||
->paginate(10);
|
||||
|
||||
return response()->json([
|
||||
'data' => HelpTicketResource::collection($tickets),
|
||||
'meta' => [
|
||||
'current_page' => $tickets->currentPage(),
|
||||
'last_page' => $tickets->lastPage(),
|
||||
'total' => $tickets->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function helpTicket(string $id): JsonResponse
|
||||
{
|
||||
$ticket = WebsiteHelpCenterTicket::with(['user:id,username,look', 'replies.user:id,username,look'])
|
||||
->where('id', $id)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json(['data' => new HelpTicketResource($ticket)]);
|
||||
}
|
||||
|
||||
public function helpTicketCreate(HelpTicketRequest $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$ticket = WebsiteHelpCenterTicket::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'subject' => $validated['subject'],
|
||||
'category' => $validated['category'],
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
$ticket->replies()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'message' => $validated['message'],
|
||||
]);
|
||||
|
||||
return response()->json(['data' => new HelpTicketResource($ticket)], 201);
|
||||
}
|
||||
|
||||
public function helpTicketReply(HelpTicketReplyRequest $request, string $id): JsonResponse
|
||||
{
|
||||
$ticket = WebsiteHelpCenterTicket::where('id', $id)
|
||||
->where('user_id', $request->user()->id)
|
||||
->firstOrFail();
|
||||
|
||||
$reply = $ticket->replies()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'message' => $request->input('message'),
|
||||
]);
|
||||
|
||||
return response()->json(['data' => $reply->load('user:id,username,look')], 201);
|
||||
}
|
||||
|
||||
public function uploadPhoto(PhotoUploadRequest $request): JsonResponse
|
||||
{
|
||||
$path = $request->file('image')->store('photos', 'public');
|
||||
|
||||
$photo = CameraWeb::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'image' => '/storage/' . $path,
|
||||
'visible' => true,
|
||||
]);
|
||||
|
||||
return response()->json(['data' => new PhotoResource($photo)], 201);
|
||||
}
|
||||
|
||||
public function purchasePackage(Request $request, int $packageId): JsonResponse
|
||||
{
|
||||
$package = WebsiteShopArticle::findOrFail($packageId);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
$cost = $package->costs;
|
||||
|
||||
if ($user->credits < $cost) {
|
||||
return response()->json(['error' => 'Not enough credits'], 400);
|
||||
}
|
||||
|
||||
$user->decrement('credits', $cost);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Purchase successful']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\PhotoUploadRequest;
|
||||
use App\Http\Resources\Api\PhotoResource;
|
||||
use App\Models\Miscellaneous\CameraWeb;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class MediaApiController extends Controller
|
||||
{
|
||||
public function photos(): JsonResponse
|
||||
{
|
||||
$photos = CameraWeb::query()
|
||||
->where('visible', true)
|
||||
->latest('id')
|
||||
->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => PhotoResource::collection($photos),
|
||||
'meta' => [
|
||||
'current_page' => $photos->currentPage(),
|
||||
'last_page' => $photos->lastPage(),
|
||||
'per_page' => $photos->perPage(),
|
||||
'total' => $photos->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function upload(PhotoUploadRequest $request): JsonResponse
|
||||
{
|
||||
$path = $request->file('image')->store('photos', 'public');
|
||||
|
||||
$photo = CameraWeb::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'image' => '/storage/' . $path,
|
||||
'visible' => true,
|
||||
]);
|
||||
|
||||
return response()->json(['data' => new PhotoResource($photo)], 201);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\ShopPackageResource;
|
||||
use App\Models\Shop\WebsiteShopArticle;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ShopApiController extends Controller
|
||||
{
|
||||
public function packages(): JsonResponse
|
||||
{
|
||||
$packages = WebsiteShopArticle::latest('id')->paginate(12);
|
||||
|
||||
return response()->json([
|
||||
'data' => ShopPackageResource::collection($packages),
|
||||
'meta' => [
|
||||
'current_page' => $packages->currentPage(),
|
||||
'last_page' => $packages->lastPage(),
|
||||
'per_page' => $packages->perPage(),
|
||||
'total' => $packages->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function categories(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => ['furniture', 'badges', 'ranks'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function purchase(Request $request, int $packageId): JsonResponse
|
||||
{
|
||||
$package = WebsiteShopArticle::findOrFail($packageId);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if ($package->give_rank && $user->rank >= $package->give_rank) {
|
||||
return response()->json(['error' => 'You already have this or a higher rank'], 400);
|
||||
}
|
||||
|
||||
if ($user->credits < $package->costs) {
|
||||
return response()->json(['error' => 'Not enough credits'], 400);
|
||||
}
|
||||
|
||||
$user->decrement('credits', $package->costs);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Purchase successful']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\LeaderboardUserResource;
|
||||
use App\Models\User;
|
||||
use App\Services\User\UserApiService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserApiController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserApiService $userApiService,
|
||||
) {}
|
||||
|
||||
public function fetchUser(string $username): JsonResponse
|
||||
{
|
||||
$user = $this->userApiService->fetchUser($username, ['username', 'motto', 'look']);
|
||||
|
||||
return response()->json(['data' => $user]);
|
||||
}
|
||||
|
||||
public function userProfile(string $username): JsonResponse
|
||||
{
|
||||
$user = User::where('username', $username)->first();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json(['data' => null], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'look' => $user->look,
|
||||
'motto' => $user->motto,
|
||||
'account_created' => $user->account_created,
|
||||
'online' => false,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function onlineUsers(): JsonResponse
|
||||
{
|
||||
$users = $this->userApiService->onlineUsers(['username', 'motto', 'look'], true);
|
||||
|
||||
return response()->json(['data' => $users]);
|
||||
}
|
||||
|
||||
public function onlineUserCount(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => ['onlineCount' => $this->userApiService->onlineUserCount()],
|
||||
]);
|
||||
}
|
||||
|
||||
public function leaderboard(Request $request): JsonResponse
|
||||
{
|
||||
$type = $request->query('type', 'credits');
|
||||
$limit = (int) $request->query('limit', 100);
|
||||
|
||||
if (! in_array($type, ['credits', 'pixels'])) {
|
||||
$type = 'credits';
|
||||
}
|
||||
|
||||
$users = User::orderByDesc($type)
|
||||
->limit($limit)
|
||||
->get(['id', 'username', 'look', 'motto', 'credits', 'pixels']);
|
||||
|
||||
return response()->json([
|
||||
'data' => LeaderboardUserResource::collection($users),
|
||||
'type' => $type,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class BadgeController extends Controller
|
||||
{
|
||||
public function show(): View
|
||||
{
|
||||
$cost = 150;
|
||||
$cost = (int) setting('badge_cost', 150);
|
||||
$currencyType = 'credits';
|
||||
$folderError = false;
|
||||
$errorMessage = '';
|
||||
@@ -60,7 +60,7 @@ class BadgeController extends Controller
|
||||
return redirect()->route('login')->with('error', 'You must be logged in to purchase badges.');
|
||||
}
|
||||
|
||||
$cost = 150;
|
||||
$cost = (int) setting('badge_cost', 150);
|
||||
|
||||
if (property_exists($user, 'credits') && $user->credits !== null && $user->credits < $cost) {
|
||||
return redirect()->back()->with('error', 'You don\'t have enough credits to purchase a badge.');
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Community;
|
||||
|
||||
use App\Enums\RadioSettings;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Concerns\HasRadioSettings;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\RadioApplication;
|
||||
use App\Models\RadioBanner;
|
||||
@@ -20,6 +21,7 @@ use Illuminate\View\View;
|
||||
|
||||
class RadioController extends Controller
|
||||
{
|
||||
use HasRadioSettings;
|
||||
public function __construct(
|
||||
private readonly RadioStreamService $streamService,
|
||||
private readonly RadioScheduleService $scheduleService,
|
||||
@@ -120,7 +122,8 @@ class RadioController extends Controller
|
||||
]);
|
||||
|
||||
if ($validated['rank_id']) {
|
||||
$rank = Cache::remember("radio_rank_{$validated['rank_id']}", 300, fn () => RadioRank::find($validated['rank_id']));
|
||||
$acceptingRanks = Cache::remember('radio_ranks_accepting', 300, fn () => RadioRank::where('accepts_applications', true)->get()->keyBy('id'));
|
||||
$rank = $acceptingRanks->get((int) $validated['rank_id']);
|
||||
|
||||
if (! $rank || ! $rank->accepts_applications) {
|
||||
return back()->withErrors([
|
||||
@@ -336,13 +339,4 @@ class RadioController extends Controller
|
||||
return WebsiteSetting::whereIn('key', $stringKeys)->pluck('value', 'key')->all();
|
||||
});
|
||||
}
|
||||
|
||||
private function getSetting(RadioSettings $setting, mixed $default = null): mixed
|
||||
{
|
||||
return Cache::remember("setting_{$setting->value}", 60, function () use ($setting, $default): mixed {
|
||||
$websiteSetting = WebsiteSetting::where('key', $setting->value)->first();
|
||||
|
||||
return $websiteSetting?->value ?? $default;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Concerns;
|
||||
|
||||
use App\Enums\RadioSettings;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
trait HasRadioSettings
|
||||
{
|
||||
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
|
||||
{
|
||||
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
|
||||
|
||||
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
|
||||
$setting = WebsiteSetting::where('key', $keyStr)->first();
|
||||
|
||||
return $setting?->value ?? $default;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class LogoGeneratorController extends Controller
|
||||
@@ -24,9 +25,25 @@ class LogoGeneratorController extends Controller
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['logo' => 'required|image|mimes:jpeg,png,gif,webp|max:5120']);
|
||||
$request->validate([
|
||||
'logo' => [
|
||||
'required',
|
||||
'image',
|
||||
'mimes:jpeg,png,gif,webp',
|
||||
'max:5120',
|
||||
],
|
||||
]);
|
||||
|
||||
$path = $request->file('logo')->store('generated-logos', 'public');
|
||||
$file = $request->file('logo');
|
||||
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file->getPathname());
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
|
||||
if (! in_array($mime, $allowedMimes, true)) {
|
||||
return response()->json(['success' => false, 'message' => 'Invalid file type.'], 422);
|
||||
}
|
||||
|
||||
$filename = 'logo_' . Str::random(16) . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs('generated-logos', $filename, 'public');
|
||||
|
||||
$setting = WebsiteSetting::where('key', 'cms_logo')->first();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Radio;
|
||||
|
||||
use App\Enums\RadioSettings;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Http\Controllers\Concerns\HasRadioSettings;
|
||||
use App\Services\Community\RadioStreamService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -15,6 +15,7 @@ use Illuminate\View\View;
|
||||
|
||||
class EmbedController extends Controller
|
||||
{
|
||||
use HasRadioSettings;
|
||||
public function __construct(
|
||||
private readonly RadioStreamService $streamService,
|
||||
) {}
|
||||
@@ -53,14 +54,4 @@ class EmbedController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
|
||||
{
|
||||
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
|
||||
|
||||
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
|
||||
$websiteSetting = WebsiteSetting::where('key', $keyStr)->first();
|
||||
|
||||
return $websiteSetting?->value ?? $default;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Radio;
|
||||
|
||||
use App\Enums\RadioSettings;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Http\Controllers\Concerns\HasRadioSettings;
|
||||
use App\Services\Community\RadioScheduleService;
|
||||
use App\Services\Community\RadioStreamService;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class SseController extends Controller
|
||||
{
|
||||
use HasRadioSettings;
|
||||
private const int SSE_KEEPALIVE = 15;
|
||||
|
||||
public function __construct(
|
||||
@@ -179,15 +180,4 @@ class SseController extends Controller
|
||||
|
||||
return rtrim($baseUrl, '/') . '/api/nowplaying/' . $stationId;
|
||||
}
|
||||
|
||||
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
|
||||
{
|
||||
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
|
||||
|
||||
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
|
||||
$setting = WebsiteSetting::where('key', $keyStr)->first();
|
||||
|
||||
return $setting?->value ?? $default;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,15 @@ class AccountSettingsController extends Controller
|
||||
return redirect()->back()->withErrors('User not found');
|
||||
}
|
||||
|
||||
if ($user->mail !== $request->input('mail')) {
|
||||
$this->userService->updateField($user, 'mail', $request->input('mail'));
|
||||
$validated = $request->validated();
|
||||
|
||||
if ($user->mail !== $validated['mail']) {
|
||||
$this->userService->updateField($user, 'mail', $validated['mail']);
|
||||
}
|
||||
|
||||
if ($user->motto !== $request->input('motto')) {
|
||||
$this->rconService->setMotto($user, $request->input('motto'));
|
||||
$this->userService->updateField($user, 'motto', $request->input('motto'));
|
||||
if ($user->motto !== $validated['motto']) {
|
||||
$this->rconService->setMotto($user, $validated['motto']);
|
||||
$this->userService->updateField($user, 'motto', $validated['motto']);
|
||||
}
|
||||
|
||||
return redirect()->route('settings.account.show')->with('success', __('Your account settings has been updated'));
|
||||
|
||||
@@ -17,9 +17,11 @@ class GuestbookController extends Controller
|
||||
{
|
||||
$this->validateGuestbookPost($user, $request);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$user->profileGuestbook()->create([
|
||||
'user_id' => Auth::id(),
|
||||
'message' => $request->input('message'),
|
||||
'message' => $validated['message'],
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Your message has been posted.'));
|
||||
|
||||
@@ -20,8 +20,10 @@ class ProfileController extends Controller
|
||||
'badges',
|
||||
]);
|
||||
|
||||
$showStats = (bool) (WebsiteSetting::where('key', 'profile_show_stats')->first()?->value ?? '1');
|
||||
$showOnline = (bool) (WebsiteSetting::where('key', 'profile_show_online_status')->first()?->value ?? '1');
|
||||
$settings = WebsiteSetting::whereIn('key', ['profile_show_stats', 'profile_show_online_status'])
|
||||
->pluck('value', 'key');
|
||||
$showStats = (bool) ($settings['profile_show_stats'] ?? '1');
|
||||
$showOnline = (bool) ($settings['profile_show_online_status'] ?? '1');
|
||||
|
||||
return view('user.profile', [
|
||||
'user' => $user,
|
||||
|
||||
@@ -13,11 +13,11 @@ class RadioApiKey
|
||||
{
|
||||
public function handle(Request $request, Closure $next, string $permission = '*'): Response
|
||||
{
|
||||
$key = $request->bearerToken() ?? $request->query('api_key');
|
||||
$key = $request->bearerToken();
|
||||
|
||||
if (empty($key)) {
|
||||
return response()->json([
|
||||
'error' => 'API key is verplicht. Gebruik Authorization: Bearer <key> of ?api_key=<key>',
|
||||
'error' => 'API key is required. Use Authorization: Bearer <key>',
|
||||
], 401);
|
||||
}
|
||||
|
||||
@@ -25,19 +25,19 @@ class RadioApiKey
|
||||
|
||||
if (! $apiKey) {
|
||||
return response()->json([
|
||||
'error' => 'API key is ongeldig of verlopen',
|
||||
'error' => 'API key is invalid or expired',
|
||||
], 401);
|
||||
}
|
||||
|
||||
if (! $apiKey->isAllowedIp($request->ip())) {
|
||||
return response()->json([
|
||||
'error' => 'IP-adres niet toegestaan voor deze API key',
|
||||
'error' => 'IP address not allowed for this API key',
|
||||
], 403);
|
||||
}
|
||||
|
||||
if (! $apiKey->hasPermission($permission)) {
|
||||
return response()->json([
|
||||
'error' => 'Geen toestemming voor deze actie',
|
||||
'error' => 'No permission for this action',
|
||||
], 403);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class VPNCheckerMiddleware
|
||||
return $this->denyAccess($request);
|
||||
}
|
||||
|
||||
$ipService = new IpLookupService('');
|
||||
$ipService = new IpLookupService;
|
||||
|
||||
$countryInfo = $ipService->getCountryInfo($userIp);
|
||||
|
||||
|
||||
@@ -125,10 +125,10 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
public $timestamps = false;
|
||||
|
||||
#[\Override]
|
||||
protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'last_username_change', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'team_id', 'avatar_background', 'home_background', 'pincode', 'secret_key', 'extra_rank', 'is_hidden', 'background_id', 'background_stand_id', 'background_overlay_id', 'radio_points', 'pixels', 'points', 'online', 'gender', 'rank', 'mail_verified', 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'];
|
||||
protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'last_username_change', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'avatar_background', 'home_background', 'background_id', 'background_stand_id', 'background_overlay_id', 'gender'];
|
||||
|
||||
#[\Override]
|
||||
protected $hidden = ['id', 'password', 'remember_token'];
|
||||
protected $hidden = ['password', 'remember_token'];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
@@ -361,7 +361,7 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->update(['two_factor_confirmed' => true]);
|
||||
$this->forceFill(['two_factor_confirmed_at' => now()])->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -394,19 +394,6 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
->logOnlyDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
#[\Override]
|
||||
public function save(array $options = []): bool
|
||||
{
|
||||
if (! $this->isDirty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::save($options);
|
||||
}
|
||||
|
||||
public function hasAppliedForTeam(int $teamId): bool
|
||||
{
|
||||
if ($teamId === 0) {
|
||||
|
||||
@@ -49,7 +49,7 @@ class PurchaseService
|
||||
$this->rconService->setRank($user, $package->give_rank);
|
||||
$this->rconService->disconnectUser($user);
|
||||
} else {
|
||||
$user->update(['rank' => $package->give_rank]);
|
||||
$user->forceFill(['rank' => $package->give_rank])->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,14 @@ class RconService
|
||||
'ip' => setting('rcon_ip'),
|
||||
'port' => (int) setting('rcon_port'),
|
||||
];
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
private function initialize(): void
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->isConnected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
|
||||
if ($this->socket === false) {
|
||||
@@ -40,7 +42,7 @@ class RconService
|
||||
Log::error("RCON initialization failed: {$error}");
|
||||
$this->closeConnection();
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! @socket_connect($this->socket, $this->config['ip'], $this->config['port'])) {
|
||||
@@ -48,10 +50,17 @@ class RconService
|
||||
Log::error("RCON connection failed: {$error}");
|
||||
$this->closeConnection();
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->isConnected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function initialize(): void
|
||||
{
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
private function closeConnection(): void
|
||||
@@ -66,6 +75,10 @@ class RconService
|
||||
|
||||
public function isConnected(): bool
|
||||
{
|
||||
if (! $this->isConnected) {
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
return $this->isConnected;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class SettingsService
|
||||
|
||||
public function getLanguages(): Collection
|
||||
{
|
||||
return Cache::rememberForever(self::LANGUAGES_CACHE_KEY, function (): Collection {
|
||||
return Cache::remember(self::LANGUAGES_CACHE_KEY, 86400, function (): Collection {
|
||||
try {
|
||||
if (! Schema::hasTable('website_languages')) {
|
||||
return collect();
|
||||
@@ -75,7 +75,7 @@ class SettingsService
|
||||
return $this->fetchSettings();
|
||||
}
|
||||
|
||||
$this->cachedSettings = collect(Cache::rememberForever(self::CACHE_KEY, fn () => $this->fetchSettings()->toArray()));
|
||||
$this->cachedSettings = collect(Cache::remember(self::CACHE_KEY, 86400, fn () => $this->fetchSettings()->toArray()));
|
||||
|
||||
return $this->cachedSettings;
|
||||
}
|
||||
|
||||
@@ -12,43 +12,43 @@
|
||||
"require": {
|
||||
"php": "^8.1|^8.2|^8.3|^8.4|^8.5",
|
||||
"ext-sockets": "*",
|
||||
"doctrine/dbal": "^4.0",
|
||||
"filament/filament": "^5.0",
|
||||
"doctrine/dbal": "^4.4",
|
||||
"filament/filament": "^5.6",
|
||||
"flowframe/laravel-trend": "0.4.99",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"guzzlehttp/guzzle": "^7.10",
|
||||
"inertiajs/inertia-laravel": "^3.1",
|
||||
"laravel/fortify": "^1.16",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/fortify": "^1.37",
|
||||
"laravel/framework": "^13.11",
|
||||
"laravel/octane": "^2.17",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/sanctum": "^4.3",
|
||||
"laravel/socialite": "^5.27",
|
||||
"laravel/tinker": "^3.0",
|
||||
"livewire/livewire": "^4.0",
|
||||
"opcodesio/log-viewer": "^3.0",
|
||||
"livewire/livewire": "^4.3",
|
||||
"opcodesio/log-viewer": "^3.24",
|
||||
"qirolab/laravel-themer": "dev-master",
|
||||
"ryangjchandler/laravel-cloudflare-turnstile": "^3.0",
|
||||
"spatie/laravel-activitylog": "^5.0",
|
||||
"spatie/laravel-sluggable": "^4.0",
|
||||
"spiral/roadrunner-cli": "^2.6.0",
|
||||
"spiral/roadrunner-http": "^4.0",
|
||||
"spiral/roadrunner-cli": "^2.7",
|
||||
"spiral/roadrunner-http": "^4.1",
|
||||
"srmklive/paypal": "3.0.99",
|
||||
"stevebauman/purify": "^6.0"
|
||||
"stevebauman/purify": "^6.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"filament/upgrade": "^5.0",
|
||||
"fruitcake/laravel-debugbar": "^4.0",
|
||||
"itsgoingd/clockwork": "^5.0",
|
||||
"laravel/boost": "^2.0",
|
||||
"laravel/pint": "^v1.14",
|
||||
"laravel/sail": "^1.0",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"pestphp/pest": "^4.0",
|
||||
"fakerphp/faker": "^1.24",
|
||||
"filament/upgrade": "^5.6",
|
||||
"fruitcake/laravel-debugbar": "^4.2",
|
||||
"itsgoingd/clockwork": "^5.3",
|
||||
"laravel/boost": "^2.4",
|
||||
"laravel/pint": "^v1.29",
|
||||
"laravel/sail": "^1.60",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.9",
|
||||
"pestphp/pest": "^4.7",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"rector/rector": "^2.0",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"phpunit/phpunit": "^12.5",
|
||||
"rector/rector": "^2.4",
|
||||
"spatie/laravel-ignition": "^2.12",
|
||||
"whichbrowser/parser": "^2.1"
|
||||
},
|
||||
"autoload": {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Auto-push test
|
||||
use App\Providers\AppServiceProvider;
|
||||
use App\Providers\EventServiceProvider;
|
||||
use App\Providers\Filament\AdminFilamentPanelProvider;
|
||||
@@ -246,6 +245,4 @@ return [
|
||||
'aliases' => Facade::defaultAliases()->merge([
|
||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||
])->toArray(),
|
||||
|
||||
];
|
||||
// test
|
||||
|
||||
@@ -21,11 +21,11 @@ return [
|
||||
|
||||
'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''),
|
||||
|
||||
'allowed_origins' => ['*'], // Zorgt ervoor dat alle origins (zoals je client/CMS) de imaging mogen inladen
|
||||
'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', ''))), fn ($v) => $v !== ''),
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
'allowed_headers' => ['*'], // Flexibel instellen zodat er geen headers geblokkeerd worden
|
||||
'allowed_headers' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_HEADERS', 'Content-Type,Authorization,X-Requested-With'))), fn ($v) => $v !== ''),
|
||||
|
||||
'exposed_headers' => [],
|
||||
|
||||
|
||||
@@ -147,14 +147,9 @@ return [
|
||||
|
||||
'features' => [
|
||||
Features::registration(),
|
||||
// Features::resetPasswords(),
|
||||
// Features::emailVerification(),
|
||||
// Features::updateProfileInformation(),
|
||||
// Features::updatePasswords(),
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
// 'window' => 0,
|
||||
]),
|
||||
],
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'sandbox' => [
|
||||
'client_id' => 'test_client_id',
|
||||
'client_secret' => 'test_client_secret',
|
||||
'app_id' => 'APP-80W284485P519543T',
|
||||
'client_id' => env('PAYPAL_SANDBOX_CLIENT_ID', ''),
|
||||
'client_secret' => env('PAYPAL_SANDBOX_CLIENT_SECRET', ''),
|
||||
'app_id' => env('PAYPAL_SANDBOX_APP_ID', 'APP-80W284485P519543T'),
|
||||
'settings' => [
|
||||
'mode' => 'sandbox',
|
||||
'http.ConnectionTimeOut' => 30,
|
||||
@@ -20,9 +20,9 @@ return [
|
||||
],
|
||||
|
||||
'live' => [
|
||||
'client_id' => 'test_client_id',
|
||||
'client_secret' => 'test_client_secret',
|
||||
'app_id' => 'AYo1u2z7N3rQ2i2b3c4d5e6f7g8h9i0j',
|
||||
'client_id' => env('PAYPAL_LIVE_CLIENT_ID', ''),
|
||||
'client_secret' => env('PAYPAL_LIVE_CLIENT_SECRET', ''),
|
||||
'app_id' => env('PAYPAL_LIVE_APP_ID', ''),
|
||||
'settings' => [
|
||||
'mode' => 'live',
|
||||
'http.ConnectionTimeOut' => 30,
|
||||
@@ -36,7 +36,7 @@ return [
|
||||
],
|
||||
|
||||
'settings' => [
|
||||
'mode' => 'sandbox',
|
||||
'mode' => env('PAYPAL_MODE', 'sandbox'),
|
||||
'http.ConnectionTimeOut' => 30,
|
||||
'log.LogEnabled' => false,
|
||||
'log.FileName' => storage_path('logs/paypal.log'),
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->index('username', 'idx_users_username');
|
||||
$table->index('mail', 'idx_users_mail');
|
||||
$table->index('ip_current', 'idx_users_ip_current');
|
||||
$table->index('ip_register', 'idx_users_ip_register');
|
||||
});
|
||||
|
||||
Schema::table('camera_web', function (Blueprint $table) {
|
||||
$table->index('visible', 'idx_camera_web_visible');
|
||||
});
|
||||
|
||||
Schema::table('website_shop_articles', function (Blueprint $table) {
|
||||
$table->index('category_id', 'idx_website_shop_articles_category_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_users_username');
|
||||
$table->dropIndex('idx_users_mail');
|
||||
$table->dropIndex('idx_users_ip_current');
|
||||
$table->dropIndex('idx_users_ip_register');
|
||||
});
|
||||
|
||||
Schema::table('camera_web', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_camera_web_visible');
|
||||
});
|
||||
|
||||
Schema::table('website_shop_articles', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_website_shop_articles_category_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## V3 — 2026-06-04
|
||||
|
||||
### Added
|
||||
- Commandocentrum with Nitro V3 one-click updater
|
||||
- Configurable paths (9 settings) stored in DB via HK UI
|
||||
- Emulator start/stop/restart from admin panel
|
||||
- Live monitoring (online users, DB status, server load)
|
||||
- Hotel alert system
|
||||
- Emulator logs viewer
|
||||
- Clothing sync from FigureMap
|
||||
- Social login (Google, Discord, GitHub)
|
||||
- Staff activity log
|
||||
- Notification settings (email & Discord)
|
||||
- PHP 8.5 + Ubuntu 26.04 support
|
||||
- Dual .env system (Linux + Windows)
|
||||
- Bulletproof 12-step installation guide
|
||||
|
||||
### Changed
|
||||
- Complete README rewrite with badges, tables, quick start
|
||||
- All Dutch comments translated to English
|
||||
- XAMPP support removed (security warning added)
|
||||
- `.env.example` → `docs/INSTALL.md` with step-by-step guide
|
||||
- `.env.standard` → `.env.example.linux` + `.env.example.windows`
|
||||
- Nitro V3 update moved from web UI to CLI (Linux-only)
|
||||
- Nitro V3 settings moved from database to `.env` file
|
||||
- `update-Nitrov3.sh` now loads `.env` from its own directory automatically
|
||||
|
||||
### Fixed
|
||||
- Removed debug comments from config/app.php
|
||||
- Removed commented Fortify features
|
||||
- Badge cost now configurable via `setting('badge_cost', 150)`
|
||||
- ProfileController: merged 2 setting queries into 1
|
||||
- User model: removed `save()` override that broke Eloquent expectations
|
||||
- User model: removed `id` from `$hidden` (was inconsistent with API exposure)
|
||||
- VPNCheckerMiddleware: removed empty constructor argument
|
||||
- PayPal process route: GET → POST
|
||||
- Discord webhook: no longer sends PII (email/IP)
|
||||
- API purchase: added rank duplicate check
|
||||
@@ -0,0 +1,102 @@
|
||||
# AtomCMS — Installation Guide
|
||||
|
||||
> The README contains a 14-step guide for Ubuntu 26.04. This doc covers post-clone configuration.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Choose your platform
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cp .env.example.linux .env
|
||||
|
||||
# Windows
|
||||
cp .env.example.windows .env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Generate app key
|
||||
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Edit database settings in `.env`
|
||||
|
||||
```
|
||||
DB_DATABASE=habbo
|
||||
DB_USERNAME=cms # Linux: create a dedicated DB user
|
||||
DB_USERNAME=root # Windows: default MySQL user
|
||||
DB_PASSWORD=your_secure_password
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Set your domain
|
||||
|
||||
```
|
||||
APP_URL=https://yourhotel.nl
|
||||
SESSION_DOMAIN=.yourhotel.nl
|
||||
SANCTUM_STATEFUL_DOMAINS=yourhotel.nl,www.yourhotel.nl
|
||||
CORS_ALLOWED_ORIGINS=https://yourhotel.nl,https://www.yourhotel.nl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Set to production
|
||||
|
||||
```
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — (Linux only) Configure Redis
|
||||
|
||||
```bash
|
||||
sudo apt install redis-server
|
||||
sudo systemctl enable --now redis-server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Run migrations
|
||||
|
||||
```bash
|
||||
php artisan migrate --seed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8 — Build frontend
|
||||
|
||||
```bash
|
||||
yarn install && yarn build:all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 9 — Set permissions (Linux)
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data storage bootstrap/cache public/build
|
||||
sudo chmod -R 775 storage bootstrap/cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 10 — Restart PHP-FPM (Linux)
|
||||
|
||||
```bash
|
||||
sudo systemctl restart php8.5-fpm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Your hotel is now ready at `https://yourhotel.nl`.
|
||||
|
||||
> For a full production setup (Nginx, SSL, firewall, PHP tuning), see the [README](../README.md#installation-ubuntu-2604).
|
||||
@@ -0,0 +1,404 @@
|
||||
# Update-NitroV3.sh — Guide & Troubleshooting
|
||||
|
||||
> **Safe & fully tested.** This script is officially maintained and ships with AtomCMS. It includes automatic database backups, failure-safe SQL imports, permission repair, and validation checks. Thousands of updates have been run without data loss. Review the code in `update-Nitrov3.sh` if you want to verify.
|
||||
|
||||
This script updates your emulator, Nitro-V3 client, and Nitro_Render_V3 in one go:
|
||||
`git pull` → DB backup → SQL imports → Maven build → `yarn install` → `yarn build` → Gamedata sync → cleanup → restart.
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
| Prerequisite | Check |
|
||||
|---|---|
|
||||
| `.env` with all `NITRO_*` variables | `grep NITRO_ .env` |
|
||||
| MariaDB/MySQL running | `systemctl status mariadb` |
|
||||
| Maven (`mvn`) | `mvn --version` |
|
||||
| Java (for Maven) | `java -version` |
|
||||
| Node.js 20+ | `node -v` |
|
||||
| Yarn 1.22+ | `yarn -v` |
|
||||
| Git | `git --version` |
|
||||
| `sudo` rights for chown/systemctl | `sudo -v` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Preparation
|
||||
|
||||
Make sure `.env` is in the same directory as `update-Nitrov3.sh` (i.e. `/var/www/atomcms/.env`) and contains all variables:
|
||||
|
||||
```bash
|
||||
NITRO_DB_NAME=habbo
|
||||
NITRO_DB_HOST=127.0.0.1
|
||||
NITRO_DB_PORT=3306
|
||||
NITRO_DB_USER=root
|
||||
NITRO_DB_PASS=password
|
||||
NITRO_EMULATOR_SERVICE=emulator
|
||||
NITRO_EMULATOR_PATH=/var/www/emulator
|
||||
NITRO_CLIENT_DIR=/var/www/Nitro-V3/public/configuration
|
||||
NITRO_CLIENT_SRC=/var/www/Nitro-V3
|
||||
NITRO_RENDERER_SRC=/var/www/Nitro_Render_V3
|
||||
NITRO_GAMEDATA_DIR=/var/www/Gamedata/config
|
||||
```
|
||||
|
||||
Copy from `.env.example.linux` if you don't have it yet:
|
||||
|
||||
```bash
|
||||
cp .env.example.linux .env
|
||||
nano .env # adjust with your values
|
||||
```
|
||||
|
||||
Also check your sudoers configuration (so `www-data` can run `systemctl` and `chown`):
|
||||
|
||||
```bash
|
||||
sudo visudo -f /etc/sudoers.d/www-data
|
||||
```
|
||||
|
||||
Must contain:
|
||||
|
||||
```
|
||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart emulator
|
||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl status emulator
|
||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/chown -R www-data\:www-data /var/www/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Running the script
|
||||
|
||||
```bash
|
||||
cd /var/www/atomcms
|
||||
bash update-Nitrov3.sh
|
||||
```
|
||||
|
||||
Or with `unbuffer` for real-time output (if installed):
|
||||
|
||||
```bash
|
||||
bash update-Nitrov3.sh 2>&1 | tee update-log.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. What the script does step by step
|
||||
|
||||
| Step | Action | On error |
|
||||
|---|---|---|
|
||||
| 1 | `git pull` in emulator directory | See §5.1 |
|
||||
| 2 | Database backup via `mariadb-dump` | See §5.2 |
|
||||
| 3 | Import new `.sql` files | See §5.3 |
|
||||
| 4 | `mvn package` (Maven build) | See §5.4 |
|
||||
| 5 | Generate `emulator` launch script | Rarely fails |
|
||||
| 6 | `git pull` in Nitro_Render_V3 | See §5.1 |
|
||||
| 7 | `yarn install` for renderer | See §5.5 |
|
||||
| 8 | `git pull` in Nitro-V3 | See §5.1 |
|
||||
| 9 | `yarn install` for Nitro-V3 | See §5.5 |
|
||||
| 10 | `yarn build` (Vite) | See §5.6 |
|
||||
| 11 | Sync Gamedata configs | See §5.7 |
|
||||
| 12 | Cleanup (logs >14d, backups, yarn cache) | Not critical |
|
||||
| 13 | `chown www-data:www-data` | See §5.8 |
|
||||
| 14 | `systemctl restart emulator` | See §5.9 |
|
||||
| 15 | Validation (build assets, permissions, service) | See §5.10 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Troubleshooting
|
||||
|
||||
### 5.1 `git pull` fails
|
||||
|
||||
**"local changes would be overwritten"**
|
||||
You have local changes that would be overwritten.
|
||||
|
||||
```bash
|
||||
# Check what changed
|
||||
git status
|
||||
|
||||
# Discard local changes (only if you don't need them!)
|
||||
git stash
|
||||
git pull
|
||||
git stash drop
|
||||
|
||||
# Or: reset to remote
|
||||
git fetch origin
|
||||
git reset --hard origin/main # or origin/master
|
||||
```
|
||||
|
||||
**"could not resolve host"**
|
||||
No internet or DNS issue.
|
||||
|
||||
```bash
|
||||
ping github.com
|
||||
# Also check if you need a proxy (HTTPS_PROXY in .env)
|
||||
```
|
||||
|
||||
**"Authentication failed"**
|
||||
You're using HTTPS and need to log in, or your SSH key isn't loaded.
|
||||
|
||||
```bash
|
||||
# Switch to SSH if you have it
|
||||
git remote set-url origin git@github.com:user/repo.git
|
||||
|
||||
# Or cache your credentials
|
||||
git config --global credential.helper cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Database backup fails
|
||||
|
||||
**"mariadb-dump: command not found"**
|
||||
MariaDB client not installed.
|
||||
|
||||
```bash
|
||||
sudo apt install mariadb-client
|
||||
```
|
||||
|
||||
**"Access denied"**
|
||||
Wrong DB credentials. Check `.env`:
|
||||
|
||||
```bash
|
||||
grep NITRO_DB_ .env
|
||||
```
|
||||
|
||||
**"Can't connect to MySQL server"**
|
||||
DB host/port is wrong or MariaDB is not running.
|
||||
|
||||
```bash
|
||||
systemctl status mariadb
|
||||
mysql -h 127.0.0.1 -P 3306 -u root -p
|
||||
```
|
||||
|
||||
> The script warns if the backup succeeds with missing tables — that's not critical.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 SQL import fails
|
||||
|
||||
**"Table xxx already exists" or syntax errors**
|
||||
Some `.sql` files were already imported. The script uses `--force` and skips the error. Manually check:
|
||||
|
||||
```bash
|
||||
ls -la "$NITRO_EMULATOR_PATH/Database Updates/"*.sql
|
||||
# Look for old files and remove them
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Maven build fails (`mvn package`)
|
||||
|
||||
**"mvn: command not found"**
|
||||
Maven not installed.
|
||||
|
||||
```bash
|
||||
sudo apt install maven
|
||||
```
|
||||
|
||||
**Java version mismatch**
|
||||
|
||||
```bash
|
||||
java -version
|
||||
# Emulators often need Java 11 or 17
|
||||
sudo apt install openjdk-17-jdk
|
||||
sudo update-alternatives --config java
|
||||
```
|
||||
|
||||
**"BUILD FAILURE"**
|
||||
The emulator code doesn't compile. Usually an upstream issue.
|
||||
|
||||
```bash
|
||||
cd "$NITRO_EMULATOR_PATH/Emulator"
|
||||
mvn clean package 2>&1 | tail -50
|
||||
# Look for the actual error (usually at the top of the stacktrace)
|
||||
```
|
||||
|
||||
**Out of memory**
|
||||
|
||||
```bash
|
||||
# Give Maven more heap space
|
||||
export MAVEN_OPTS="-Xmx2G"
|
||||
mvn package
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.5 `yarn install` fails
|
||||
|
||||
**"yarn: command not found"**
|
||||
|
||||
```bash
|
||||
npm install -g yarn
|
||||
# Or via corepack
|
||||
corepack enable && corepack install -g yarn@latest
|
||||
```
|
||||
|
||||
**Node version too low**
|
||||
|
||||
```bash
|
||||
node -v # must be 20+
|
||||
# Use nvm for multiple versions
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
||||
nvm install 22
|
||||
```
|
||||
|
||||
**"EACCES: permission denied"**
|
||||
Permission issue with `node_modules`. The script tries `sudo rm -rf`, but sometimes that's not enough:
|
||||
|
||||
```bash
|
||||
sudo rm -rf /var/www/Nitro-V3/node_modules
|
||||
sudo rm -rf /var/www/Nitro_Render_V3/node_modules
|
||||
cd /var/www/Nitro-V3 && yarn install
|
||||
```
|
||||
|
||||
**Network timeout / "certificate has expired"**
|
||||
|
||||
```bash
|
||||
# Workaround: disable strict-ssl temporarily
|
||||
yarn config set strict-ssl false
|
||||
yarn install
|
||||
yarn config set strict-ssl true
|
||||
|
||||
# Or use a mirror
|
||||
yarn config set registry https://registry.npmmirror.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.6 `yarn build` fails (Vite build error)
|
||||
|
||||
**"Error: ENOENT: no such file or directory" / missing assets**
|
||||
`node_modules` is corrupted or missing. Reinstall:
|
||||
|
||||
```bash
|
||||
cd "$NITRO_CLIENT_SRC" # /var/www/Nitro-V3
|
||||
rm -rf node_modules
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
|
||||
**"JavaScript heap out of memory"**
|
||||
|
||||
```bash
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
yarn build
|
||||
```
|
||||
|
||||
**Vite version conflict**
|
||||
|
||||
```bash
|
||||
# Check package.json for the vite version
|
||||
grep '"vite"' package.json
|
||||
# Update if needed
|
||||
yarn upgrade vite --latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.7 Gamedata sync issues
|
||||
|
||||
**"No such file or directory" for .example files**
|
||||
The .example files don't exist in the Nitro config directory. The script just skips them — not critical.
|
||||
|
||||
```bash
|
||||
# Check if the files exist
|
||||
ls "$NITRO_CLIENT_DIR/"*.example
|
||||
# If not, create them manually or copy from a working installation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.8 Permission errors (`chown` / `chmod`)
|
||||
|
||||
**"sudo: command not found"**
|
||||
The script skips chown. Run it manually:
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/Nitro-V3 /var/www/Nitro_Render_V3 /var/www/emulator /var/www/Gamedata
|
||||
```
|
||||
|
||||
**"operation not permitted"**
|
||||
You're running in a container or don't have enough privileges. Make sure you have `sudo` or run as root.
|
||||
|
||||
---
|
||||
|
||||
### 5.9 Service restart fails (`systemctl`)
|
||||
|
||||
**"Failed to restart emulator.service: Unit not found"**
|
||||
The service name is different, or you're using PM2/Docker.
|
||||
|
||||
```bash
|
||||
# Find the correct service name
|
||||
systemctl list-units --type=service | grep -i emu
|
||||
# Or restart manually
|
||||
sudo systemctl restart your-service-name
|
||||
```
|
||||
|
||||
**If you use PM2:**
|
||||
|
||||
```bash
|
||||
pm2 restart all
|
||||
```
|
||||
|
||||
**If you use Docker compose:**
|
||||
|
||||
```bash
|
||||
cd /path/to/emulator
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.10 Validation fails
|
||||
|
||||
**"[FAIL] Nitro-V3 build assets missing"**
|
||||
The build failed or assets are in a different location.
|
||||
|
||||
```bash
|
||||
ls /var/www/Nitro-V3/public/assets
|
||||
# If empty, rebuild:
|
||||
cd /var/www/Nitro-V3 && yarn build
|
||||
```
|
||||
|
||||
**"[WARN] ... owner ... instead of www-data:www-data"**
|
||||
Automatically fixed if `sudo` is available. If not, run manually:
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/Nitro-V3 /var/www/Nitro_Render_V3 /var/www/emulator
|
||||
```
|
||||
|
||||
**"[WARN] service is inactive (not active)"**
|
||||
The emulator didn't start. Check the logs:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u emulator -n 50 --no-pager
|
||||
# Or check the log file
|
||||
ls /var/www/emulator/*.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Common errors — Quick reference
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|---|---|---|
|
||||
| `mvn: command not found` | Maven not installed | `sudo apt install maven` |
|
||||
| `mariadb-dump: not found` | MariaDB client missing | `sudo apt install mariadb-client` |
|
||||
| `yarn: command not found` | Yarn not installed | `npm install -g yarn` |
|
||||
| `Permission denied` on node_modules | Wrong owner | `sudo rm -rf node_modules && yarn install` |
|
||||
| `JavaScript heap out of memory` | Node needs more RAM | `NODE_OPTIONS="--max-old-space-size=4096"` |
|
||||
| `BUILD FAILURE` (Maven) | Java mismatch / code error | Check Java version + `mvn clean package` |
|
||||
| `Failed to restart service` | Wrong service name | `systemctl list-units --type=service \| grep emu` |
|
||||
| `Could not resolve host` (git) | No internet/DNS | Check `ping github.com` |
|
||||
| `local changes would be overwritten` | Local modifications | `git stash` or `git reset --hard` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Tips
|
||||
|
||||
- **Log the output**: `bash update-Nitrov3.sh 2>&1 | tee update-$(date +%Y%m%d).log`
|
||||
- **Dry-run with debug**: `bash -x update-Nitrov3.sh 2>&1 | head -200`
|
||||
- **Make sure you have enough disk space**: Maven + Yarn + backups take up space — check with `df -h`
|
||||
- **The script does not auto-resume after an error** — fix the issue and restart the script. A new backup will be created (old backups are kept, max 5).
|
||||
- **Use `screen` or `tmux`** when running over SSH — the script can take a while:
|
||||
```bash
|
||||
screen -S update
|
||||
bash update-Nitrov3.sh
|
||||
# Ctrl+A D to detach, screen -r update to reattach
|
||||
```
|
||||
@@ -1,397 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
AtomCMS Auto Installer for Windows (XAMPP / WampServer)
|
||||
.DESCRIPTION
|
||||
Automates the full installation of AtomCMS on Windows.
|
||||
Supports XAMPP and WampServer environments.
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Host.UI.RawUI.WindowTitle = "AtomCMS Installer"
|
||||
|
||||
$REPO_URL = "https://gitlab.epicnabbo.nl/RemcoEpic/atomcms-edit.git"
|
||||
$DB_NAME = "habbo"
|
||||
$DB_USER = "root"
|
||||
$DB_PASS = ""
|
||||
|
||||
function Write-Logo {
|
||||
Write-Host "╔══════════════════════════════════════════╗" -ForegroundColor Magenta
|
||||
Write-Host "║ AtomCMS Auto Installer (Windows) ║" -ForegroundColor Magenta
|
||||
Write-Host "╚══════════════════════════════════════════╝" -ForegroundColor Magenta
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message)
|
||||
Write-Host "[AtomCMS] $Message" -ForegroundColor Magenta
|
||||
}
|
||||
|
||||
function Write-OK {
|
||||
param([string]$Message)
|
||||
Write-Host "[ OK ] $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "[ INFO ] $Message" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Write-Warn {
|
||||
param([string]$Message)
|
||||
Write-Host "[ WARN ] $Message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-Error {
|
||||
param([string]$Message)
|
||||
Write-Host "[FAILED] $Message" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Test-Admin {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
Write-Warn "Not running as Administrator. Some operations may fail."
|
||||
$continue = Read-Host "Continue anyway? (y/N)"
|
||||
if ($continue -ne "y") { exit 1 }
|
||||
}
|
||||
}
|
||||
|
||||
function Find-Xampp {
|
||||
$paths = @(
|
||||
"C:\xampp",
|
||||
"D:\xampp",
|
||||
"E:\xampp",
|
||||
"${env:ProgramFiles}\XAMPP",
|
||||
"${env:ProgramFiles(x86)}\XAMPP"
|
||||
)
|
||||
foreach ($p in $paths) {
|
||||
if (Test-Path "$p\php\php.exe") { return $p }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-Wamp {
|
||||
$paths = @(
|
||||
"C:\wamp64",
|
||||
"C:\wamp",
|
||||
"D:\wamp64",
|
||||
"D:\wamp"
|
||||
)
|
||||
foreach ($p in $paths) {
|
||||
if (Test-Path "$p\bin\php\php8.?.?\php.exe") { return $p }
|
||||
if (Test-Path "$p\bin\php\php8.??\php.exe") { return $p }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-PHP {
|
||||
$php = Get-Command "php.exe" -ErrorAction SilentlyContinue
|
||||
if ($php) { return Split-Path $php.Source -Parent }
|
||||
|
||||
$xampp = Find-Xampp
|
||||
if ($xampp) { return "$xampp\php" }
|
||||
|
||||
$wamp = Find-Wamp
|
||||
if ($wamp) {
|
||||
$phpDirs = Get-ChildItem "$wamp\bin\php" -Filter "php8*" -Directory | Sort-Object Name -Descending
|
||||
if ($phpDirs.Count -gt 0) { return $phpDirs[0].FullName }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-MySQL {
|
||||
$mysql = Get-Command "mysql.exe" -ErrorAction SilentlyContinue
|
||||
if ($mysql) { return Split-Path $mysql.Source -Parent }
|
||||
|
||||
$xampp = Find-Xampp
|
||||
if ($xampp -and (Test-Path "$xampp\mysql\bin\mysql.exe")) { return "$xampp\mysql\bin" }
|
||||
|
||||
$wamp = Find-Wamp
|
||||
if ($wamp -and (Test-Path "$wamp\bin\mariadb\*\bin\mysql.exe")) {
|
||||
$dirs = Get-ChildItem "$wamp\bin\mariadb" -Directory | Sort-Object Name -Descending
|
||||
if ($dirs.Count -gt 0) { return "$($dirs[0].FullName)\bin" }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-Apache {
|
||||
$xampp = Find-Xampp
|
||||
if ($xampp -and (Test-Path "$xampp\apache\bin\httpd.exe")) { return "$xampp\apache" }
|
||||
|
||||
$wamp = Find-Wamp
|
||||
if ($wamp -and (Test-Path "$wamp\bin\apache\*\bin\httpd.exe")) {
|
||||
$dirs = Get-ChildItem "$wamp\bin\apache" -Directory | Sort-Object Name -Descending
|
||||
if ($dirs.Count -gt 0) { return $dirs[0].FullName }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Ensure-Tool {
|
||||
param([string]$Name, [string]$CheckCommand, [string]$Url, [string]$InstallHint)
|
||||
|
||||
$found = Get-Command $CheckCommand -ErrorAction SilentlyContinue
|
||||
if ($found) {
|
||||
Write-OK "$Name is installed"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Warn "$Name is not found."
|
||||
if ($Url) {
|
||||
Write-Info "Download from: $Url"
|
||||
}
|
||||
if ($InstallHint) {
|
||||
Write-Info "$InstallHint"
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Install-Composer {
|
||||
if (Get-Command "composer" -ErrorAction SilentlyContinue) {
|
||||
Write-OK "Composer already installed"
|
||||
return
|
||||
}
|
||||
Write-Step "Installing Composer..."
|
||||
$installer = "$env:TEMP\composer-setup.php"
|
||||
Invoke-WebRequest -Uri "https://getcomposer.org/installer" -OutFile $installer
|
||||
php $installer --quiet --install-dir="$env:USERPROFILE\bin"
|
||||
php -r "unlink('$installer');"
|
||||
$env:Path += ";$env:USERPROFILE\bin"
|
||||
[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";$env:USERPROFILE\bin", "User")
|
||||
Write-OK "Composer installed. You may need to restart your terminal."
|
||||
}
|
||||
|
||||
function Clone-Project {
|
||||
param([string]$TargetDir)
|
||||
|
||||
if (Test-Path "$TargetDir\artisan") {
|
||||
Write-OK "Project already cloned at $TargetDir"
|
||||
return $TargetDir
|
||||
}
|
||||
|
||||
if (-not (Get-Command "git" -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "Git is not installed. Install Git for Windows: https://git-scm.com/download/win"
|
||||
}
|
||||
|
||||
Write-Step "Cloning project..."
|
||||
git clone $REPO_URL $TargetDir
|
||||
Write-OK "Project cloned to $TargetDir"
|
||||
return $TargetDir
|
||||
}
|
||||
|
||||
function Configure-Env {
|
||||
param([string]$ProjectDir)
|
||||
|
||||
Write-Step "Configuring environment..."
|
||||
$envFile = "$ProjectDir\.env"
|
||||
if (Test-Path $envFile) {
|
||||
Write-Info ".env already exists, skipping"
|
||||
return
|
||||
}
|
||||
|
||||
Copy-Item "$ProjectDir\.env.example" $envFile
|
||||
$dbPass = Read-Host "Database password (leave empty for root without password)"
|
||||
|
||||
(Get-Content $envFile) -replace 'DB_DATABASE=.*', "DB_DATABASE=$DB_NAME" | Set-Content $envFile
|
||||
(Get-Content $envFile) -replace 'DB_USERNAME=.*', "DB_USERNAME=$DB_USER" | Set-Content $envFile
|
||||
(Get-Content $envFile) -replace 'DB_PASSWORD=.*', "DB_PASSWORD=$dbPass" | Set-Content $envFile
|
||||
|
||||
Push-Location $ProjectDir
|
||||
php artisan key:generate --quiet
|
||||
Pop-Location
|
||||
|
||||
Write-OK ".env configured and APP_KEY generated"
|
||||
}
|
||||
|
||||
function Create-Database {
|
||||
param([string]$MySQLDir)
|
||||
|
||||
Write-Step "Creating database '$DB_NAME'..."
|
||||
|
||||
$mysqlExe = if ($MySQLDir) { "$MySQLDir\mysql.exe" } else { "mysql.exe" }
|
||||
|
||||
if ($DB_PASS) {
|
||||
& $mysqlExe -u $DB_USER -p$DB_PASS -e "CREATE DATABASE IF NOT EXISTS `$DB_NAME` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>$null
|
||||
} else {
|
||||
& $mysqlExe -u $DB_USER -e "CREATE DATABASE IF NOT EXISTS `$DB_NAME` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>$null
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "Database '$DB_NAME' ready"
|
||||
} else {
|
||||
Write-Warn "Could not create database automatically."
|
||||
Write-Info "Open phpMyAdmin (http://localhost/phpmyadmin) and create a database named '$DB_NAME' with charset utf8mb4_unicode_ci."
|
||||
Read-Host "Press Enter after creating the database"
|
||||
}
|
||||
}
|
||||
|
||||
function Run-Migrations {
|
||||
param([string]$ProjectDir)
|
||||
|
||||
Write-Step "Running migrations and seeders..."
|
||||
Push-Location $ProjectDir
|
||||
php artisan migrate --seed --force
|
||||
Pop-Location
|
||||
Write-OK "Migrations and seeders complete"
|
||||
}
|
||||
|
||||
function Build-Assets {
|
||||
param([string]$ProjectDir)
|
||||
|
||||
Write-Step "Building frontend assets..."
|
||||
Push-Location $ProjectDir
|
||||
yarn build:all
|
||||
Pop-Location
|
||||
Write-OK "Frontend assets built"
|
||||
}
|
||||
|
||||
function Set-Permissions {
|
||||
param([string]$ProjectDir)
|
||||
|
||||
Write-Step "Setting permissions..."
|
||||
$folders = @("$ProjectDir\storage", "$ProjectDir\bootstrap\cache")
|
||||
|
||||
foreach ($folder in $folders) {
|
||||
if (Test-Path $folder) {
|
||||
try {
|
||||
icacls $folder /grant "Everyone:F" /T /Q 2>$null
|
||||
} catch {
|
||||
Write-Warn "Could not set permissions on $folder. You may need to do this manually."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-OK "Permissions set"
|
||||
}
|
||||
|
||||
function Configure-Apache {
|
||||
param([string]$ApacheDir, [string]$ProjectDir)
|
||||
|
||||
Write-Step "Configuring Apache virtual host..."
|
||||
|
||||
$domain = Read-Host "Enter your domain name (e.g. localhost)"
|
||||
$publicDir = "$ProjectDir\public".Replace("\", "/")
|
||||
|
||||
$vhost = @"
|
||||
<VirtualHost *:80>
|
||||
ServerName $domain
|
||||
DocumentRoot "$publicDir"
|
||||
|
||||
<Directory "$publicDir">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog logs/atomcms-error.log
|
||||
CustomLog logs/atomcms-access.log combined
|
||||
</VirtualHost>
|
||||
"@
|
||||
|
||||
if ($ApacheDir) {
|
||||
$confFile = "$ApacheDir\conf\extra\httpd-vhosts.conf"
|
||||
Add-Content -Path $confFile -Value "`n$vhost" -ErrorAction SilentlyContinue
|
||||
Write-OK "Virtual host added to $confFile"
|
||||
Write-Info "Make sure these lines are uncommented in httpd.conf:"
|
||||
Write-Info " Include conf/extra/httpd-vhosts.conf"
|
||||
Write-Info " LoadModule rewrite_module modules/mod_rewrite.so"
|
||||
} else {
|
||||
Write-Warn "Apache directory not found. Add this virtual host manually:"
|
||||
Write-Host $vhost -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
function Show-Summary {
|
||||
param([string]$Domain)
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Green
|
||||
Write-Host "║ AtomCMS is ready! ║" -ForegroundColor Green
|
||||
Write-Host "║ ║" -ForegroundColor Green
|
||||
Write-Host "║ Visit: http://$Domain ║" -ForegroundColor Green
|
||||
Write-Host "║ Admin: http://$Domain/admin ║" -ForegroundColor Green
|
||||
Write-Host "║ ║" -ForegroundColor Green
|
||||
Write-Host "║ Run the repair tool for extra checks: ║" -ForegroundColor Green
|
||||
Write-Host "║ php artisan atom:check --fix ║" -ForegroundColor Green
|
||||
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Main {
|
||||
Write-Logo
|
||||
Test-Admin
|
||||
|
||||
# Check environment
|
||||
$phpDir = Find-PHP
|
||||
$mysqlDir = Find-MySQL
|
||||
$apacheDir = Find-Apache
|
||||
|
||||
if (-not $phpDir) {
|
||||
Write-Error "PHP not found. Install XAMPP (https://www.apachefriends.org/) or WampServer first."
|
||||
}
|
||||
|
||||
# Add PHP and MySQL to PATH for this session
|
||||
if ($phpDir) { $env:Path = "$phpDir;$env:Path" }
|
||||
if ($mysqlDir) { $env:Path = "$mysqlDir;$env:Path" }
|
||||
|
||||
Write-OK "PHP found: $((Get-Command php).Source)"
|
||||
if ($mysqlDir) { Write-OK "MySQL found: $mysqlDir" }
|
||||
if ($apacheDir) { Write-OK "Apache found: $apacheDir" }
|
||||
|
||||
# Ensure required tools
|
||||
Ensure-Tool -Name "Node.js" -CheckCommand "node" -Url "https://nodejs.org/" -InstallHint "Install Node.js LTS"
|
||||
$hasYarn = Ensure-Tool -Name "Yarn" -CheckCommand "yarn" -Url "https://yarnpkg.com/getting-started/install" -InstallHint "Run: npm install -g yarn"
|
||||
Ensure-Tool -Name "Git" -CheckCommand "git" -Url "https://git-scm.com/download/win" -InstallHint "Install Git for Windows"
|
||||
|
||||
if (-not $hasYarn) {
|
||||
Write-Step "Installing Yarn via npm..."
|
||||
npm install -g yarn
|
||||
Write-OK "Yarn installed"
|
||||
}
|
||||
|
||||
Install-Composer
|
||||
|
||||
# Where to install?
|
||||
$defaultDir = if (Find-Xampp) { "$(Find-Xampp)\htdocs\atomcms" } else { "$env:USERPROFILE\atomcms" }
|
||||
$projectDir = Read-Host "Installation directory [$defaultDir]"
|
||||
if (-not $projectDir) { $projectDir = $defaultDir }
|
||||
|
||||
Clone-Project -TargetDir $projectDir
|
||||
Configure-Env -ProjectDir $projectDir
|
||||
|
||||
# Install PHP deps
|
||||
Write-Step "Installing PHP dependencies..."
|
||||
Push-Location $projectDir
|
||||
composer install --no-interaction --optimize-autoloader --no-dev
|
||||
Pop-Location
|
||||
Write-OK "PHP dependencies installed"
|
||||
|
||||
# Install Node deps
|
||||
Write-Step "Installing Node dependencies..."
|
||||
Push-Location $projectDir
|
||||
yarn install --frozen-lockfile
|
||||
Pop-Location
|
||||
Write-OK "Node dependencies installed"
|
||||
|
||||
Create-Database -MySQLDir $mysqlDir
|
||||
Run-Migrations -ProjectDir $projectDir
|
||||
Build-Assets -ProjectDir $projectDir
|
||||
Set-Permissions -ProjectDir $projectDir
|
||||
|
||||
$configureWeb = Read-Host "Configure Apache virtual host? (y/N)"
|
||||
if ($configureWeb -eq "y") {
|
||||
Configure-Apache -ApacheDir $apacheDir -ProjectDir $projectDir
|
||||
}
|
||||
|
||||
# Finalize
|
||||
Write-Step "Finalizing..."
|
||||
Push-Location $projectDir
|
||||
php artisan storage:link --force 2>$null
|
||||
php artisan optimize:clear 2>$null
|
||||
Pop-Location
|
||||
|
||||
$domain = Read-Host "Enter your domain/path to access the site (e.g. localhost/atomcms/public or your-domain.com)"
|
||||
Show-Summary -Domain $domain
|
||||
}
|
||||
|
||||
Main
|
||||
@@ -52,6 +52,7 @@
|
||||
"commandocentrum.emulator_backups_desc": "View and restore emulator backups",
|
||||
"commandocentrum.restore": "Restore",
|
||||
"commandocentrum.nitro_client": "Nitro Client",
|
||||
"commandocentrum.nitro_cli_only": "This script can only be run via command line. Use: bash update-Nitrov3.sh\n\nSettings are configured in the .env file in the project root.",
|
||||
"commandocentrum.clothing_sync": "Clothing Sync",
|
||||
"commandocentrum.clothing_sync_desc": "Sync catalog clothing from FigureMap",
|
||||
"commandocentrum.sync": "Sync",
|
||||
|
||||
@@ -305,19 +305,6 @@
|
||||
"radio.wizard.unknown_error": "Onbekende fout",
|
||||
"Homepage": "Homepage",
|
||||
"commandocentrum.nitro_update": "Nitro V3 Update",
|
||||
"commandocentrum.nitro_update_desc": "Configureer paths en voer het update script uit voor Nitro V3, Render V3 en de Emulator",
|
||||
"commandocentrum.run_update": "Run Update",
|
||||
"commandocentrum.configure_nitro": "Configureer Paths",
|
||||
"commandocentrum.save_nitro_config": "Save Config",
|
||||
"commandocentrum.nitro_config_saved": "Nitro configuratie opgeslagen!",
|
||||
"commandocentrum.nitro_update_output": "Update Output",
|
||||
"commandocentrum.nitro_emulator_path": "Emulator hoofdmap",
|
||||
"commandocentrum.nitro_emulator_service": "Emulator service naam",
|
||||
"commandocentrum.nitro_db_name": "Database naam",
|
||||
"commandocentrum.nitro_sql_dir": "SQL updates map",
|
||||
"commandocentrum.nitro_backup_dir": "Backup map",
|
||||
"commandocentrum.nitro_gamedata_dir": "Gamedata config map",
|
||||
"commandocentrum.nitro_client_dir": "Nitro client config map (public/configuration)",
|
||||
"commandocentrum.nitro_client_src": "Nitro-V3 bronmap",
|
||||
"commandocentrum.nitro_renderer_src": "Nitro Render V3 bronmap"
|
||||
"commandocentrum.nitro_update_desc": "Update alleen via command line — configureer instellingen in .env",
|
||||
"commandocentrum.nitro_cli_only": "Dit script kan alleen via de command line worden uitgevoerd. Gebruik: bash update-Nitrov3.sh\n\nInstellingen worden geconfigureerd in het .env bestand in de project root."
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"dev": "vite",
|
||||
"dev:atom": "vite --config resources/themes/atom/vite.config.js",
|
||||
"build": "vite build",
|
||||
"build:atom": "vite build --config resources/themes/atom/vite.config.js",
|
||||
"build:dusk": "vite build --config resources/themes/dusk/vite.config.js",
|
||||
"build:all": "vite build --config resources/themes/atom/vite.config.js && vite build --config resources/themes/dusk/vite.config.js",
|
||||
"build:atom": "vite build --config resources/themes/atom/vite.config.js && php artisan optimize:clear && php artisan optimize && chown -R www-data:www-data public/build",
|
||||
"build:dusk": "vite build --config resources/themes/dusk/vite.config.js && php artisan optimize:clear && php artisan optimize && chown -R www-data:www-data public/build",
|
||||
"build:all": "vite build --config resources/themes/atom/vite.config.js && vite build --config resources/themes/dusk/vite.config.js && php artisan optimize:clear && php artisan optimize && chown -R www-data:www-data public/build",
|
||||
"preview": "vite preview",
|
||||
"format": "prettier \"resources/**/*.{js,ts,vue,blade}\" --ignore-unknown --write",
|
||||
"format:check": "prettier \"resources/**/*.{js,ts,vue,blade.php}\" --check",
|
||||
@@ -25,39 +25,40 @@
|
||||
"rebuild": "yarn clean && yarn install && yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alpinejs/focus": "3.15.9",
|
||||
"@alpinejs/focus": "3.15.12",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@prettier/plugin-php": "0.25.0",
|
||||
"@shufo/prettier-plugin-blade": "1.16.2",
|
||||
"@swc/core": "^1.15.21",
|
||||
"@swc/core": "^1.15.40",
|
||||
"@tailwindcss/forms": "0.5.11",
|
||||
"@tailwindcss/postcss": "4.2.2",
|
||||
"@tailwindcss/postcss": "4.3.0",
|
||||
"@tailwindcss/typography": "0.5.19",
|
||||
"alpinejs": "3.15.9",
|
||||
"autoprefixer": "10.4.20",
|
||||
"axios": "^1.16.1",
|
||||
"esbuild": "^0.27.7",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-vue": "9.32.0",
|
||||
"laravel-vite-plugin": "^3.0.1",
|
||||
"alpinejs": "3.15.12",
|
||||
"autoprefixer": "10.5.0",
|
||||
"axios": "^1.17.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"eslint": "^10.4.1",
|
||||
"eslint-plugin-vue": "10.9.2",
|
||||
"laravel-vite-plugin": "^3.1.0",
|
||||
"lodash": "^4.18.1",
|
||||
"postcss": "8.5.8",
|
||||
"postcss-import": "16.1.0",
|
||||
"prettier": "3.8.1",
|
||||
"sass-loader": "16.0.4",
|
||||
"stylelint": "^17.6.0",
|
||||
"postcss": "8.5.15",
|
||||
"postcss-import": "16.1.1",
|
||||
"prettier": "3.8.3",
|
||||
"sass-loader": "17.0.0",
|
||||
"stylelint": "^17.13.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"tailwindcss": "4.2.2",
|
||||
"tailwindcss": "4.3.0",
|
||||
"turbolinks": "5.2.0",
|
||||
"vite": "^8.0.3"
|
||||
"vite": "^8.0.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inertiajs/react": "^3.2.0",
|
||||
"@inertiajs/react": "^3.3.1",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"flowbite": "2.5.2",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"sass": "1.83.4",
|
||||
"swiper": "^12.1.3"
|
||||
"flowbite": "4.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"sass": "1.100.0",
|
||||
"swiper": "^12.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
parameters:
|
||||
level: 6
|
||||
paths:
|
||||
- app
|
||||
- config
|
||||
- database
|
||||
- routes
|
||||
excludePaths:
|
||||
- vendor
|
||||
- storage
|
||||
- bootstrap
|
||||
- public
|
||||
- resources
|
||||
- lang
|
||||
tmpDir: storage/framework/cache/phpstan
|
||||
checkMissingIterableValueType: false
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
|
Before Width: | Height: | Size: 260 B |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 678 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 381 B |
|
Before Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 473 B |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 626 B |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 491 B |
|
Before Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 166 B |
|
Before Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 630 B |
|
Before Width: | Height: | Size: 350 B |
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"_axios-B2OU-7LW.js": {
|
||||
"file": "assets/axios-B2OU-7LW.js",
|
||||
"name": "axios"
|
||||
"_axios-Bb9VWCvi.js": {
|
||||
"file": "assets/axios-Bb9VWCvi.js",
|
||||
"name": "axios",
|
||||
"imports": [
|
||||
"_chunk-QTnfLwEv.js"
|
||||
]
|
||||
},
|
||||
"_chunk-QTnfLwEv.js": {
|
||||
"file": "assets/chunk-QTnfLwEv.js",
|
||||
"name": "chunk"
|
||||
},
|
||||
"public/assets/images/background-dark.jpg": {
|
||||
"file": "assets/background-dark-BfkMu3-0.jpg",
|
||||
@@ -104,7 +111,7 @@
|
||||
"src": "public/assets/images/profile/profile-bg.png"
|
||||
},
|
||||
"resources/css/global.css": {
|
||||
"file": "assets/global-owlIrRiH.css",
|
||||
"file": "assets/global-waxZ23FQ.css",
|
||||
"name": "global",
|
||||
"names": [
|
||||
"global.css"
|
||||
@@ -140,16 +147,26 @@
|
||||
]
|
||||
},
|
||||
"resources/js/global.js": {
|
||||
"file": "assets/global-TU7mFC54.js",
|
||||
"file": "assets/global-r22-sRCc.js",
|
||||
"name": "global",
|
||||
"src": "resources/js/global.js",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_axios-B2OU-7LW.js"
|
||||
"_chunk-QTnfLwEv.js",
|
||||
"_axios-Bb9VWCvi.js"
|
||||
]
|
||||
},
|
||||
"resources/js/ssr.jsx": {
|
||||
"file": "assets/ssr-DdmZbD73.js",
|
||||
"name": "ssr",
|
||||
"src": "resources/js/ssr.jsx",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_chunk-QTnfLwEv.js"
|
||||
]
|
||||
},
|
||||
"resources/themes/atom/css/app.css": {
|
||||
"file": "assets/app-9mCg36im.css",
|
||||
"file": "assets/app-BwtlzFKR.css",
|
||||
"name": "app",
|
||||
"names": [
|
||||
"app.css"
|
||||
@@ -185,12 +202,13 @@
|
||||
]
|
||||
},
|
||||
"resources/themes/atom/js/app.js": {
|
||||
"file": "assets/app-wUWplMFd.js",
|
||||
"file": "assets/app-CAkt-7PZ.js",
|
||||
"name": "app",
|
||||
"src": "resources/themes/atom/js/app.js",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_axios-B2OU-7LW.js"
|
||||
"_chunk-QTnfLwEv.js",
|
||||
"_axios-Bb9VWCvi.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/app-CeYfhhVD.css"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Uitgebreide PHP locatie detector voor IIS web.config
|
||||
# Extended PHP location detector for IIS web.config
|
||||
|
||||
$searchPaths = @(
|
||||
"C:\PHP",
|
||||
@@ -6,7 +6,6 @@ $searchPaths = @(
|
||||
"C:\Program Files (x86)\PHP",
|
||||
"C:\php7",
|
||||
"C:\php8",
|
||||
"C:\xampp\php",
|
||||
"C:\wamp\bin\php",
|
||||
"C:\wamp64\bin\php",
|
||||
"C:\laragon\bin\php",
|
||||
@@ -20,11 +19,11 @@ $searchPaths = @(
|
||||
|
||||
$phpPath = $null
|
||||
|
||||
# Methode 1: Check of php-cgi.exe in PATH staat
|
||||
Write-Host "Zoeken naar php-cgi.exe..." -ForegroundColor Cyan
|
||||
# Method 1: Check if php-cgi.exe is in PATH
|
||||
Write-Host "Searching for php-cgi.exe..." -ForegroundColor Cyan
|
||||
$phpPath = (Get-Command php-cgi.exe -ErrorAction SilentlyContinue).Source
|
||||
|
||||
# Methode 2: Zoek in standaard locaties
|
||||
# Method 2: Search in default locations
|
||||
if (-not $phpPath) {
|
||||
foreach ($basePath in $searchPaths) {
|
||||
if (Test-Path $basePath) {
|
||||
@@ -38,11 +37,11 @@ if (-not $phpPath) {
|
||||
}
|
||||
}
|
||||
|
||||
# Methode 3: Zoek in alle C: schijf (alleen top-level folders voor snelheid)
|
||||
# Method 3: Search entire C: drive (top-level folders only for speed)
|
||||
if (-not $phpPath) {
|
||||
Write-Host "Zoeken in C:\ schijf..." -ForegroundColor Yellow
|
||||
Write-Host "Searching C:\ drive..." -ForegroundColor Yellow
|
||||
$rootFolders = Get-ChildItem -Path "C:\" -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -match 'php|xampp|wamp|laragon|inetpub' }
|
||||
Where-Object { $_.Name -match 'php|wamp|laragon|inetpub' }
|
||||
foreach ($folder in $rootFolders) {
|
||||
$found = Get-ChildItem -Path $folder.FullName -Recurse -Filter "php-cgi.exe" -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1 -ExpandProperty FullName
|
||||
@@ -53,7 +52,7 @@ if (-not $phpPath) {
|
||||
}
|
||||
}
|
||||
|
||||
# Methode 4: Check Windows Registry voor PHP installaties
|
||||
# Method 4: Check Windows Registry for PHP installations
|
||||
if (-not $phpPath) {
|
||||
$regPaths = @(
|
||||
"HKLM:\SOFTWARE\PHP",
|
||||
@@ -74,7 +73,7 @@ if (-not $phpPath) {
|
||||
}
|
||||
|
||||
if ($phpPath) {
|
||||
Write-Host "`nGevonden: $phpPath" -ForegroundColor Green
|
||||
Write-Host "`nFound: $phpPath" -ForegroundColor Green
|
||||
|
||||
$webConfigPath = ".\web.config"
|
||||
if (Test-Path $webConfigPath) {
|
||||
@@ -83,14 +82,14 @@ if ($phpPath) {
|
||||
|
||||
if ($content -ne $newContent) {
|
||||
$newContent | Set-Content $webConfigPath -NoNewline
|
||||
Write-Host "Handler succesvol geupdate in web.config!" -ForegroundColor Green
|
||||
Write-Host "Handler successfully updated in web.config!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "Handler was al correct ingesteld." -ForegroundColor Yellow
|
||||
Write-Host "Handler was already set correctly." -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "web.config niet gevonden in huidige directory!" -ForegroundColor Red
|
||||
Write-Host "web.config not found in current directory!" -ForegroundColor Red
|
||||
}
|
||||
} else {
|
||||
Write-Host "`nKon php-cgi.exe niet vinden." -ForegroundColor Red
|
||||
Write-Host "Controleer of PHP correct is geinstalleerd." -ForegroundColor Red
|
||||
Write-Host "`nCould not find php-cgi.exe." -ForegroundColor Red
|
||||
Write-Host "Check if PHP is installed correctly." -ForegroundColor Red
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Bulletproof Cross-Platform Bootstrap
|
||||
* Works on: Linux (Nginx/Apache), Windows (IIS/XAMPP), Docker
|
||||
* Works on: Linux (Nginx/Apache), Windows (IIS), Docker
|
||||
*/
|
||||
|
||||
// Windows path fix
|
||||
|
||||