From fe59d936a7dbb32a95a4236bcdf286f540dd8fae Mon Sep 17 00:00:00 2001 From: grothedev Date: Mon, 27 Oct 2025 22:56:47 -0400 Subject: getting back to work on this. dont know why i never pushed models. might as well include these artifacts from claude. --- README.md | 16 +- artifacts-from-claude/dashboardctrler | 63 +++ artifacts-from-claude/dashboardroutes | 7 + artifacts-from-claude/select-examples.html | 126 ++++++ artifacts-from-claude/userdashboard | 379 ++++++++++++++++++ artifacts-from-claude/userfilemgmt | 591 +++++++++++++++++++++++++++++ models.txt | 21 + resources/views/home.blade.php | 3 +- 8 files changed, 1203 insertions(+), 3 deletions(-) create mode 100644 artifacts-from-claude/dashboardctrler create mode 100644 artifacts-from-claude/dashboardroutes create mode 100644 artifacts-from-claude/select-examples.html create mode 100644 artifacts-from-claude/userdashboard create mode 100644 artifacts-from-claude/userfilemgmt create mode 100644 models.txt diff --git a/README.md b/README.md index 9bad143..aab7fed 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,18 @@ https://laravel.com/api/11.x/ https://laravel.com/docs/11.x/blade #### TODO: +- a site statistics page + - total visits, visit frequency, country of recent visits, average location makeup, + - current connections, time spent on site (average, min, max, ) + - +- views + - want to make a view that consolidates the two styles of files and links + - links + - add additional metadata on hover + - files - htmx - make /htmx/{thing}/{param} routes for sub-elements of pages? e.g. /links/# gives a blade page, but /htmx/links/# would give just the single html element (and maybe would be queried by /links/#, though it might be silly to have to require a double request like that, when the php can already do the necessary processing in blade) +- error handling and validation for file upload and everything - writing - add drag-drop file to upload and then generate appropriate html to embed that file - auto capitalization https://marked.js.org/using_pro @@ -28,7 +38,11 @@ https://laravel.com/docs/11.x/blade - import any model via json - links - break out the canvas cursor renderer from main.js + - and more js organizination: treat each js file like a "plugin" that provides some functionality to some page if the page has the appropriate elements on it. define what dom elems are needed and optional for some js file. this way it won't be coupled with the page as much. - homepage image - + the entire webapp downloads and stores an application in the users localstorage. + if connection lost, user can still interact with the website services while offline. + in addition, other users who are on the same network can still use the webapp if it was downloaded to their localstorage. + - user dashboard system diff --git a/artifacts-from-claude/dashboardctrler b/artifacts-from-claude/dashboardctrler new file mode 100644 index 0000000..4f41417 --- /dev/null +++ b/artifacts-from-claude/dashboardctrler @@ -0,0 +1,63 @@ +middleware('auth'); + } + + /** + * Show the user dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + $user = Auth::user(); + + // You can add any additional data preparation here + // before passing to the view + + return view('dashboard'); + } + + /** + * Show activity statistics. + * + * @return \Illuminate\Http\JsonResponse + */ + public function statistics() + { + $user = Auth::user(); + + $stats = [ + 'files' => [ + 'count' => $user->files()->count(), + 'size' => $user->files()->sum('size'), + 'recent' => $user->files()->latest()->take(5)->get() + ], + 'writings' => [ + 'count' => $user->writings()->count(), + 'recent' => $user->writings()->latest()->take(5)->get() + ], + 'storage' => [ + 'used' => $user->getStorageUsed(), + 'total' => $user->storage_quota, + 'percent' => min(100, round(($user->getStorageUsed() / max(1, $user->storage_quota)) * 100)) + ] + ]; + + return response()->json($stats); + } +} \ No newline at end of file diff --git a/artifacts-from-claude/dashboardroutes b/artifacts-from-claude/dashboardroutes new file mode 100644 index 0000000..91f97e0 --- /dev/null +++ b/artifacts-from-claude/dashboardroutes @@ -0,0 +1,7 @@ +// Add these routes to your routes/web.php file + +Route::middleware(['auth'])->group(function () { + // Dashboard + Route::get('/dashboard', [App\Http\Controllers\DashboardController::class, 'index'])->name('dashboard'); + Route::get('/dashboard/stats', [App\Http\Controllers\DashboardController::class, 'statistics'])->name('dashboard.stats'); +}); \ No newline at end of file diff --git a/artifacts-from-claude/select-examples.html b/artifacts-from-claude/select-examples.html new file mode 100644 index 0000000..2142fa0 --- /dev/null +++ b/artifacts-from-claude/select-examples.html @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; + +export default function SelectExamples() { + const [basicValue, setBasicValue] = useState(''); + const [multiValue, setMultiValue] = useState([]); + const [groupValue, setGroupValue] = useState(''); + + return ( +
+ {/* Basic Select */} +
+

