diff --git a/app/Console/Commands/BuildGameLibrary.php b/app/Console/Commands/BuildGameLibrary.php index 41b3168..54f5e41 100644 --- a/app/Console/Commands/BuildGameLibrary.php +++ b/app/Console/Commands/BuildGameLibrary.php @@ -12,7 +12,7 @@ class BuildGameLibrary extends Command {--limit=100 : Number of games to fetch per batch} {--batches=10 : Number of batches to process} {--start-year=1980 : Start year for game releases} - {--end-year=2024 : End year for game releases}'; + {--end-year=2030 : End year for game releases}'; protected $description = 'Build a comprehensive game library by fetching games in batches'; diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 0739e2e..1f2bb57 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -9,6 +9,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; use Illuminate\Validation\Rules; use Illuminate\View\View; @@ -37,6 +38,7 @@ class RegisteredUserController extends Controller $user = User::create([ 'name' => $request->name, +// 'slug' => $this->generateSlug($request->name), 'email' => $request->email, 'password' => Hash::make($request->password), ]); @@ -47,4 +49,17 @@ class RegisteredUserController extends Controller return redirect(route('dashboard', absolute: false)); } + + private function generateSlug(string $name): string + { + $slug = Str::slug($name); + $originalSlug = $slug; + $count = 1; + + while (User::where('slug', $slug)->exists()) { + $slug = $originalSlug . '-' . $count++; + } + + return $slug; + } } diff --git a/app/Http/Controllers/ForumCategoryController.php b/app/Http/Controllers/ForumCategoryController.php new file mode 100644 index 0000000..0000af6 --- /dev/null +++ b/app/Http/Controllers/ForumCategoryController.php @@ -0,0 +1,105 @@ +get(); + return view('forum.index', compact('categories')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + // This would be restricted to admins in a middleware + return view('forum.categories.create'); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + // This would be restricted to admins in a middleware + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'order' => 'nullable|integer', + ]); + + $validated['slug'] = Str::slug($validated['name']); + + ForumCategory::create($validated); + + return redirect()->route('forum.index') + ->with('success', 'Category created successfully.'); + } + + /** + * Display the specified resource. + */ + public function show(ForumCategory $category) + { + $threads = $category->threads() + ->with('user') + ->orderBy('is_sticky', 'desc') + ->orderBy('last_activity_at', 'desc') + ->paginate(20); + + return view('forum.categories.show', compact('category', 'threads')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(ForumCategory $category) + { + // This would be restricted to admins in a middleware + return view('forum.categories.edit', compact('category')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ForumCategory $category) + { + // This would be restricted to admins in a middleware + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'order' => 'nullable|integer', + ]); + + if ($request->name !== $category->name) { + $validated['slug'] = Str::slug($validated['name']); + } + + $category->update($validated); + + return redirect()->route('forum.index') + ->with('success', 'Category updated successfully.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ForumCategory $category) + { + // This would be restricted to admins in a middleware + $category->delete(); + + return redirect()->route('forum.index') + ->with('success', 'Category deleted successfully.'); + } +} diff --git a/app/Http/Controllers/ForumPostController.php b/app/Http/Controllers/ForumPostController.php new file mode 100644 index 0000000..2cec934 --- /dev/null +++ b/app/Http/Controllers/ForumPostController.php @@ -0,0 +1,177 @@ +route('forum.index'); + } + + /** + * Show the form for creating a new resource. + */ + public function create(Request $request) + { + $topic_id = $request->query('topic_id'); + if (!$topic_id) { + return redirect()->route('forum.index') + ->with('error', 'Topic ID is required to create a post.'); + } + + $topic = ForumTopic::findOrFail($topic_id); + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot create new posts.'); + } + + return view('forum.posts.create', compact('topic', 'thread')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'content' => 'required|string', + 'topic_id' => 'required|exists:forum_topics,id', + ]); + + $topic = ForumTopic::findOrFail($validated['topic_id']); + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot create new posts.'); + } + + // Create the post + $post = new ForumPost(); + $post->topic_id = $validated['topic_id']; + $post->user_id = auth()->id(); + $post->content = $validated['content']; + $post->save(); + + // Update topic and thread last activity + $topic->last_activity_at = now(); + $topic->save(); + + $thread->last_activity_at = now(); + $thread->save(); + + return redirect()->route('forum.topics.show', $topic) + ->with('success', 'Post created successfully.'); + } + + /** + * Display the specified resource. + */ + public function show(ForumPost $post) + { + // Redirect to the topic with an anchor to the post + return redirect()->route('forum.topics.show', $post->topic) . '#post-' . $post->id; + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(ForumPost $post) + { + // Check if user is authorized to edit this post + if (auth()->id() !== $post->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.topics.show', $post->topic) + ->with('error', 'You are not authorized to edit this post.'); + } + + $topic = $post->topic; + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot edit posts.'); + } + + return view('forum.posts.edit', compact('post', 'topic', 'thread')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ForumPost $post) + { + // Check if user is authorized to edit this post + if (auth()->id() !== $post->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.topics.show', $post->topic) + ->with('error', 'You are not authorized to edit this post.'); + } + + $topic = $post->topic; + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot edit posts.'); + } + + $validated = $request->validate([ + 'content' => 'required|string', + ]); + + // Update the post + $post->content = $validated['content']; + $post->edited_at = now(); + $post->save(); + + return redirect()->route('forum.topics.show', $topic) + ->with('success', 'Post updated successfully.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ForumPost $post) + { + // Check if user is authorized to delete this post + if (auth()->id() !== $post->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.topics.show', $post->topic) + ->with('error', 'You are not authorized to delete this post.'); + } + + $topic = $post->topic; + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot delete posts.'); + } + + // Check if this is the only post in the topic + if ($topic->posts()->count() <= 1) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'Cannot delete the only post in a topic. Delete the topic instead.'); + } + + $post->delete(); + + return redirect()->route('forum.topics.show', $topic) + ->with('success', 'Post deleted successfully.'); + } +} diff --git a/app/Http/Controllers/ForumThreadController.php b/app/Http/Controllers/ForumThreadController.php new file mode 100644 index 0000000..4c8a7b1 --- /dev/null +++ b/app/Http/Controllers/ForumThreadController.php @@ -0,0 +1,159 @@ +orderBy('is_sticky', 'desc') + ->orderBy('last_activity_at', 'desc') + ->paginate(20); + + return view('forum.threads.index', compact('threads')); + } + + /** + * Show the form for creating a new resource. + */ + public function create(Request $request) + { + $categories = ForumCategory::orderBy('order')->get(); + $category_id = $request->query('category_id'); + + return view('forum.threads.create', compact('categories', 'category_id')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + 'category_id' => 'required|exists:forum_categories,id', + ]); + + // Create the thread + $thread = new ForumThread(); + $thread->title = $validated['title']; + $thread->slug = Str::slug($validated['title']) . '-' . uniqid(); + $thread->category_id = $validated['category_id']; + $thread->user_id = auth()->id(); + $thread->last_activity_at = now(); + $thread->save(); + + // Create the initial topic + $topic = new ForumTopic(); + $topic->thread_id = $thread->id; + $topic->user_id = auth()->id(); + $topic->title = $validated['title']; + $topic->content = $validated['content']; + $topic->last_activity_at = now(); + $topic->save(); + + return redirect()->route('forum.threads.show', $thread) + ->with('success', 'Thread created successfully.'); + } + + /** + * Display the specified resource. + */ + public function show(ForumThread $thread) + { + // Increment view count + $thread->increment('views'); + + // Get topics with pagination + $topics = $thread->topics() + ->with('user') + ->orderBy('is_pinned', 'desc') + ->orderBy('created_at', 'asc') + ->paginate(15); + + return view('forum.threads.show', compact('thread', 'topics')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(ForumThread $thread) + { + // Check if user is authorized to edit this thread + if (auth()->id() !== $thread->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.threads.show', $thread) + ->with('error', 'You are not authorized to edit this thread.'); + } + + $categories = ForumCategory::orderBy('order')->get(); + $initialTopic = $thread->topics()->oldest()->first(); + + return view('forum.threads.edit', compact('thread', 'categories', 'initialTopic')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ForumThread $thread) + { + // Check if user is authorized to edit this thread + if (auth()->id() !== $thread->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.threads.show', $thread) + ->with('error', 'You are not authorized to edit this thread.'); + } + + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + 'category_id' => 'required|exists:forum_categories,id', + ]); + + // Update the thread + $thread->title = $validated['title']; + if ($thread->isDirty('title')) { + $thread->slug = Str::slug($validated['title']) . '-' . uniqid(); + } + $thread->category_id = $validated['category_id']; + $thread->save(); + + // Update the initial topic + $initialTopic = $thread->topics()->oldest()->first(); + if ($initialTopic) { + $initialTopic->title = $validated['title']; + $initialTopic->content = $validated['content']; + $initialTopic->save(); + } + + return redirect()->route('forum.threads.show', $thread) + ->with('success', 'Thread updated successfully.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ForumThread $thread) + { + // Check if user is authorized to delete this thread + if (auth()->id() !== $thread->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.threads.show', $thread) + ->with('error', 'You are not authorized to delete this thread.'); + } + + $categoryId = $thread->category_id; + $thread->delete(); + + return redirect()->route('forum.categories.show', ['category' => ForumCategory::find($categoryId)]) + ->with('success', 'Thread deleted successfully.'); + } +} diff --git a/app/Http/Controllers/ForumTopicController.php b/app/Http/Controllers/ForumTopicController.php new file mode 100644 index 0000000..34109fa --- /dev/null +++ b/app/Http/Controllers/ForumTopicController.php @@ -0,0 +1,180 @@ +route('forum.index'); + } + + /** + * Show the form for creating a new resource. + */ + public function create(Request $request) + { + $thread_id = $request->query('thread_id'); + if (!$thread_id) { + return redirect()->route('forum.index') + ->with('error', 'Thread ID is required to create a topic.'); + } + + $thread = ForumThread::findOrFail($thread_id); + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.threads.show', $thread) + ->with('error', 'This thread is locked. You cannot create new topics.'); + } + + return view('forum.topics.create', compact('thread')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + 'thread_id' => 'required|exists:forum_threads,id', + ]); + + $thread = ForumThread::findOrFail($validated['thread_id']); + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.threads.show', $thread) + ->with('error', 'This thread is locked. You cannot create new topics.'); + } + + // Create the topic + $topic = new ForumTopic(); + $topic->thread_id = $validated['thread_id']; + $topic->user_id = auth()->id(); + $topic->title = $validated['title']; + $topic->content = $validated['content']; + $topic->last_activity_at = now(); + $topic->save(); + + // Update thread's last activity + $thread->last_activity_at = now(); + $thread->save(); + + return redirect()->route('forum.topics.show', $topic) + ->with('success', 'Topic created successfully.'); + } + + /** + * Display the specified resource. + */ + public function show(ForumTopic $topic) + { + $thread = $topic->thread; + + // Get posts with pagination + $posts = $topic->posts() + ->with('user') + ->orderBy('created_at', 'asc') + ->paginate(15); + + return view('forum.topics.show', compact('topic', 'thread', 'posts')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(ForumTopic $topic) + { + // Check if user is authorized to edit this topic + if (auth()->id() !== $topic->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'You are not authorized to edit this topic.'); + } + + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot edit topics.'); + } + + return view('forum.topics.edit', compact('topic', 'thread')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ForumTopic $topic) + { + // Check if user is authorized to edit this topic + if (auth()->id() !== $topic->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'You are not authorized to edit this topic.'); + } + + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot edit topics.'); + } + + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + ]); + + // Update the topic + $topic->title = $validated['title']; + $topic->content = $validated['content']; + $topic->save(); + + return redirect()->route('forum.topics.show', $topic) + ->with('success', 'Topic updated successfully.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ForumTopic $topic) + { + // Check if user is authorized to delete this topic + if (auth()->id() !== $topic->user_id && !auth()->user()->hasRole('admin')) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'You are not authorized to delete this topic.'); + } + + $thread = $topic->thread; + + // Check if thread is locked + if ($thread->is_locked) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'This thread is locked. You cannot delete topics.'); + } + + // Check if this is the only topic in the thread + if ($thread->topics()->count() <= 1) { + return redirect()->route('forum.topics.show', $topic) + ->with('error', 'Cannot delete the only topic in a thread. Delete the thread instead.'); + } + + $topic->delete(); + + return redirect()->route('forum.threads.show', $thread) + ->with('success', 'Topic deleted successfully.'); + } +} diff --git a/app/Http/Controllers/GameController.php b/app/Http/Controllers/GameController.php index c8d0536..58960ec 100644 --- a/app/Http/Controllers/GameController.php +++ b/app/Http/Controllers/GameController.php @@ -101,7 +101,77 @@ class GameController extends Controller public function show(Game $game) { - $game->load(['platforms', 'genres', 'screenshots', 'videos', 'similar_games', 'dlcs', 'reviews.user']); - return view('games.show', compact('game')); + // Load game relationships except reviews + $game->load(['platforms', 'genres', 'screenshots', 'videos', 'similar_games', 'dlcs']); + + // Load reviews with users separately (could be paginated if needed) + $reviews = $game->reviews()->with('user')->get(); + + // Calculate rating distribution percentages + $ratingDistribution = $this->calculateRatingDistribution($game); + + return view('games.show', compact('game', 'ratingDistribution', 'reviews')); + } + + /** + * Calculate the rating distribution percentages for a game + * + * @param Game $game + * @return array + */ + private function calculateRatingDistribution(Game $game) + { + $distribution = []; + + // Get the total number of reviews for this game + $totalReviews = $game->reviews()->count(); + + // If there are no reviews, return empty distribution + if ($totalReviews === 0) { + for ($i = 10; $i >= 1; $i--) { + $distribution[] = [ + 'range' => $i, + 'count' => 0, + 'percentage' => 0 + ]; + } + return $distribution; + } + + // Get the count of reviews for each rating range directly from the database + $ratingCounts = $game->reviews() + ->selectRaw(' + CASE + WHEN rating = 1 THEN 1 + WHEN rating = 2 THEN 2 + WHEN rating = 3 THEN 3 + WHEN rating = 4 THEN 4 + WHEN rating = 5 THEN 5 + WHEN rating = 6 THEN 6 + WHEN rating = 7 THEN 7 + WHEN rating = 8 THEN 8 + WHEN rating = 9 THEN 9 + WHEN rating = 10 THEN 10 + END as range_id, + COUNT(*) as count + ') + ->groupBy('range_id') + ->orderBy('range_id', 'desc') + ->get() + ->keyBy('range_id'); + + // Build the distribution array with percentages + for ($i = 10; $i >= 1; $i--) { + $count = $ratingCounts->get($i)->count ?? 0; + $percentage = ($count / $totalReviews) * 100; + + $distribution[] = [ + 'range' => $i, + 'count' => $count, + 'percentage' => $percentage + ]; + } + + return $distribution; } } diff --git a/app/Http/Controllers/ReviewController.php b/app/Http/Controllers/ReviewController.php index 85b7e93..2ef04d8 100644 --- a/app/Http/Controllers/ReviewController.php +++ b/app/Http/Controllers/ReviewController.php @@ -22,7 +22,8 @@ class ReviewController extends Controller public function create(Game $game) { - return view('reviews.create', compact('game')); + $platforms = $game->platforms()->get(); + return view('reviews.create', compact('game', 'platforms')); } public function store(Request $request, Game $game) @@ -31,6 +32,7 @@ class ReviewController extends Controller 'content' => 'required|string|min:10|max:1000', 'recommended' => 'sometimes|nullable|boolean', 'rating' => 'required|between:1,10', + 'platform_id' => 'required|exists:platforms,id', ]); if (!isset($validated['recommended'])) { @@ -54,7 +56,8 @@ class ReviewController extends Controller public function edit(Game $game, Review $review) { $this->authorize('update', $review); - return view('reviews.edit', compact('game', 'review')); + $platforms = $game->platforms; + return view('reviews.edit', compact('game', 'review', 'platforms')); } public function update(Request $request, Game $game, Review $review) @@ -64,7 +67,8 @@ class ReviewController extends Controller $validated = $request->validate([ 'content' => 'required|string|min:10|max:1000', 'recommended' => 'required|boolean', - 'rating' => 'required|integer|between:1,20', + 'rating' => 'required|integer|between:1,10', + 'platform_id' => 'required|exists:platforms,id', ]); $review->update($validated); diff --git a/app/Models/ForumCategory.php b/app/Models/ForumCategory.php new file mode 100644 index 0000000..ac0e2c8 --- /dev/null +++ b/app/Models/ForumCategory.php @@ -0,0 +1,33 @@ +hasMany(ForumThread::class, 'category_id'); + } + + /** + * Get the route key for the model. + * + * @return string + */ + public function getRouteKeyName() + { + return 'slug'; + } +} diff --git a/app/Models/ForumPost.php b/app/Models/ForumPost.php new file mode 100644 index 0000000..4f136e5 --- /dev/null +++ b/app/Models/ForumPost.php @@ -0,0 +1,37 @@ + 'boolean', + 'edited_at' => 'datetime', + ]; + + /** + * Get the topic that owns the post. + */ + public function topic() + { + return $this->belongsTo(ForumTopic::class, 'topic_id'); + } + + /** + * Get the user that owns the post. + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/ForumThread.php b/app/Models/ForumThread.php new file mode 100644 index 0000000..0fa9d50 --- /dev/null +++ b/app/Models/ForumThread.php @@ -0,0 +1,59 @@ + 'boolean', + 'is_locked' => 'boolean', + 'last_activity_at' => 'datetime', + ]; + + /** + * Get the category that owns the thread. + */ + public function category() + { + return $this->belongsTo(ForumCategory::class, 'category_id'); + } + + /** + * Get the user that owns the thread. + */ + public function user() + { + return $this->belongsTo(User::class); + } + + /** + * Get the topics for the thread. + */ + public function topics() + { + return $this->hasMany(ForumTopic::class, 'thread_id'); + } + + /** + * Get the route key for the model. + * + * @return string + */ + public function getRouteKeyName() + { + return 'slug'; + } +} diff --git a/app/Models/ForumTopic.php b/app/Models/ForumTopic.php new file mode 100644 index 0000000..3145ce7 --- /dev/null +++ b/app/Models/ForumTopic.php @@ -0,0 +1,46 @@ + 'boolean', + 'last_activity_at' => 'datetime', + ]; + + /** + * Get the thread that owns the topic. + */ + public function thread() + { + return $this->belongsTo(ForumThread::class, 'thread_id'); + } + + /** + * Get the user that owns the topic. + */ + public function user() + { + return $this->belongsTo(User::class); + } + + /** + * Get the posts for the topic. + */ + public function posts() + { + return $this->hasMany(ForumPost::class, 'topic_id'); + } +} diff --git a/app/Models/Review.php b/app/Models/Review.php index 3c088f0..8397fe6 100644 --- a/app/Models/Review.php +++ b/app/Models/Review.php @@ -16,7 +16,7 @@ class Review extends Model 'content', 'recommended', 'rating', - 'platform', + 'platform_id', ]; protected $casts = [ @@ -33,4 +33,9 @@ class Review extends Model { return $this->belongsTo(Game::class); } + + public function platform(): BelongsTo + { + return $this->belongsTo(Platform::class); + } } diff --git a/app/Models/User.php b/app/Models/User.php index b9af0c9..16a7b8f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,11 +9,12 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Relations\HasMany; use Laravel\Sanctum\HasApiTokens; use Illuminate\Support\Str; +use Multicaret\Acquaintances\Traits\Friendable; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasFactory, Notifiable, Friendable; /** * The attributes that are mass assignable. @@ -47,7 +48,7 @@ class User extends Authenticatable 'password' => 'hashed', ]; - protected static function boot() + protected static function boot(): void { parent::boot(); @@ -93,4 +94,28 @@ class User extends Authenticatable { return $this->hasMany(GameList::class); } + + /** + * Get the forum threads created by the user. + */ + public function forumThreads() + { + return $this->hasMany(ForumThread::class); + } + + /** + * Get the forum topics created by the user. + */ + public function forumTopics() + { + return $this->hasMany(ForumTopic::class); + } + + /** + * Get the forum posts created by the user. + */ + public function forumPosts() + { + return $this->hasMany(ForumPost::class); + } } diff --git a/composer.json b/composer.json index 006146c..7d32a34 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "imdbphp/imdbphp": "^3.3", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", - "marcreichel/igdb-laravel": "^5.3" + "marcreichel/igdb-laravel": "^5.3", + "multicaret/laravel-acquaintances": "^3.7" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.15", diff --git a/composer.lock b/composer.lock index 7399ce8..a108da5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0814da2c93b6041a9dc275ff1eda9043", + "content-hash": "9a128482d8d2ecd18a2ab97af440d77e", "packages": [ { "name": "brick/math", @@ -2216,6 +2216,76 @@ ], "time": "2025-03-24T10:02:05+00:00" }, + { + "name": "multicaret/laravel-acquaintances", + "version": "v3.7.2", + "source": { + "type": "git", + "url": "https://github.com/multicaret/laravel-acquaintances.git", + "reference": "18afb0079aa4aae580b7659db7ecd4480cc2fe96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/multicaret/laravel-acquaintances/zipball/18afb0079aa4aae580b7659db7ecd4480cc2fe96", + "reference": "18afb0079aa4aae580b7659db7ecd4480cc2fe96", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "^0.3.2", + "doctrine/dbal": "^2.5", + "fzaninotto/faker": "~1.4", + "laravel/laravel": "5.*|^9.0|^10.0|^11.0", + "mockery/mockery": "1.0.x-dev", + "phpunit/phpunit": "5.*" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Multicaret\\Acquaintances\\AcquaintancesServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Multicaret\\Acquaintances\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mohamed Kawsara", + "email": "mkwsra@gmail.com" + } + ], + "description": "This light package, with no dependencies, gives Eloquent models the ability to manage friendships (with groups). And interactions such as: Likes, favorites, votes, subscribe, follow, ..etc. And it includes advanced rating system.", + "keywords": [ + "Followers", + "Rate", + "Rating", + "eloquent", + "followships", + "friend-system", + "friends", + "friendships", + "interactions", + "laravel", + "like", + "social-media", + "wish-list" + ], + "support": { + "issues": "https://github.com/multicaret/laravel-acquaintances/issues", + "source": "https://github.com/multicaret/laravel-acquaintances/tree/v3.7.2" + }, + "time": "2025-03-23T22:12:11+00:00" + }, { "name": "nesbot/carbon", "version": "3.9.0", diff --git a/config/acquaintances.php b/config/acquaintances.php new file mode 100644 index 0000000..2296c5a --- /dev/null +++ b/config/acquaintances.php @@ -0,0 +1,87 @@ + false, + + /* + * Models Related. + */ + 'model_namespace' => (int) app()->version() <= 7 ? 'App' : 'App\Models', + 'models' => [ + /* + * Model name of User model + */ + 'user' => 'User', + /* + * Model name of Interaction Relation model + */ + 'interaction_relation' => \Multicaret\Acquaintances\Models\InteractionRelation::class, + /* + * Model name of Interaction Relation model + */ + 'friendship' => \Multicaret\Acquaintances\Models\Friendship::class, + /* + * Model name of Interaction Relation model + */ + 'friendship_groups' => \Multicaret\Acquaintances\Models\FriendFriendshipGroups::class, + ], + + 'tables' => [ + /* + * Table name of interactions relations. + */ + 'interactions' => 'interactions', + /* + * user foreign key column name within interactions table. + */ + 'interactions_user_id_fk_column_name' => 'user_id', + /* + * user foreign key column type within interactions table. + */ + 'interactions_user_id_fk_column_type' => 'unsignedBigInteger', + /* + * Table name of friendships relations. + */ + 'friendships' => 'friendships', + /* + * Table name of friendship Groups relations. + */ + 'friendship_groups' => 'friendship_groups', + ], + + 'rating' => [ + 'defaults' => [ + 'amount' => 5, + /* + * Default type here is 'general', as longs as you have one criteria of rating a model + * you can ignore this setting. + * It will be the default type of rating of null is provided, if you wish to tweak this type name + * use the value below as you wish. + * + */ + 'type' => 'general', + ], + 'types' => [ + /* Add any other type that your website/application have here, + * the following added rating types are for demonstration purposes only. + * There is no effect on deleting them nor adding to them, however, its a good practice + * to not hard code your rating types, hence, please use this simple array + */ + 'delivery-time', + 'quality', + 'communication', + 'commitment', + ] + ], + + 'friendships_groups' => [ + 'acquaintances' => 0, + 'close_friends' => 1, + 'family' => 2 + ], + +]; diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 05fb5d9..9097efb 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -14,6 +14,7 @@ return new class extends Migration Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); + $table->string('slug')->unique(); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); diff --git a/database/migrations/2025_04_12_154639_add_slug_to_users_table.php b/database/migrations/2025_04_12_121455_add_platform_id_to_reviews_table.php similarity index 53% rename from database/migrations/2025_04_12_154639_add_slug_to_users_table.php rename to database/migrations/2025_04_12_121455_add_platform_id_to_reviews_table.php index 96fd296..fab0935 100644 --- a/database/migrations/2025_04_12_154639_add_slug_to_users_table.php +++ b/database/migrations/2025_04_12_121455_add_platform_id_to_reviews_table.php @@ -11,8 +11,8 @@ return new class extends Migration */ public function up(): void { - Schema::table('users', function (Blueprint $table) { - $table->string('slug')->unique()->after('name'); + Schema::table('reviews', function (Blueprint $table) { + $table->foreignId('platform_id')->nullable()->after('rating')->constrained()->onDelete('set null'); }); } @@ -21,8 +21,9 @@ return new class extends Migration */ public function down(): void { - Schema::table('users', function (Blueprint $table) { - $table->dropColumn('slug'); + Schema::table('reviews', function (Blueprint $table) { + $table->dropForeign(['platform_id']); + $table->dropColumn('platform_id'); }); } }; diff --git a/database/migrations/2025_07_13_122343_create_acquaintances_friendship_table.php b/database/migrations/2025_07_13_122343_create_acquaintances_friendship_table.php new file mode 100644 index 0000000..add712b --- /dev/null +++ b/database/migrations/2025_07_13_122343_create_acquaintances_friendship_table.php @@ -0,0 +1,27 @@ +id(); + $table->morphs('sender'); + $table->morphs('recipient'); + $table->string('status')->default('pending')->comment('pending/accepted/denied/blocked/'); + $table->timestamps(); + }); + + } + + public function down() + { + Schema::dropIfExists(config('acquaintances.tables.friendships')); + } + +} diff --git a/database/migrations/2025_07_13_122343_create_acquaintances_friendships_groups_table.php b/database/migrations/2025_07_13_122343_create_acquaintances_friendships_groups_table.php new file mode 100644 index 0000000..ce8a67f --- /dev/null +++ b/database/migrations/2025_07_13_122343_create_acquaintances_friendships_groups_table.php @@ -0,0 +1,38 @@ +id(); + + $table->unsignedBigInteger('friendship_id')->unsigned(); + $table->morphs('friend'); + $table->integer('group_id')->unsigned(); + + $table->foreign('friendship_id') + ->references('id') + ->on(config('acquaintances.tables.friendships')) + ->onDelete('cascade'); + + $table->unique(['friendship_id', 'friend_id', 'friend_type', 'group_id'], 'unique'); + + }); + + } + + public function down() + { + Schema::dropIfExists(config('acquaintances.tables.friendship_groups')); + } + +} diff --git a/database/migrations/2025_07_13_122343_create_acquaintances_interactions_table.php b/database/migrations/2025_07_13_122343_create_acquaintances_interactions_table.php new file mode 100644 index 0000000..92a879f --- /dev/null +++ b/database/migrations/2025_07_13_122343_create_acquaintances_interactions_table.php @@ -0,0 +1,50 @@ +id(); + + $userModel = Interaction::getUserModelName(); + $userModel = (new $userModel); + $userIdFkColumnName = config('acquaintances.tables.interactions_user_id_fk_column_name', 'user_id'); + + $userIdFkType = config('acquaintances.tables.interactions_user_id_fk_column_type'); + $table->{$userIdFkType}($userIdFkColumnName)->index(); + $table->morphs('subject'); + $table->string('relation')->default('follow')->comment('follow/like/subscribe/favorite/upvote/downvote'); + $table->double('relation_value')->nullable(); + $table->string('relation_type')->nullable(); + $table->timestamps(); + + + $table->foreign($userIdFkColumnName) + ->references($userModel->getKeyName()) + ->on($userModel->getTable()) + ->onUpdate('cascade') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table(config('acquaintances.tables.interactions', 'interactions'), function ($table) { + $table->dropForeign(config('acquaintances.tables.interactions', 'interactions').'_user_id_foreign'); + }); + + Schema::drop(config('acquaintances.tables.interactions', 'interactions')); + } +} diff --git a/database/migrations/2025_07_19_200202_create_forum_categories_table.php b/database/migrations/2025_07_19_200202_create_forum_categories_table.php new file mode 100644 index 0000000..d2ad9cb --- /dev/null +++ b/database/migrations/2025_07_19_200202_create_forum_categories_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->integer('order')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('forum_categories'); + } +}; diff --git a/database/migrations/2025_07_19_200231_create_forum_threads_table.php b/database/migrations/2025_07_19_200231_create_forum_threads_table.php new file mode 100644 index 0000000..e8ed06d --- /dev/null +++ b/database/migrations/2025_07_19_200231_create_forum_threads_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('category_id')->constrained('forum_categories')->onDelete('cascade'); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('title'); + $table->string('slug')->unique(); + $table->boolean('is_sticky')->default(false); + $table->boolean('is_locked')->default(false); + $table->integer('views')->default(0); + $table->timestamp('last_activity_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('forum_threads'); + } +}; diff --git a/database/migrations/2025_07_19_200301_create_forum_topics_table.php b/database/migrations/2025_07_19_200301_create_forum_topics_table.php new file mode 100644 index 0000000..8d35563 --- /dev/null +++ b/database/migrations/2025_07_19_200301_create_forum_topics_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('thread_id')->constrained('forum_threads')->onDelete('cascade'); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('title'); + $table->text('content'); + $table->boolean('is_pinned')->default(false); + $table->timestamp('last_activity_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('forum_topics'); + } +}; diff --git a/database/migrations/2025_07_19_200330_create_forum_posts_table.php b/database/migrations/2025_07_19_200330_create_forum_posts_table.php new file mode 100644 index 0000000..f9277eb --- /dev/null +++ b/database/migrations/2025_07_19_200330_create_forum_posts_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('topic_id')->constrained('forum_topics')->onDelete('cascade'); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->text('content'); + $table->boolean('is_solution')->default(false); + $table->timestamp('edited_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('forum_posts'); + } +}; diff --git a/docker-compose.yml b/docker-compose.yml index e804819..9921fee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ services: laravel.test: build: - context: './vendor/laravel/sail/runtimes/8.4' + context: ./docker/8.4 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' - image: 'sail-8.4/app' + image: sail-8.4/app extra_hosts: - 'host.docker.internal:host-gateway' ports: @@ -36,7 +36,7 @@ services: MYSQL_ALLOW_EMPTY_PASSWORD: 1 volumes: - 'sail-mysql:/var/lib/mysql' - - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' + - './docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' networks: - sail healthcheck: diff --git a/docker/8.0/Dockerfile b/docker/8.0/Dockerfile new file mode 100644 index 0000000..b7b34e2 --- /dev/null +++ b/docker/8.0/Dockerfile @@ -0,0 +1,70 @@ +FROM ubuntu:24.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP +ARG NODE_VERSION=22 +ARG POSTGRES_VERSION=17 + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC +ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" +ENV SUPERVISOR_PHP_USER="sail" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom + +RUN apt-get update && apt-get upgrade -y \ + && mkdir -p /etc/apt/keyrings \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \ + && echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.0-cli php8.0-dev \ + php8.0-pgsql php8.0-sqlite3 php8.0-gd php8.0-imagick \ + php8.0-curl php8.0-memcached php8.0-mongodb \ + php8.0-imap php8.0-mysql php8.0-mbstring \ + php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap \ + php8.0-intl php8.0-readline php8.0-pcov \ + php8.0-msgpack php8.0-igbinary php8.0-ldap \ + php8.0-redis php8.0-swoole php8.0-xdebug \ + && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && npm install -g npm \ + && npm install -g bun \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null \ + && echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \ + && echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y mysql-client \ + && apt-get install -y postgresql-client-$POSTGRES_VERSION \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN update-alternatives --set php /usr/bin/php8.0 + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.0 + +RUN userdel -r ubuntu +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.0/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 80/tcp + +ENTRYPOINT ["start-container"] diff --git a/docker/8.0/php.ini b/docker/8.0/php.ini new file mode 100644 index 0000000..0d8ce9e --- /dev/null +++ b/docker/8.0/php.ini @@ -0,0 +1,5 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +variables_order = EGPCS +pcov.directory = . diff --git a/docker/8.0/start-container b/docker/8.0/start-container new file mode 100644 index 0000000..40c55df --- /dev/null +++ b/docker/8.0/start-container @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then + echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." + exit 1 +fi + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ]; then + if [ "$SUPERVISOR_PHP_USER" = "root" ]; then + exec "$@" + else + exec gosu $WWWUSER "$@" + fi +else + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.0/supervisord.conf b/docker/8.0/supervisord.conf new file mode 100644 index 0000000..656da8a --- /dev/null +++ b/docker/8.0/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=%(ENV_SUPERVISOR_PHP_COMMAND)s +user=%(ENV_SUPERVISOR_PHP_USER)s +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/8.1/Dockerfile b/docker/8.1/Dockerfile new file mode 100644 index 0000000..cc5c611 --- /dev/null +++ b/docker/8.1/Dockerfile @@ -0,0 +1,69 @@ +FROM ubuntu:24.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP +ARG NODE_VERSION=22 +ARG POSTGRES_VERSION=17 + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC +ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" +ENV SUPERVISOR_PHP_USER="sail" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom + +RUN apt-get update && apt-get upgrade -y \ + && mkdir -p /etc/apt/keyrings \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \ + && echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.1-cli php8.1-dev \ + php8.1-pgsql php8.1-sqlite3 php8.1-gd php8.1-imagick \ + php8.1-curl php8.1-mongodb \ + php8.1-imap php8.1-mysql php8.1-mbstring \ + php8.1-xml php8.1-zip php8.1-bcmath php8.1-soap \ + php8.1-intl php8.1-readline \ + php8.1-ldap \ + php8.1-msgpack php8.1-igbinary php8.1-redis php8.1-swoole \ + php8.1-memcached php8.1-pcov php8.1-xdebug \ + && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && npm install -g npm \ + && npm install -g bun \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarn.gpg >/dev/null \ + && echo "deb [signed-by=/usr/share/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \ + && echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y mysql-client \ + && apt-get install -y postgresql-client-$POSTGRES_VERSION \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.1 + +RUN userdel -r ubuntu +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.1/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 80/tcp + +ENTRYPOINT ["start-container"] diff --git a/docker/8.1/php.ini b/docker/8.1/php.ini new file mode 100644 index 0000000..0d8ce9e --- /dev/null +++ b/docker/8.1/php.ini @@ -0,0 +1,5 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +variables_order = EGPCS +pcov.directory = . diff --git a/docker/8.1/start-container b/docker/8.1/start-container new file mode 100644 index 0000000..40c55df --- /dev/null +++ b/docker/8.1/start-container @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then + echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." + exit 1 +fi + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ]; then + if [ "$SUPERVISOR_PHP_USER" = "root" ]; then + exec "$@" + else + exec gosu $WWWUSER "$@" + fi +else + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.1/supervisord.conf b/docker/8.1/supervisord.conf new file mode 100644 index 0000000..656da8a --- /dev/null +++ b/docker/8.1/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=%(ENV_SUPERVISOR_PHP_COMMAND)s +user=%(ENV_SUPERVISOR_PHP_USER)s +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/8.2/Dockerfile b/docker/8.2/Dockerfile new file mode 100644 index 0000000..536dffe --- /dev/null +++ b/docker/8.2/Dockerfile @@ -0,0 +1,70 @@ +FROM ubuntu:24.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP +ARG NODE_VERSION=22 +ARG POSTGRES_VERSION=17 + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC +ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" +ENV SUPERVISOR_PHP_USER="sail" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom + +RUN apt-get update && apt-get upgrade -y \ + && mkdir -p /etc/apt/keyrings \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.2-cli php8.2-dev \ + php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \ + php8.2-curl php8.2-mongodb \ + php8.2-imap php8.2-mysql php8.2-mbstring \ + php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \ + php8.2-intl php8.2-readline \ + php8.2-ldap \ + php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \ + php8.2-memcached php8.2-pcov php8.2-xdebug \ + && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && npm install -g npm \ + && npm install -g pnpm \ + && npm install -g bun \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y mysql-client \ + && apt-get install -y postgresql-client-$POSTGRES_VERSION \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2 + +RUN userdel -r ubuntu +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.2/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 80/tcp + +ENTRYPOINT ["start-container"] diff --git a/docker/8.2/php.ini b/docker/8.2/php.ini new file mode 100644 index 0000000..0d8ce9e --- /dev/null +++ b/docker/8.2/php.ini @@ -0,0 +1,5 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +variables_order = EGPCS +pcov.directory = . diff --git a/docker/8.2/start-container b/docker/8.2/start-container new file mode 100644 index 0000000..40c55df --- /dev/null +++ b/docker/8.2/start-container @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then + echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." + exit 1 +fi + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ]; then + if [ "$SUPERVISOR_PHP_USER" = "root" ]; then + exec "$@" + else + exec gosu $WWWUSER "$@" + fi +else + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.2/supervisord.conf b/docker/8.2/supervisord.conf new file mode 100644 index 0000000..656da8a --- /dev/null +++ b/docker/8.2/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=%(ENV_SUPERVISOR_PHP_COMMAND)s +user=%(ENV_SUPERVISOR_PHP_USER)s +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/8.3/Dockerfile b/docker/8.3/Dockerfile new file mode 100644 index 0000000..ef5904b --- /dev/null +++ b/docker/8.3/Dockerfile @@ -0,0 +1,71 @@ +FROM ubuntu:24.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP +ARG NODE_VERSION=22 +ARG MYSQL_CLIENT="mysql-client" +ARG POSTGRES_VERSION=17 + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC +ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" +ENV SUPERVISOR_PHP_USER="sail" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom + +RUN apt-get update && apt-get upgrade -y \ + && mkdir -p /etc/apt/keyrings \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.3-cli php8.3-dev \ + php8.3-pgsql php8.3-sqlite3 php8.3-gd \ + php8.3-curl php8.3-mongodb \ + php8.3-imap php8.3-mysql php8.3-mbstring \ + php8.3-xml php8.3-zip php8.3-bcmath php8.3-soap \ + php8.3-intl php8.3-readline \ + php8.3-ldap \ + php8.3-msgpack php8.3-igbinary php8.3-redis \ + php8.3-memcached php8.3-pcov php8.3-imagick php8.3-xdebug php8.3-swoole \ + && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && npm install -g npm \ + && npm install -g pnpm \ + && npm install -g bun \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y $MYSQL_CLIENT \ + && apt-get install -y postgresql-client-$POSTGRES_VERSION \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3 + +RUN userdel -r ubuntu +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.3/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 80/tcp + +ENTRYPOINT ["start-container"] diff --git a/docker/8.3/php.ini b/docker/8.3/php.ini new file mode 100644 index 0000000..0d8ce9e --- /dev/null +++ b/docker/8.3/php.ini @@ -0,0 +1,5 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +variables_order = EGPCS +pcov.directory = . diff --git a/docker/8.3/start-container b/docker/8.3/start-container new file mode 100644 index 0000000..40c55df --- /dev/null +++ b/docker/8.3/start-container @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then + echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." + exit 1 +fi + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ]; then + if [ "$SUPERVISOR_PHP_USER" = "root" ]; then + exec "$@" + else + exec gosu $WWWUSER "$@" + fi +else + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.3/supervisord.conf b/docker/8.3/supervisord.conf new file mode 100644 index 0000000..656da8a --- /dev/null +++ b/docker/8.3/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=%(ENV_SUPERVISOR_PHP_COMMAND)s +user=%(ENV_SUPERVISOR_PHP_USER)s +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/8.4/Dockerfile b/docker/8.4/Dockerfile new file mode 100644 index 0000000..cb0fbdc --- /dev/null +++ b/docker/8.4/Dockerfile @@ -0,0 +1,71 @@ +FROM ubuntu:24.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP +ARG NODE_VERSION=22 +ARG MYSQL_CLIENT="mysql-client" +ARG POSTGRES_VERSION=17 + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC +ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" +ENV SUPERVISOR_PHP_USER="sail" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ + echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom + +RUN apt-get update && apt-get upgrade -y \ + && mkdir -p /etc/apt/keyrings \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.4-cli php8.4-dev \ + php8.4-pgsql php8.4-sqlite3 php8.4-gd \ + php8.4-curl php8.4-mongodb \ + php8.4-imap php8.4-mysql php8.4-mbstring \ + php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \ + php8.4-intl php8.4-readline \ + php8.4-ldap \ + php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \ + php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \ + && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && npm install -g npm \ + && npm install -g pnpm \ + && npm install -g bun \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y $MYSQL_CLIENT \ + && apt-get install -y postgresql-client-$POSTGRES_VERSION \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.4 + +RUN userdel -r ubuntu +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 80/tcp + +ENTRYPOINT ["start-container"] diff --git a/docker/8.4/php.ini b/docker/8.4/php.ini new file mode 100644 index 0000000..0d8ce9e --- /dev/null +++ b/docker/8.4/php.ini @@ -0,0 +1,5 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +variables_order = EGPCS +pcov.directory = . diff --git a/docker/8.4/start-container b/docker/8.4/start-container new file mode 100644 index 0000000..40c55df --- /dev/null +++ b/docker/8.4/start-container @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then + echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." + exit 1 +fi + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ]; then + if [ "$SUPERVISOR_PHP_USER" = "root" ]; then + exec "$@" + else + exec gosu $WWWUSER "$@" + fi +else + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.4/supervisord.conf b/docker/8.4/supervisord.conf new file mode 100644 index 0000000..656da8a --- /dev/null +++ b/docker/8.4/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=%(ENV_SUPERVISOR_PHP_COMMAND)s +user=%(ENV_SUPERVISOR_PHP_USER)s +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/mariadb/create-testing-database.sh b/docker/mariadb/create-testing-database.sh new file mode 100644 index 0000000..d3b19d9 --- /dev/null +++ b/docker/mariadb/create-testing-database.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +/usr/bin/mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL + CREATE DATABASE IF NOT EXISTS testing; + GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%'; +EOSQL diff --git a/docker/mysql/create-testing-database.sh b/docker/mysql/create-testing-database.sh new file mode 100644 index 0000000..aeb1826 --- /dev/null +++ b/docker/mysql/create-testing-database.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +mysql --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL + CREATE DATABASE IF NOT EXISTS testing; + GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%'; +EOSQL diff --git a/docker/pgsql/create-testing-database.sql b/docker/pgsql/create-testing-database.sql new file mode 100644 index 0000000..d84dc07 --- /dev/null +++ b/docker/pgsql/create-testing-database.sql @@ -0,0 +1,2 @@ +SELECT 'CREATE DATABASE testing' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'testing')\gexec diff --git a/resources/views/forum/categories/create.blade.php b/resources/views/forum/categories/create.blade.php new file mode 100644 index 0000000..50308f9 --- /dev/null +++ b/resources/views/forum/categories/create.blade.php @@ -0,0 +1,51 @@ +@extends('layouts.app') + +@section('title', 'Create Category - Forum - ClearGame') + +@section('content') +
{{ $category->description }}
+Be the first to start a discussion in this category!
+ @auth + + Create Thread + + @else + + Login to Create Thread + + @endauth +Check back later for forum discussions.
+{{ $category->description }}
+Threads
+Topics
+Posts
+{{ $topic->title }}
+in topic: {{ $post->topic->title }}
+Be the first to start a topic in this thread!
+ @auth + @if(!$thread->is_locked) + + Create Topic + + @else +in thread: {{ $thread->title }}
+in thread: {{ $thread->title }}
+Be the first to reply to this topic!
+ @auth + @if(!$thread->is_locked) + + Post Reply + + @else +{{ Str::limit($game->summary, 200) }}
-No ratings yet
@endif - @auth -+ Released on {{ $game->first_release_date->format('F j, Y') }} + @if(isset($game->company)) + by {{ $game->company }} + @endif +
+ + +{{ $game->summary }}
+ @if($game->storyline) +{{ $game->storyline }}
+ @endif +{{ $game->summary }}
- @if($game->storyline) -{{ $game->storyline }}
- @endif -No reviews yet. Be the first to review this game!
@@ -308,56 +263,8 @@ @endauthBased on {{ $game->reviews->count() }} reviews
-