Add models, enums, and services for user roles, theme preferences, slug history, and ID generation

This commit is contained in:
pacnpal
2025-02-23 19:50:40 -05:00
parent 32aea21e48
commit 7e5d15eb46
55 changed files with 6462 additions and 4 deletions

View File

@@ -0,0 +1,82 @@
# Operator Model Conversion
## Original Django Model Structure
### Company Model (Now Operator)
```python
class Company(TrackedModel):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
website = models.URLField(blank=True)
headquarters = models.CharField(max_length=255, blank=True)
description = models.TextField(blank=True)
total_parks = models.IntegerField(default=0)
total_rides = models.IntegerField(default=0)
```
### Manufacturer Model
```python
class Manufacturer(TrackedModel):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
website = models.URLField(blank=True)
headquarters = models.CharField(max_length=255, blank=True)
description = models.TextField(blank=True)
total_rides = models.IntegerField(default=0)
total_roller_coasters = models.IntegerField(default=0)
```
## Laravel Implementation Plan
### Database Migrations
1. Create operators table:
```php
Schema::create('operators', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('website')->nullable();
$table->string('headquarters')->nullable();
$table->text('description')->nullable();
$table->integer('total_parks')->default(0);
$table->integer('total_rides')->default(0);
$table->timestamps();
});
```
2. Create manufacturers table:
```php
Schema::create('manufacturers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('website')->nullable();
$table->string('headquarters')->nullable();
$table->text('description')->nullable();
$table->integer('total_rides')->default(0);
$table->integer('total_roller_coasters')->default(0);
$table->timestamps();
});
```
### Models
1. Operator Model:
- Implement Sluggable trait
- Add relationships (parks)
- Add statistics updating methods
- Add slug history functionality
2. Manufacturer Model:
- Implement Sluggable trait
- Add relationships (rides)
- Add statistics updating methods
- Add slug history functionality
### Next Steps
1. [ ] Create operators table migration
2. [ ] Create manufacturers table migration
3. [ ] Create Operator model
4. [ ] Create Manufacturer model
5. [ ] Implement statistics update methods

View File

@@ -0,0 +1,104 @@
# Location Model
## Overview
The Location model provides polymorphic location management for parks, areas, and other entities in ThrillWiki. It handles geocoding, coordinate management, and location-based search capabilities.
## Structure
### Database Table
- Table Name: `locations`
- Primary Key: `id` (bigint)
- Polymorphic Fields: `locatable_type`, `locatable_id`
- Timestamps: `created_at`, `updated_at`
### Fields
- **Address Components**
- `address` (string, nullable) - Street address
- `city` (string) - City name
- `state` (string, nullable) - State/province
- `country` (string) - Country name
- `postal_code` (string, nullable) - Postal/ZIP code
- **Coordinates**
- `latitude` (decimal, 10,8) - Latitude coordinate
- `longitude` (decimal, 11,8) - Longitude coordinate
- `elevation` (decimal, 8,2, nullable) - Elevation in meters
- **Additional Details**
- `timezone` (string, nullable) - Location timezone
- `metadata` (json, nullable) - Additional location data
- `is_approximate` (boolean) - Indicates if location is approximate
- `source` (string, nullable) - Data source identifier
- **Geocoding**
- `geocoding_data` (json, nullable) - Cached geocoding response
- `geocoded_at` (timestamp, nullable) - Last geocoding timestamp
### Indexes
- Coordinates: `(latitude, longitude)`
- Location: `(country, state, city)`
- Postal: `postal_code`
## Relationships
### Polymorphic
- `locatable()` - Polymorphic relationship to parent model (Park, Area, etc.)
## Accessors & Mutators
- `coordinates` - Returns [lat, lng] array
- `formatted_address` - Returns formatted address string
- `map_url` - Returns Google Maps URL
## Methods
### Location Management
- `updateCoordinates(float $lat, float $lng)` - Update coordinates
- `setAddress(array $components)` - Set address components
- `geocode()` - Trigger geocoding refresh
- `reverseGeocode()` - Get address from coordinates
### Queries
- `scopeNearby($query, $lat, $lng, $radius)` - Find nearby locations
- `scopeInBounds($query, $ne, $sw)` - Find locations in bounds
- `scopeInCountry($query, $country)` - Filter by country
- `scopeInState($query, $state)` - Filter by state
- `scopeInCity($query, $city)` - Filter by city
### Calculations
- `distanceTo($lat, $lng)` - Calculate distance to point
- `bearingTo($lat, $lng)` - Calculate bearing to point
## Usage Examples
```php
// Create location for park
$park->location()->create([
'address' => '123 Main St',
'city' => 'Orlando',
'state' => 'FL',
'country' => 'USA',
'latitude' => 28.538336,
'longitude' => -81.379234
]);
// Find parks within 50km
$nearbyParks = Park::whereHas('location', function ($query) {
$query->nearby(28.538336, -81.379234, 50);
})->get();
```
## Integration Points
### Services
- GeocodeService - Address/coordinate lookup
- LocationSearchService - Advanced location search
### Components
- LocationSelector - Map-based location picker
- LocationDisplay - Location visualization
## Notes
- Coordinates use high precision for accuracy
- Geocoding results are cached to reduce API calls
- Polymorphic design allows reuse across models
- Search methods use spatial indexes for performance

