Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 3 additions & 47 deletions .github/workflows/publish-docker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ env:


jobs:
build-nginx:
build:
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -46,7 +46,7 @@ jobs:
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-nginx
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
type=raw,value=develop,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }}
Expand All @@ -56,51 +56,7 @@ jobs:
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
file: docker/production/Dockerfile.nginx
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

build-php-fpm:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-php-fpm
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
type=raw,value=develop,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }}
type=semver,pattern={{version}}

- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
file: docker/production/Dockerfile.php-fpm
file: docker/production/Dockerfile
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
Expand Down
137 changes: 46 additions & 91 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ These are using Laravel Socialite, so any provider supported by Socialite can be

These are custom integrations but more can be added and used if people develop them. The Internal provider allows you to manually issue tickets to users.

## Setup

## Technology

This project is written in PHP 8.4 using the Laravel 12 framework. It was migrated from Laravel 10 and 11 so has some
legacy project structure - but this is the intended upgrade path.

Horizon and Telescope are installed and enabled, with access limited to the admin role. The application itself is
served using Laravel Octane and FrankenPHP.

Websocket communications are handled using Laravel Reverb.

## Development Setup

You will need to create a Discord application and have the Client ID and Client Secret available.

Expand All @@ -43,108 +54,52 @@ You should now be able to login. The first user will be given the admin role.

## Production Deployment

I use the following docker-compose for running this in production:

```yaml
version: '3'
services:
nginx:
image: ghcr.io/mintopia/control-nginx:develop
env_file: .env.nginx
restart: unless-stopped
depends_on:
- php-fpm
networks:
- frontend
- default
volumes:
- ./public:/var/www/storage/public

php-fpm:
image: ghcr.io/mintopia/control-php-fpm:develop
env_file: .env
restart: unless-stopped
depends_on:
- redis
- database
volumes:
- ./logs:/var/www/storage/logs
- ./public:/var/www/storage/public

redis:
image: redis:6.2.6
restart: unless-stopped

database:
image: mariadb:10.5-focal
env_file: .env.mariadb
restart: unless-stopped
volumes:
- ./database:/var/lib/mysql

worker:
image: ghcr.io/mintopia/control-php-fpm:develop
restart: unless-stopped
deploy:
replicas: 2
env_file: .env
depends_on:
- database
- redis
volumes:
- ./logs:/var/www/storage/logs
- ./public:/var/www/storage/public
entrypoint: ['php']
command: 'artisan queue:work'


scheduler:
image: ghcr.io/mintopia/control-php-fpm:develop
restart: unless-stopped
env_file: .env
depends_on:
- database
- redis
volumes:
- ./logs:/var/www/storage/logs
- ./public:/var/www/storage/public
entrypoint: ['php']
command: 'artisan schedule:work'

artisan:
image: ghcr.io/mintopia/control-php-fpm:develop
profiles:
- artisan
env_file: .env
depends_on:
- database
- redis
volumes:
- ./logs:/var/www/storage/logs
- ./public:/var/www/storage/public
entrypoint: ['php', 'artisan']

networks:
frontend:
external: true
```
In the `example` directory there is a docker compose file and some .env example files. These are for the setup I use.
Just rename the .env files and edit them accordingly. You can get a [random Laravel application key here](https://generate-random.org/laravel-key-generator).

You need to expose the `control` container to the public. This is configured to listen on port 80
in the docker compose, so you probably want something like Traefik or Caddy in-front as a reverse proxy.

I'm running this with an external docker network called `frontend` with Caddy running as HTTP/HTTPS ingress. You will
need to add a network section for the `control` service to add it to the `frontend` network if you
want to do this.

I'm running with an external docker network called `frontend` with Caddy running as HTTP/HTTPS ingress. To bring up the site, run the following:
You will need to make a logs directory and chmod it 777 as I still need to sort permissions out.

To bring up the site, run the following:

```bash
# Create your docker compose file
# Create your .env file from the project's .env.example and edit as required.
docker compose up -d redis database
docker compose run --rm artisan key:generate
docker compose run --rm artisan migrate
docker compose run --rm artisan db:seed
docker compose run --rm artisan control:setup-discord
docker compose run --rm artisan setup:discord
docker compose up -d
```

You should now be able to visit the site and login. From here you can use the admin menu to configure the site.

## Observability

Control supports basic observability functionality in using an OpenTelemetry collector. It can support traces, logs
and metrics. If enabled, it will create traces for all HTTP requests. To enable it, add the following to your `.env`:

```dotenv
OPENTELEMETRY_ENABLED=true
```

For logging output, a logger is defined and can be used. I suggest you use this with your usual logger, eg. `daily`.
You can specify this logging with the following environment variables:

```dotenv
LOG_CHANNEL=stack
LOG_STACK=opentelemetry,daily
```

By default it is configured to send to an OpenTelemetry container running with the name `collector`. An example config
is supplied with placeholders for sending data to [Honeycomb](https://www.honeycomb.io/).

The plan will be to add further spans within individual requests and have spans for the jobs and queued actions.

## Contributing

It's an open source project and I'm happy to accept pull requests. I am terrible at UI and UX, which is why this is entirely using server-side rendering. If someone wants to use Vue/Laravel Livewire - please go ahead!
Expand Down
1 change: 1 addition & 0 deletions app/Console/Commands/SetupDiscord.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Models\SocialProvider;
use App\Services\SocialProviders\DiscordProvider;
use Illuminate\Console\Command;

use function Laravel\Prompts\confirm;
use function Laravel\Prompts\password;
use function Laravel\Prompts\table;
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/SyncDiscordRoles.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function handle(?DiscordApi $discord)
$query->whereSocialProviderId($discordProvider->id)
->whereIn('external_id', $ids)
->with(['user', 'user.tickets', 'user.tickets.type'])
->chunk(100, function($accounts) use ($members) {
->chunk(100, function ($accounts) use ($members) {
foreach ($accounts as $linkedAccount) {
$this->syncAccount($linkedAccount, $members[$linkedAccount->external_id]);
}
Expand Down
6 changes: 3 additions & 3 deletions app/Console/Commands/UpdateEventSeatingLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ class UpdateEventSeatingLock extends Command
* The console command description.
*
* @var string
*
*
*/
public function handle()
{
Event::where('seating_opens_at', '<=', Carbon::now())->chunk(100, function($chunk) {
Event::where('seating_opens_at', '<=', Carbon::now())->chunk(100, function ($chunk) {
foreach ($chunk as $event) {
$this->output->writeln("{$event} Unlocking seating");
Log::info("{$event}: Unlocking seating");
Expand All @@ -33,7 +33,7 @@ public function handle()
$event->save();
}
});
Event::where('seating_closes_at', '<=', Carbon::now())->chunk(100, function($chunk) {
Event::where('seating_closes_at', '<=', Carbon::now())->chunk(100, function ($chunk) {
foreach ($chunk as $event) {
$this->output->writeln("{$event} Locking seating");
Log::info("{$event}: Locking seating");
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public function create()

public function store(EventUpdateRequest $request)
{
$event = new Event;
$event = new Event();
$this->updateObject($event, $request);
return response()->redirectToRoute('admin.events.show', $event->code)->with('successMessage', 'The event has been created');
}
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/SocialProviderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function update(SocialProviderUpdateRequest $request, SocialProvider $pro
if ($provider->supports_auth) {
$provider->auth_enabled = (bool)$request->input('auth_enabled');
}
if($provider->can_be_renamed){
if ($provider->can_be_renamed) {
$provider->name = $request->input('name');
}
$provider->save();
Expand Down
5 changes: 2 additions & 3 deletions app/Http/Controllers/Admin/ThemeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function create()
if ($default) {
$theme = clone $default;
} else {
$theme = new Theme;
$theme = new Theme();
}
$theme->name = null;
$theme->readonly = false;
Expand All @@ -27,7 +27,7 @@ public function create()

public function store(ThemeUpdateRequest $request)
{
$theme = new Theme;
$theme = new Theme();
$this->updateObject($theme, $request);
return response()->redirectToRoute('admin.settings.index')->with('successMessage', 'The theme has been created');
}
Expand Down Expand Up @@ -85,6 +85,5 @@ protected function updateObject(Theme $theme, Request $request)
}
}
$theme->save();

}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/TicketController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Models\TicketProvider;
use App\Models\TicketType;
use Illuminate\Http\Request;

use function App\makeCode;

class TicketController extends Controller
Expand Down Expand Up @@ -243,5 +244,4 @@ public function import_process(Request $request)

return response()->redirectToRoute('admin.tickets.index')->with('successMessage', 'The tickets have been imported');
}

}
3 changes: 2 additions & 1 deletion app/Http/Controllers/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
use AuthorizesRequests;
use ValidatesRequests;
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function home(Request $request)
$tickets = $request
->user()
->tickets()
->whereHas('event', function($query) use ($request) {
->whereHas('event', function ($query) use ($request) {
$query->where('ends_at', '>=', Carbon::now());
if (!$request->user()->hasRole('admin')) {
$query->whereDraft(false);
Expand Down
8 changes: 4 additions & 4 deletions app/Http/Controllers/TicketController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public function index(Request $request)
->tickets();

if (!$request->user()->hasRole('admin')) {
$query->whereHas('event', function($query) {
$query->whereHas('event', function ($query) {
$query->whereDraft(false);
});
}

$tickets = $query->with(['event' => function ($query) {
$query->orderBy('starts_at', 'DESC');
}, 'type', 'seat'])
}, 'type', 'seat'])
->paginate();

return view('tickets.index', [
Expand All @@ -40,7 +40,7 @@ public function show(Request $request, Ticket $ticket)

public function update(Request $request, Ticket $ticket)
{
if(Setting::fetch('disable-ticket-transfers')){
if (Setting::fetch('disable-ticket-transfers')) {
return response()->redirectToRoute('tickets.show', $ticket->id)->with('errorMessage', 'Ticket transfers are disabled.');
}
if ($request->has('generate')) {
Expand All @@ -56,7 +56,7 @@ public function update(Request $request, Ticket $ticket)

public function transfer(TicketTransferRequest $request)
{
if(Setting::fetch('disable-ticket-transfers')){
if (Setting::fetch('disable-ticket-transfers')) {
return response()->redirectToRoute('tickets.index')->with('errorMessage', 'Ticket transfers are disabled.');
}
$ticket = Ticket::whereTransferCode($request->input('code'))->first();
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Http\Middleware\Authenticate;
use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\MeasureRequest;
use App\Http\Middleware\MetricsCollector;
use App\Http\Middleware\PreventRequestsDuringMaintenance;
use App\Http\Middleware\RedirectIfAuthenticated;
Expand Down Expand Up @@ -41,6 +42,7 @@ class Kernel extends HttpKernel
* @var array<int, class-string|string>
*/
protected $middleware = [
MeasureRequest::class,
MetricsCollector::class,
// \App\Http\Middleware\TrustHosts::class,
TrustProxies::class,
Expand Down
Loading