Basic Select

+ +

Selected value: {basicValue}

+ +
+{``}
+        
+
+ + {/* Multiple Select */} +
+

Multiple Select

+ +

Selected values: {multiValue.join(', ')}

+ +
+{``}
+        
+
+ + {/* Option Groups */} +
+

Option Groups

+ +

Selected value: {groupValue}

+ +
+{``}
+        
+
+ + {/* Key Attributes Reference */} +
+

Key Attributes Reference

+
+

Select attributes:

+
    +
  • multiple - Allow multiple selections
  • +
  • size - Number of visible options
  • +
  • disabled - Disable the entire select
  • +
  • required - Make selection required
  • +
  • name - Form field name
  • +
+ +

Option attributes:

+
    +
  • value - Option's value
  • +
  • selected - Pre-select the option
  • +
  • disabled - Disable specific option
  • +
+
+
+
+ ); +} \ No newline at end of file diff --git a/artifacts-from-claude/userdashboard b/artifacts-from-claude/userdashboard new file mode 100644 index 0000000..09cddef --- /dev/null +++ b/artifacts-from-claude/userdashboard @@ -0,0 +1,379 @@ +{{-- resources/views/dashboard.blade.php --}} +@extends('template') + +@section('head') + +@endsection + +@section('body') +
+

Dashboard

+ + @php + $user = Auth::user(); + + // Storage stats + $usedStorage = $user->getStorageUsed(); + $totalStorage = $user->storage_quota; + $storagePercent = $totalStorage > 0 ? min(100, round(($usedStorage / $totalStorage) * 100)) : 0; + + // File stats + $fileCount = $user->files()->count(); + $recentFiles = $user->files()->orderBy('created_at', 'desc')->take(3)->get(); + + // Writing stats + $writingCount = $user->writings()->count(); + $recentWritings = $user->writings()->orderBy('created_at', 'desc')->take(3)->get(); + + // Quest stats (if implemented) + $questCount = method_exists($user, 'quests') ? $user->quests()->count() : 0; + $activeQuests = method_exists($user, 'quests') ? $user->quests() + ->wherePivot('status', 'in_progress') + ->orderBy('created_at', 'desc') + ->take(3) + ->get() : collect(); + + // Inventory stats (placeholder) + $inventoryCount = 0; // Update when inventory is implemented + @endphp + + +
+
+
{{ strtoupper(substr($user->name, 0, 1)) }}
+
+

{{ $user->name }}

+

{{ $user->email }}

+

+ {{ $user->role == 0 ? 'Admin' : 'User' }} + Member since {{ $user->created_at->format('M Y') }} +

+
+
+
+ + +
+
+
{{ $fileCount }}
+
Files
+
+
+
{{ $writingCount }}
+
Writings
+
+
+
{{ $questCount }}
+
Quests
+
+
+
{{ $inventoryCount }}
+
Items
+
+
+ +
+ +
+

Storage

+ +
+
+ {{ round($usedStorage / 1024, 2) }} MB used + {{ round($totalStorage / 1024, 2) }} MB total +
+
+
+
+
+ +

Recent Files

+ @if($recentFiles->count() > 0) + + @else +
No files uploaded yet
+ @endif + + +
+ + +
+