View File

@@ -0,0 +1,164 @@
# Park Model Conversion
## Original Django Model Structure
### Park Model
```python
class Park(TrackedModel):
# Status choices
STATUS_CHOICES = [
("OPERATING", "Operating"),
("CLOSED_TEMP", "Temporarily Closed"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
]
# Basic info
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
description = models.TextField(blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="OPERATING")
# Location fields (GenericRelation)
location = GenericRelation(Location)
# Details
opening_date = models.DateField(null=True, blank=True)
closing_date = models.DateField(null=True, blank=True)
operating_season = models.CharField(max_length=255, blank=True)
size_acres = models.DecimalField(max_digits=10, decimal_places=2, null=True)
website = models.URLField(blank=True)
# Statistics
average_rating = models.DecimalField(max_digits=3, decimal_places=2, null=True)
ride_count = models.IntegerField(null=True)
coaster_count = models.IntegerField(null=True)
# Relationships
operator = models.ForeignKey(Operator, SET_NULL, null=True, related_name="parks")
photos = GenericRelation(Photo)
```
### ParkArea Model
```python
class ParkArea(TrackedModel):
park = models.ForeignKey(Park, CASCADE, related_name="areas")
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
description = models.TextField(blank=True)
opening_date = models.DateField(null=True, blank=True)
closing_date = models.DateField(null=True, blank=True)
```
## Laravel Implementation Plan
### Enums
1. Create ParkStatus enum with status options and color methods:
```php
enum ParkStatus: string {
case OPERATING = 'OPERATING';
case CLOSED_TEMP = 'CLOSED_TEMP';
case CLOSED_PERM = 'CLOSED_PERM';
case UNDER_CONSTRUCTION = 'UNDER_CONSTRUCTION';
case DEMOLISHED = 'DEMOLISHED';
case RELOCATED = 'RELOCATED';
}
```
### Database Migrations
1. Create parks table:
```php
Schema::create('parks', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->string('status', 20);
// Details
$table->date('opening_date')->nullable();
$table->date('closing_date')->nullable();
$table->string('operating_season')->nullable();
$table->decimal('size_acres', 10, 2)->nullable();
$table->string('website')->nullable();
// Statistics
$table->decimal('average_rating', 3, 2)->nullable();
$table->integer('ride_count')->nullable();
$table->integer('coaster_count')->nullable();
// Foreign keys
$table->foreignId('operator_id')->nullable()->constrained('operators')->nullOnDelete();
$table->timestamps();
});
```
2. Create park_areas table:
```php
Schema::create('park_areas', function (Blueprint $table) {
$table->id();
$table->foreignId('park_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->string('slug');
$table->text('description')->nullable();
$table->date('opening_date')->nullable();
$table->date('closing_date')->nullable();
$table->timestamps();
$table->unique(['park_id', 'slug']);
});
```
### Models
1. Park Model:
- Implement Sluggable trait
- Add status color methods
- Set up relationships (operator, areas, photos, location)
- Add history tracking
- Implement slug history functionality
2. ParkArea Model:
- Implement Sluggable trait
- Set up relationship with Park
- Add history tracking
- Implement slug history functionality
### Livewire Components
1. ParkListComponent:
- Display parks with status badges
- Filter by status
- Sort functionality
- Search by name
2. ParkFormComponent:
- Create/edit park details
- Location selection
- Operator selection
- Status management
3. ParkAreaComponent:
- Manage park areas
- Add/edit/delete areas
- Sort/reorder areas
### Features to Implement
1. Slug history tracking
2. Location management
3. Photo management
4. Statistics calculation
5. Area management
6. Park status badges with colors
### Next Steps
1. [ ] Create ParkStatus enum
2. [ ] Create parks table migration
3. [ ] Create park_areas table migration
4. [ ] Create Park model
5. [ ] Create ParkArea model
6. [ ] Implement Livewire components

