- Add .gitea/workflows/docker-build-push.yaml: build api/admin/upload on push to main - Build script: build for linux/amd64 by default (PLATFORM), doc PLATFORM=linux/arm64 - Deploy README: Option A Gitea Actions, Option B manual; proxy timeout troubleshooting Co-authored-by: Cursor <cursoragent@cursor.com>
Event Video Upload Application
Web application for event organizers to collect video uploads from attendees. Admins create events with shareable URLs; attendees upload videos to Google Drive folders per event.
Stack
- Backend: Laravel 12 (PHP 8.3+), MySQL 8.0+, Sanctum, Google Drive API v3, Queue (database)
- Admin: Vue 3 + TypeScript + Vite + Bootstrap 5
- Upload: Vue 3 + TypeScript + Vite + Tailwind CSS + Uppy
Getting Started
-
Clone and install
- Backend:
cd api && composer install - Admin:
cd admin && npm install - Upload:
cd upload && npm install
- Backend:
-
Configure
- Copy
api/.env.exampletoapi/.env - Set MySQL (
DB_*),SANCTUM_STATEFUL_DOMAINS=localhost:5173,localhost:5174,SESSION_DOMAIN=localhost - Set Google OAuth:
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_REDIRECT_URI - Run
php artisan key:generateinapi/
- Copy
-
Database
- Create MySQL database (e.g.
event_uploader) - In
api/:php artisan migrate
- Create MySQL database (e.g.
-
Admin user
cd api && php artisan make:admin --email=admin@example.com --password=secret
-
Run (requires 4 separate terminal windows)
- API:
cd api && php artisan serve - Queue Worker (REQUIRED for uploads):
cd api && php artisan queue:work - Admin:
cd admin && npm run dev(port 5173) - Upload:
cd upload && npm run dev(port 5174)
⚠️ Important: The queue worker MUST be running for file uploads to process to Google Drive. Without it, uploads will stay in "pending" status.
- API:
-
Use
- Open http://localhost:5173, log in, connect Google Drive, create an event, copy the upload URL
- Share the upload URL (http://localhost:5174/events/{slug}); attendees upload there
MySQL: Docker vs Homebrew
| Docker | Homebrew | |
|---|---|---|
| Setup | Requires Docker Desktop. One docker run or docker compose up. |
brew install mysql and create DB. |
| Best for | Same MySQL version everywhere; no system install; easy reset. | Quick local use; no Docker needed. |
| Reset | docker compose down -v for a clean DB. |
Manual drop/recreate or reinstall. |
Recommendation: Use Docker if you already have it or want a reproducible setup; use Homebrew if you prefer a single local MySQL and no containers.
Option A: MySQL with Docker
A docker-compose.yml in the project root runs only MySQL (API and frontends run on your machine).
-
Start MySQL (from project root):
docker compose up -dWait for the healthcheck (a few seconds), then continue.
-
Configure the API (
api/.env):DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=event_uploader DB_USERNAME=root DB_PASSWORD=secret APP_URL=http://localhost:8000 SESSION_DOMAIN=localhost SANCTUM_STATEFUL_DOMAINS=localhost:5173,localhost:5174 -
Then:
cd api && php artisan migrate && php artisan make:admin --email=admin@test.com --password=secret, and run API, queue, admin, and upload (see step 4 in Option B).
Stop: docker compose down
Stop and wipe DB: docker compose down -v
Option B: MySQL with Homebrew (macOS)
-
Install MySQL (if needed):
brew install mysql brew services start mysqlOr run once:
mysql.server start. -
Create the database and user (optional; root with no password works for local):
mysql -u root -e "CREATE DATABASE IF NOT EXISTS event_uploader;"If MySQL has a root password, use
-pand enter it. -
Configure the API (
api/.env):DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=event_uploader DB_USERNAME=root DB_PASSWORD= APP_URL=http://localhost:8000 SESSION_DOMAIN=localhost SANCTUM_STATEFUL_DOMAINS=localhost:5173,localhost:5174Set
DB_PASSWORDif your MySQL user has a password. -
Bootstrap and run (from project root):
cd api && composer install && php artisan key:generate && php artisan migrate php artisan make:admin --email=admin@test.com --password=secret php artisan serve & php artisan queue:work & cd ../admin && npm install && npm run dev & cd ../upload && npm install && npm run devOr run each in a separate terminal: API (port 8000), queue worker, admin (5173), upload (5174).
-
Test the flow:
- Open http://localhost:5173, log in as
admin@test.com/secret. - Create an event (Google Drive can be skipped at first; you can set it up later).
- Copy the upload URL and open it in another tab (e.g. http://localhost:5174/events/your-slug).
- Upload a small test file; the queue worker will process it (Google Drive upload will fail until OAuth is configured).
- Open http://localhost:5173, log in as
Tip: Keep the queue worker running in a dedicated terminal so uploads are processed. Without it, files stay in "pending" until the job runs.
Start everything (one command)
From the project root:
./start.sh
Starts API (8000), queue worker, admin (5173), and upload (5174). Press Ctrl+C to stop all.
If you use Docker for MySQL:
./start.sh --with-docker
(Starts docker compose up -d first, then the four apps.)
First time: run chmod +x start.sh if the script is not executable.
Development Commands
# Backend
cd api && composer install && php artisan migrate && php artisan serve
# Admin
cd admin && npm install && npm run dev
# Upload
cd upload && npm install && npm run dev
# Queue
cd api && php artisan queue:work
# Create admin
cd api && php artisan make:admin --email=admin@example.com --password=secret
# Tests
cd api && ./vendor/bin/pest
Pushing to Gitea
This repo pushes to Gitea (1Password SSH key). One-time setup:
- Run from project root:
./scripts/setup-gitea-ssh.sh(creates~/.ssh/gitea-1password-only). - In 1Password: Settings → Developer → enable Use the SSH agent.
Then you can push from Cursor or run ./gitea-push.sh. For commit signing and first-push tips, see GITEA-SETUP.md.
Project Structure
api/– Laravel API (auth, events, Google Drive, public upload)admin/– Vue 3 admin SPA (events, uploads, Google Drive)upload/– Vue 3 public upload SPA (event page, password gate, Uppy)
Troubleshooting
Uploads stuck in "pending" status
Problem: Files upload successfully but stay in "pending" status and never appear in Google Drive.
Solution: The queue worker is not running. Start it with:
cd api && php artisan queue:work
You can verify the queue worker is processing jobs by checking the logs or running:
cd api && php artisan queue:failed # Check for failed jobs
419 Page Expired error on upload
Problem: Getting a 419 error when uploading files from the public upload page.
Solution: This was fixed by excluding public upload endpoints from CSRF verification. Make sure your api/bootstrap/app.php includes the CSRF exception configuration.
Google Drive callback fails with "Route [login] not defined"
Problem: After authorizing Google Drive, you get redirected to an error page.
Solution: This was fixed by moving the Google Drive callback route outside the auth:sanctum middleware group and using web middleware instead. The callback now handles authentication internally.
No folders appear when selecting a Shared Drive
Problem: When you click on a Shared Drive, you see all folders from all drives instead of just that drive's folders.
Solution: This was fixed by updating the Google Drive API query to use '{$driveId}' in parents when listing root folders of a Shared Drive.