Writings

+ + @if($recentWritings->count() > 0) +
    + @foreach($recentWritings as $writing) +
  • +
    + {{ Str::limit($writing->title, 35) }} +
    + {{ Str::limit(strip_tags(Str::markdown($writing->content)), 60) }} +
    +
    + {{ $writing->created_at->diffForHumans() }} +
    +
    +
  • + @endforeach +
+ @else +
No writings created yet
+ @endif + + +
+ + +
+

Quests

+ + @if(method_exists($user, 'quests') && $activeQuests->count() > 0) +
    + @foreach($activeQuests as $quest) +
  • +
    + {{ Str::limit($quest->title, 35) }} +
    + Progress: {{ $quest->pivot->progress }}% +
    +
    +
    +
    +
    + + {{ ucfirst($quest->difficulty) }} + + {{ $quest->reward_points }} points +
    +
    +
  • + @endforeach +
+ @else +
No active quests
+ @endif + + +
+ + +
+

Inventory

+ +
Inventory system coming soon
+ + +
+
+ + +
+

Your Tags

+ + @php + // Get all tags from user's content + $fileTags = $user->files()->with('tags')->get()->pluck('tags')->flatten(); + $writingTags = method_exists($user->writings(), 'with') ? + $user->writings()->with('tags')->get()->pluck('tags')->flatten() : + collect(); + + $allTags = $fileTags->merge($writingTags)->unique('id'); + @endphp + + @if($allTags->count() > 0) +
+ @foreach($allTags as $tag) + {{ $tag->label }} + @endforeach +
+ @else +
No tags used yet
+ @endif +
+
+@endsection \ No newline at end of file diff --git a/artifacts-from-claude/userfilemgmt b/artifacts-from-claude/userfilemgmt new file mode 100644 index 0000000..629a624 --- /dev/null +++ b/artifacts-from-claude/userfilemgmt @@ -0,0 +1,591 @@ +// 1. First, we'll update the User model to add file quota settings +// app/Models/User.php - Add these methods and properties + + 'datetime', + 'password' => 'hashed', + 'max_file_size' => 'integer', + 'storage_quota' => 'integer', + ]; + } + + // Add this relation for files + public function files() + { + return $this->hasMany(File::class); + } + + // Helper methods for file storage + + /** + * Get used storage space in KB + */ + public function getStorageUsed() + { + return $this->files()->sum('size') / 1024; // Convert bytes to KB + } + + /** + * Get remaining storage quota in KB + */ + public function getRemainingQuota() + { + $used = $this->getStorageUsed(); + return max(0, $this->storage_quota - $used); + } + + /** + * Check if user has enough quota for a file + */ + public function hasQuotaFor($fileSize) + { + // Convert file size to KB for comparison + $fileSizeKB = $fileSize / 1024; + return $fileSizeKB <= $this->getRemainingQuota(); + } + + /** + * Check if file size is within user's allowed maximum + */ + public function isFileSizeAllowed($fileSize) + { + // Convert to KB for comparison + $fileSizeKB = $fileSize / 1024; + + // If max_file_size is not set (0), use system default + if (!$this->max_file_size) { + return $fileSizeKB <= config('filesystems.max_upload_size', 10240); + } + + return $fileSizeKB <= $this->max_file_size; + } + + /** + * Get default quota based on user role + */ + public static function getDefaultQuota($role = 1) + { + // Define default quotas based on roles (in KB) + $quotas = [ + 0 => 1024 * 1024, // Admin: 1GB + 1 => 100 * 1024, // Regular user: 100MB + 2 => 50 * 1024, // Basic user: 50MB + ]; + + return $quotas[$role] ?? $quotas[1]; // Default to regular user quota + } + + /** + * Get default max file size based on user role + */ + public static function getDefaultMaxFileSize($role = 1) + { + // Define default max file sizes based on roles (in KB) + $sizes = [ + 0 => 100 * 1024, // Admin: 100MB per file + 1 => 10 * 1024, // Regular user: 10MB per file + 2 => 5 * 1024, // Basic user: 5MB per file + ]; + + return $sizes[$role] ?? $sizes[1]; // Default to regular user size + } +} + +// 2. Create migration to add quota fields to users table +// database/migrations/2025_02_22_000001_add_file_quotas_to_users_table.php + +integer('max_file_size')->default(0)->after('role'); // in KB, 0 means use system default + } + + if (!Schema::hasColumn('users', 'storage_quota')) { + $table->integer('storage_quota')->default(102400)->after('max_file_size'); // 100MB in KB + } + }); + + // Set default quotas for existing users based on their roles + $users = User::all(); + foreach ($users as $user) { + $user->storage_quota = User::getDefaultQuota($user->role); + $user->max_file_size = User::getDefaultMaxFileSize($user->role); + $user->save(); + } + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['max_file_size', 'storage_quota']); + }); + } +}; + +// 3. Update FileController to check user quota and file size limits +// Update the store method in app/Http/Controllers/FileController.php + +public function store(Request $request) +{ + // Get current user + $user = Auth::user(); + + // Guest uploads if no user is logged in + if (!$user) { + // You can either block uploads or create a guest user system + return response()->json([ + 'success' => false, + 'message' => 'You must be logged in to upload files.' + ], 403); + } + + $dir = 'uploads'; + if ($request->has('current_dir')) { + // Extract relative directory path if provided + $dir = str_replace('storage/', '', $request->current_dir); + } + + // We'll validate in multiple steps to provide better error messages + + // Step 1: Basic validation + $validated = $request->validate([ + 'f' => 'required|array', + 'f.*' => 'required|file' + ]); + + $files = $request->file('f'); + $totalSize = 0; + + // Step 2: Check each file against user's max file size limit + foreach ($files as $file) { + $fileSize = $file->getSize(); + $totalSize += $fileSize; + + if (!$user->isFileSizeAllowed($fileSize)) { + return response()->json([ + 'success' => false, + 'message' => 'File "' . $file->getClientOriginalName() . '" exceeds your maximum allowed file size of ' . + ($user->max_file_size > 0 ? ($user->max_file_size / 1024) . ' MB' : (config('filesystems.max_upload_size', 10240) / 1024) . ' MB') + ], 413); // 413 Payload Too Large + } + } + + // Step 3: Check total size against remaining quota + if (!$user->hasQuotaFor($totalSize)) { + return response()->json([ + 'success' => false, + 'message' => 'Not enough storage quota. You have ' . round($user->getRemainingQuota() / 1024, 2) . ' MB remaining, but tried to upload ' . round($totalSize / 1024 / 1024, 2) . ' MB.' + ], 413); + } + + $results = [ + 'num_files' => count($files), + 'num_uploaded' => 0, + 'files' => [] + ]; + + // Process tags + $tags = []; + if ($request->has('tags') && !empty($request->tags)) { + $tags = array_map('trim', explode(',', $request->tags)); + } + + foreach ($files as $file) { + $filename = $file->getClientOriginalName(); + + // Avoid duplicate filenames by adding timestamp + if (Storage::disk('public')->exists("${dir}/${filename}")) { + $filename = Carbon::now()->timestamp . '_' . $filename; + } + + // Store the file + $path = $file->storeAs($dir, $filename, 'public'); + + if ($path) { + // Create file record in database + $fileModel = File::create([ + 'filename' => $filename, + 'path' => $path, + 'source' => $request->ip(), + 'description' => '', + 'md5' => md5_file($file->getRealPath()), + 'size' => $file->getSize(), + 'mime_type' => $file->getMimeType(), + 'user_id' => $user->id + ]); + + // Add tags if provided + if (!empty($tags)) { + $fileModel->addTags($tags); + } + + $results['num_uploaded']++; + $results['files'][] = [ + 'filename' => $filename, + 'path' => $path, + 'id' => $fileModel->id + ]; + } + } + + // Return based on requested response format + if ($request->input('response_format') === 'html') { + return redirect()->back()->with('success', "{$results['num_uploaded']} files uploaded successfully"); + } + + return response()->json($results); +} + +// 4. Update the SiteController's uploadFiles method similarly +// Update this method in app/Http/Controllers/SiteController.php + +public function uploadFiles(Request $request) +{ + // Get current user + $user = Auth::user(); + + // Guest uploads handling + if (!$user) { + if ($request->input('response_format') === 'html') { + return redirect()->back()->with('error', 'You must be logged in to upload files.'); + } + + return response()->json([ + 'success' => false, + 'message' => 'You must be logged in to upload files.' + ], 403); + } + + $files = $request->file('f'); + $dir = config('filesystems.upload_dir', 'uploads'); + $returnJSON = $request->input('response_format') !== 'html'; + + $res = [ + 'num_files' => count($files), + 'num_failed' => 0, + 'num_uploaded' => 0, + 'success' => false, + 'files' => [] + ]; + + // Validate files (basic validation) + $validated = $request->validate([ + 'f' => 'required|array', + 'f.*' => 'required|file' + ]); + + // Check individual file size limits and total quota + $totalSize = 0; + + foreach ($files as $file) { + $fileSize = $file->getSize(); + $totalSize += $fileSize; + + if (!$user->isFileSizeAllowed($fileSize)) { + if ($returnJSON) { + return response()->json([ + 'success' => false, + 'message' => 'File "' . $file->getClientOriginalName() . '" exceeds your maximum allowed file size.' + ], 413); + } else { + return redirect()->back()->with('error', 'File "' . $file->getClientOriginalName() . '" exceeds your maximum allowed file size.'); + } + } + } + + if (!$user->hasQuotaFor($totalSize)) { + if ($returnJSON) { + return response()->json([ + 'success' => false, + 'message' => 'Not enough storage quota. You have ' . round($user->getRemainingQuota() / 1024, 2) . ' MB remaining.' + ], 413); + } else { + return redirect()->back()->with('error', 'Not enough storage quota. You have ' . round($user->getRemainingQuota() / 1024, 2) . ' MB remaining.'); + } + } + + // Process tags + $tags = []; + if ($request->has('tags') && !empty($request->tags)) { + $tags = array_map('trim', explode(',', $request->tags)); + } + + foreach ($files as $f) { + $filename = $f->getClientOriginalName(); + + // Avoid duplicate filenames + if (Storage::disk('public')->exists("${dir}/${filename}")) { + $filename = Carbon::now()->timestamp . '_' . $filename; + } + + $path = $f->storeAs($dir, $filename, 'public'); + + if ($path) { + // Create file record in database + $fileModel = File::create([ + 'filename' => $filename, + 'path' => $path, + 'source' => $request->ip(), + 'description' => '', + 'md5' => md5_file($f->getRealPath()), + 'size' => $f->getSize(), + 'mime_type' => $f->getMimeType(), + 'user_id' => $user->id + ]); + + // Add tags if provided + if (!empty($tags)) { + $fileModel->addTags($tags); + } + + $res['num_uploaded']++; + $res['files'][] = [ + 'filename' => $filename, + 'path' => $path, + 'id' => $fileModel->id + ]; + } else { + $res['num_failed']++; + } + } + + if ($res['num_uploaded'] === count($files)) { + $res['success'] = true; + } + + if ($returnJSON) { + return response()->json($res); + } else { + return redirect("f/{$filename}"); + } +} + +// 5. Add a user storage quota dashboard +// Create a new view file: resources/views/files/quota.blade.php + +@extends('template') + +@section('head') + +@endsection + +@section('body') +
+

