docs: expand EventCrew README with stack and structure
Sync auto-imports and typed-router generated declarations from Vue tooling. Made-with: Cursor
This commit is contained in:
170
README.md
170
README.md
@@ -1,73 +1,137 @@
|
|||||||
# EventCrew
|
# EventCrew
|
||||||
|
|
||||||
Full-stack event crew operations management platform.
|
Multi-tenant SaaS platform for **event and festival operations**: planning, people, accreditation, artist advancing, volunteer shifts, briefings, and show-day tooling (Mission Control). The backend is a **JSON-only** Laravel API; all user interfaces are **Vue 3** single-page apps.
|
||||||
|
|
||||||
## Tech Stack
|
---
|
||||||
|
|
||||||
|
## What EventCrew covers
|
||||||
|
|
||||||
|
- **Organisations & events** — Multi-tenant data with organisation-scoped access; events move through a defined lifecycle (draft → published → registration → buildup → show day → teardown → closed).
|
||||||
|
- **Festival structure** — Sections, time slots, shifts, assignments, claiming/approval flows for volunteers and crew.
|
||||||
|
- **People & crowds** — Crowd types (crew, volunteers, artists, guests, press, partners, suppliers), persons, lists, and (planned) rich accreditation (items, zones, hand-out).
|
||||||
|
- **Artists & advancing** — Booking status, stages/timetable concepts, advance sections and token-based portal access for external artists.
|
||||||
|
- **Communication** — Briefings, campaigns, and operational messaging (see architecture doc for target modules).
|
||||||
|
- **Portal (external users)** — One portal app, two modes: **login** (Sanctum) for long-term users such as volunteers, and **token** access for per-event links (e.g. artists, suppliers).
|
||||||
|
|
||||||
|
Implementation is phased; the authoritative feature and schema list lives in the architecture and design references below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Applications
|
||||||
|
|
||||||
|
| App | Path | Port | Role |
|
||||||
|
|-----|------|------|------|
|
||||||
|
| **Admin** | `apps/admin/` | 5173 | Platform **super admin**: organisations, billing-style flags, global settings. Full Vuexy shell. |
|
||||||
|
| **Organizer** | `apps/app/` | 5174 | Main product for **org and event staff**: events, sections, shifts, people, artists, accreditation, briefings, reports, etc. |
|
||||||
|
| **Portal** | `apps/portal/` | 5175 | **External** users: stripped layout; login- or token-based access. |
|
||||||
|
|
||||||
|
All apps talk to the API over **CORS** with **Laravel Sanctum** tokens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech stack
|
||||||
|
|
||||||
| Layer | Technology |
|
| Layer | Technology |
|
||||||
|-------|------------|
|
|-------|------------|
|
||||||
| Backend | Laravel 12 + PHP 8.3 + Sanctum |
|
| API | PHP 8.2+, Laravel 12, Sanctum, Spatie Permission (and Activity Log / Media Library where used) |
|
||||||
| Database | MySQL 8.0 |
|
| Data | MySQL 8, Redis (cache/queues) |
|
||||||
| Frontend | Vue 3 + TypeScript + Vuexy |
|
| Frontends | Vue 3, TypeScript, Vite, Vuexy + Vuetify, Pinia, TanStack Query, VeeValidate + Zod |
|
||||||
| Local Dev | Native PHP/Node + Docker |
|
| Local services | Docker Compose (MySQL, Redis, Mailpit) |
|
||||||
|
|
||||||
## Quick Start
|
**Rule of thumb:** business tables use **ULID** primary keys; event-related data is scoped by **organisation** (global scopes + policies), not ad hoc `where` clauses in controllers.
|
||||||
|
|
||||||
```bash
|
---
|
||||||
# 1. Start Docker services
|
|
||||||
make services
|
|
||||||
|
|
||||||
# 2. Open in Cursor and start building!
|
## Project structure
|
||||||
```
|
|
||||||
|
|
||||||
See [docs/SETUP.md](docs/SETUP.md) for detailed instructions.
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
```
|
||||||
event-crew/
|
event-crew/
|
||||||
├── api/ # Laravel 12 API
|
├── api/ # Laravel 12 REST API (JSON only)
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── admin/ # Admin Dashboard
|
│ ├── admin/ # Super Admin SPA
|
||||||
│ ├── band/ # Band Member Portal
|
│ ├── app/ # Organizer SPA (primary UI)
|
||||||
│ └── customers/ # Customer Portal
|
│ └── portal/ # External portal SPA
|
||||||
├── docker/ # Docker configs
|
├── docker/ # Docker / Compose assets
|
||||||
├── docs/ # Documentation
|
├── docs/ # SETUP, API notes, schema notes
|
||||||
├── resources/ # Vuexy template source (reference)
|
├── resources/
|
||||||
├── .cursor/rules # Cursor AI instructions
|
│ ├── design/ # Product source of truth (design docs, see table below)
|
||||||
└── Makefile # Development commands
|
│ └── vuexy-admin-*/ # Vuexy template reference (bundled kit)
|
||||||
|
├── .cursor/ # ARCHITECTURE.md, instructions.md, rules for AI/helpers
|
||||||
|
└── Makefile # Dev commands
|
||||||
```
|
```
|
||||||
|
|
||||||
## Frontend Apps (Vuexy v10.11.1)
|
Vuexy **`@core/`** and **`@layouts/`** in each app should stay untouched; customize via app config, navigation, and app-level components.
|
||||||
|
|
||||||
All frontend apps are built with **Vue 3 + TypeScript** using the [Vuexy Admin Template](https://pixinvent.com/vuexy-vuejs-admin-dashboard-template/).
|
---
|
||||||
|
|
||||||
| App | Template | Description |
|
## Quick start
|
||||||
|-----|----------|-------------|
|
|
||||||
| **Admin** | TypeScript Full Version | Complete admin dashboard with all Vuexy features |
|
|
||||||
| **Band Portal** | TypeScript Starter Kit | Lightweight portal for band members |
|
|
||||||
| **Customer Portal** | TypeScript Starter Kit | Lightweight portal for customers |
|
|
||||||
|
|
||||||
**Template source:** `resources/vuexy-admin-v10.11.1/vue-version/typescript-version/`
|
|
||||||
|
|
||||||
> **Note:** The `@core/` and `@layouts/` folders should not be modified directly. Customize through `themeConfig.ts` and override styles in `assets/styles/`.
|
|
||||||
|
|
||||||
## URLs
|
|
||||||
|
|
||||||
| App | Development | Production |
|
|
||||||
|-----|-------------|------------|
|
|
||||||
| API | http://localhost:8000/api/v1 | https://api.eventcrew.nl |
|
|
||||||
| Admin | http://localhost:5173 | https://admin.eventcrew.nl |
|
|
||||||
| Band Portal | http://localhost:5174 | https://band.eventcrew.nl |
|
|
||||||
| Customer Portal | http://localhost:5175 | https://customers.eventcrew.nl |
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make services # Start MySQL, Redis, Mailpit
|
# 1. Infrastructure
|
||||||
make services-stop # Stop Docker services
|
make services
|
||||||
make api # Start Laravel API
|
|
||||||
make admin # Start Admin SPA
|
# 2. API env, dependencies, database (see docs/SETUP.md)
|
||||||
make band # Start Band Portal
|
cd api && cp .env.example .env && composer install && php artisan key:generate && php artisan migrate
|
||||||
make customers # Start Customer Portal
|
|
||||||
|
# 3. Run API + the SPAs you need (separate terminals)
|
||||||
|
make api
|
||||||
|
make admin # optional
|
||||||
|
make app # optional
|
||||||
|
make portal # optional
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Detailed setup: **[docs/SETUP.md](docs/SETUP.md)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development URLs
|
||||||
|
|
||||||
|
| Service | Development | Env / notes |
|
||||||
|
|---------|-------------|-------------|
|
||||||
|
| API | http://localhost:8000/api/v1 | Base path `/api/v1` |
|
||||||
|
| Admin | http://localhost:5173 | `FRONTEND_ADMIN_URL` in Laravel CORS |
|
||||||
|
| Organizer | http://localhost:5174 | `FRONTEND_APP_URL` |
|
||||||
|
| Portal | http://localhost:5175 | `FRONTEND_PORTAL_URL` |
|
||||||
|
| Mailpit | http://localhost:8025 | Local mail capture |
|
||||||
|
|
||||||
|
Production hostnames are placeholders until you deploy; configure `config/cors.php` via `.env`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Makefile commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make services # MySQL, Redis, Mailpit
|
||||||
|
make services-stop
|
||||||
|
make api # Laravel on :8000
|
||||||
|
make admin # Admin on :5173
|
||||||
|
make app # Organizer on :5174
|
||||||
|
make portal # Portal on :5175
|
||||||
|
make migrate
|
||||||
|
make fresh # migrate:fresh --seed
|
||||||
|
make db-shell
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| Resource | Contents |
|
||||||
|
|----------|----------|
|
||||||
|
| [resources/design/](resources/design/) | **Canonical product specs** — place design documents here (often Word). Referenced by `.cursor` as source of truth for features and data model. Expected names include `EventCrew_Design_Document_v1.3.docx`, `EventCrew_Dev_Guide_v1.0.docx`, `EventCrew_Start_Guide_v1.0.docx` (adjust versions if you update files). |
|
||||||
|
| [.cursor/ARCHITECTURE.md](.cursor/ARCHITECTURE.md) | System diagram, apps, multi-tenancy, roles, event lifecycle, API route map, core schema overview (summarises `resources/design` when present) |
|
||||||
|
| [.cursor/instructions.md](.cursor/instructions.md) | Quick reference, phased roadmap, module build order |
|
||||||
|
| [.cursor/rules/](.cursor/rules/) | Workspace, Laravel, Vue, testing conventions |
|
||||||
|
| [docs/SETUP.md](docs/SETUP.md) | Environment and local setup |
|
||||||
|
| [docs/API.md](docs/API.md) | API notes (if maintained) |
|
||||||
|
| [docs/SCHEMA.md](docs/SCHEMA.md) | Schema notes (if maintained) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd api && php artisan test
|
||||||
|
```
|
||||||
|
|
||||||
|
Feature tests should cover happy paths plus **401** (unauthenticated), **403** (wrong organisation), and **422** (validation) where applicable.
|
||||||
|
|||||||
5
apps/admin/auto-imports.d.ts
vendored
5
apps/admin/auto-imports.d.ts
vendored
@@ -55,6 +55,7 @@ declare global {
|
|||||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const getUserAbilityRules: typeof import('./src/utils/auth-ability')['getUserAbilityRules']
|
||||||
const h: typeof import('vue')['h']
|
const h: typeof import('vue')['h']
|
||||||
const hexToRgb: typeof import('./src/@core/utils/colorConverter')['hexToRgb']
|
const hexToRgb: typeof import('./src/@core/utils/colorConverter')['hexToRgb']
|
||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
@@ -200,6 +201,7 @@ declare global {
|
|||||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||||
|
const useCurrentOrganisationId: typeof import('./src/composables/useOrganisationContext')['useCurrentOrganisationId']
|
||||||
const useCustomFetch: typeof import('./src/composables/useFetch')['useCustomFetch']
|
const useCustomFetch: typeof import('./src/composables/useFetch')['useCustomFetch']
|
||||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||||
const useDark: typeof import('@vueuse/core')['useDark']
|
const useDark: typeof import('@vueuse/core')['useDark']
|
||||||
@@ -423,6 +425,7 @@ declare module 'vue' {
|
|||||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
|
readonly getUserAbilityRules: UnwrapRef<typeof import('./src/utils/auth-ability')['getUserAbilityRules']>
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly hexToRgb: UnwrapRef<typeof import('./src/@core/utils/colorConverter')['hexToRgb']>
|
readonly hexToRgb: UnwrapRef<typeof import('./src/@core/utils/colorConverter')['hexToRgb']>
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
@@ -529,7 +532,6 @@ declare module 'vue' {
|
|||||||
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
readonly useApi: UnwrapRef<typeof import('./src/composables/useApi')['useApi']>
|
|
||||||
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
||||||
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
||||||
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
||||||
@@ -566,6 +568,7 @@ declare module 'vue' {
|
|||||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
||||||
|
readonly useCurrentOrganisationId: UnwrapRef<typeof import('./src/composables/useOrganisationContext')['useCurrentOrganisationId']>
|
||||||
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
||||||
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
||||||
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
||||||
|
|||||||
4
apps/app/auto-imports.d.ts
vendored
4
apps/app/auto-imports.d.ts
vendored
@@ -200,6 +200,7 @@ declare global {
|
|||||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||||
|
const useCurrentOrganisationId: typeof import('./src/composables/useOrganisationContext')['useCurrentOrganisationId']
|
||||||
const useCustomFetch: typeof import('./src/composables/useFetch')['useCustomFetch']
|
const useCustomFetch: typeof import('./src/composables/useFetch')['useCustomFetch']
|
||||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||||
const useDark: typeof import('@vueuse/core')['useDark']
|
const useDark: typeof import('@vueuse/core')['useDark']
|
||||||
@@ -376,7 +377,6 @@ import { UnwrapRef } from 'vue'
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
interface GlobalComponents {}
|
interface GlobalComponents {}
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
readonly $api: UnwrapRef<typeof import('./src/utils/api')['$api']>
|
|
||||||
readonly COOKIE_MAX_AGE_1_YEAR: UnwrapRef<typeof import('./src/utils/constants')['COOKIE_MAX_AGE_1_YEAR']>
|
readonly COOKIE_MAX_AGE_1_YEAR: UnwrapRef<typeof import('./src/utils/constants')['COOKIE_MAX_AGE_1_YEAR']>
|
||||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||||
@@ -528,7 +528,6 @@ declare module 'vue' {
|
|||||||
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
readonly useApi: UnwrapRef<typeof import('./src/composables/useApi')['useApi']>
|
|
||||||
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
||||||
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
||||||
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
||||||
@@ -565,6 +564,7 @@ declare module 'vue' {
|
|||||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
||||||
|
readonly useCurrentOrganisationId: UnwrapRef<typeof import('./src/composables/useOrganisationContext')['useCurrentOrganisationId']>
|
||||||
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
||||||
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
||||||
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
||||||
|
|||||||
1
apps/app/typed-router.d.ts
vendored
1
apps/app/typed-router.d.ts
vendored
@@ -23,6 +23,5 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'events': RouteRecordInfo<'events', '/events', Record<never, never>, Record<never, never>>,
|
'events': RouteRecordInfo<'events', '/events', Record<never, never>, Record<never, never>>,
|
||||||
'events-id': RouteRecordInfo<'events-id', '/events/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
'events-id': RouteRecordInfo<'events-id', '/events/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||||
'login': RouteRecordInfo<'login', '/login', Record<never, never>, Record<never, never>>,
|
'login': RouteRecordInfo<'login', '/login', Record<never, never>, Record<never, never>>,
|
||||||
'second-page': RouteRecordInfo<'second-page', '/second-page', Record<never, never>, Record<never, never>>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user