View File

@@ -0,0 +1,122 @@
# User Model Conversion
## Original Django Model Structure
### User Model (extends AbstractUser)
```python
class User(AbstractUser):
# Custom fields
user_id = models.CharField(max_length=10, unique=True, editable=False)
role = models.CharField(max_length=10, choices=['USER', 'MODERATOR', 'ADMIN', 'SUPERUSER'])
is_banned = models.BooleanField(default=False)
ban_reason = models.TextField(blank=True)
ban_date = models.DateTimeField(null=True, blank=True)
pending_email = models.EmailField(blank=True, null=True)
theme_preference = models.CharField(max_length=5, choices=['light', 'dark'])
```
### UserProfile Model
```python
class UserProfile:
profile_id = models.CharField(max_length=10, unique=True, editable=False)
user = models.OneToOneField(User, related_name='profile')
display_name = models.CharField(max_length=50, unique=True)
avatar = models.ImageField(upload_to='avatars/')
pronouns = models.CharField(max_length=50, blank=True)
bio = models.TextField(max_length=500, blank=True)
# Social media
twitter = models.URLField(blank=True)
instagram = models.URLField(blank=True)
youtube = models.URLField(blank=True)
discord = models.CharField(max_length=100, blank=True)
# Stats
coaster_credits = models.IntegerField(default=0)
dark_ride_credits = models.IntegerField(default=0)
flat_ride_credits = models.IntegerField(default=0)
water_ride_credits = models.IntegerField(default=0)
```
## Laravel Implementation Plan
### Database Migrations
1. Extend users table (`database/migrations/[timestamp]_add_user_fields.php`):
```php
Schema::table('users', function (Blueprint $table) {
$table->string('user_id', 10)->unique();
$table->enum('role', ['USER', 'MODERATOR', 'ADMIN', 'SUPERUSER'])->default('USER');
$table->boolean('is_banned')->default(false);
$table->text('ban_reason')->nullable();
$table->timestamp('ban_date')->nullable();
$table->string('pending_email')->nullable();
$table->enum('theme_preference', ['light', 'dark'])->default('light');
});
```
2. Create profiles table (`database/migrations/[timestamp]_create_profiles_table.php`):
```php
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->string('profile_id', 10)->unique();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('display_name', 50)->unique();
$table->string('avatar')->nullable();
$table->string('pronouns', 50)->nullable();
$table->text('bio')->nullable();
// Social media
$table->string('twitter')->nullable();
$table->string('instagram')->nullable();
$table->string('youtube')->nullable();
$table->string('discord', 100)->nullable();
// Stats
$table->integer('coaster_credits')->default(0);
$table->integer('dark_ride_credits')->default(0);
$table->integer('flat_ride_credits')->default(0);
$table->integer('water_ride_credits')->default(0);
$table->timestamps();
});
```
### Model Implementation
1. User Model (`app/Models/User.php`):
- Extend Laravel's base User model
- Add custom attributes
- Add relationship to Profile
- Add role management methods
- Add ban management methods
2. Profile Model (`app/Models/Profile.php`):
- Create new model
- Add relationship to User
- Add avatar handling methods
- Add credit management methods
### Livewire Components
1. ProfileComponent - Handle profile management
2. AvatarUploadComponent - Handle avatar uploads
3. UserSettingsComponent - Handle user settings/preferences
4. UserBanComponent - For moderator use to handle bans
### Services
1. UserService - Business logic for user management
2. ProfileService - Business logic for profile management
3. AvatarService - Handle avatar generation and storage
### Next Steps
1. [ ] Create user fields migration
2. [ ] Create profiles table migration
3. [ ] Enhance User model with new fields and methods
4. [ ] Create Profile model
5. [ ] Implement initial Livewire components for profile management
### Notes
- Will use Laravel's built-in authentication (already scaffolded)
- Email verification will be handled by Laravel's built-in features
- Password reset functionality will use Laravel's default implementation
- Will implement custom avatar generation similar to Django version