Your Storage Quota

+ +
+ @php + $user = Auth::user(); + $usedKB = $user->getStorageUsed(); + $totalKB = $user->storage_quota; + $percentUsed = $totalKB > 0 ? min(100, round(($usedKB / $totalKB) * 100)) : 0; + $usedMB = round($usedKB / 1024, 2); + $totalMB = round($totalKB / 1024, 2); + $maxFileSizeMB = $user->max_file_size > 0 ? round($user->max_file_size / 1024, 2) : round(config('filesystems.max_upload_size', 10240) / 1024, 2); + @endphp + + @if ($percentUsed > 90) +
+ Warning: You are using {{ $percentUsed }}% of your storage quota. +
+ @endif + +
+
+
+ +
+
Used: {{ $usedMB }} MB ({{ $percentUsed }}%)
+
Total: {{ $totalMB }} MB
+
+ +
+ Maximum File Size Allowed: {{ $maxFileSizeMB }} MB per file +
+
+ +
+

Your Files

+ + @php + $userFiles = $user->files()->orderBy('created_at', 'desc')->limit(10)->get(); + $totalFiles = $user->files()->count(); + $largestFile = $user->files()->orderBy('size', 'desc')->first(); + $largestFileMB = $largestFile ? round($largestFile->size / 1024 / 1024, 2) : 0; + @endphp + +
+
Total Files: {{ $totalFiles }}
+
Largest File: {{ $largestFileMB }} MB ({{ $largestFile ? $largestFile->filename : 'None' }})
+
+ +

Recently Uploaded Files

+ + @if ($userFiles->count() > 0) + + + + + + + + + + + @foreach ($userFiles as $file) + + + + + + + @endforeach + +
File NameSizeUploadedTags
{{ $file->filename }}{{ round($file->size / 1024 / 1024, 2) }} MB{{ $file->created_at->diffForHumans() }}{{ $file->getTagsString() }}
+ + @if ($totalFiles > 10) +
+ View all files +
+ @endif + @else +

You haven't uploaded any files yet.

+ @endif +
+
+@endsection + +// 6. Add routes for the quota dashboard +// Add this to routes/web.php + +Route::middleware('auth')->group(function () { + Route::get('/files/quota', [FileController::class, 'quotaDashboard'])->name('files.quota'); +}); + +// 7. Add the quota dashboard method to FileController +// Add this to app/Http/Controllers/FileController.php + +/** + * Display the user's storage quota dashboard + */ +public function quotaDashboard() +{ + return view('files.quota'); +} + +// 8. Update the f.blade.php file to show file ownership +// Add this to your existing file listing table + +Owner + + + + @php + $fileOwner = null; + if (isset($fileModel) && $fileModel && $fileModel->user_id) { + $fileOwner = App\Models\User::find($fileModel->user_id); + } + @endphp + {{ $fileOwner ? $fileOwner->name : 'Unknown' }} + + +// 9. Update the file upload form to show quota information +// Add this just before your upload form in f.blade.php + +@auth + @php + $user = Auth::user(); + $usedKB = $user->getStorageUsed(); + $totalKB = $user->storage_quota; + $percentUsed = $totalKB > 0 ? min(100, round(($usedKB / $totalKB) * 100)) : 0; + $usedMB = round($usedKB / 1024, 2); + $totalMB = round($totalKB / 1024, 2); + $maxFileSizeMB = $user->max_file_size > 0 ? round($user->max_file_size / 1024, 2) : round(config('filesystems.max_upload_size', 10240) / 1024, 2); + @endphp + +
+
+ Your Storage: {{ $usedMB }} MB used of {{ $totalMB }} MB ({{ $percentUsed }}%) +
+
+
+
+
+ Maximum file size: {{ $maxFileSizeMB }} MB per file + | View Details +
+
+@endauth \ No newline at end of file diff --git a/models.txt b/models.txt new file mode 100644 index 0000000..340b8e0 --- /dev/null +++ b/models.txt @@ -0,0 +1,21 @@ +Link +@defaults +-label:string +-value:string +-tags:Tag[] +-desc:string +Tag +@defaults +-label:string +-refs:int #how many objects have this tag associated with them +File +-filename:string +-path:string +-source:string #ip, user, link? +-desc:string +-tags:Tag[] +User +-name:string +-email:string +-description:string +-password:string diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 0771532..c99d1c6 100755 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -14,8 +14,7 @@ Welcome

-
Current Status
-
+ Current Status

Number of friends connected:


Who am I?
-- cgit v1.2.3