From 535e73c4c98a261ea5a559e32456f243231ec357 Mon Sep 17 00:00:00 2001 From: AmirmahdiNourkazemi Date: Fri, 26 Sep 2025 11:42:31 +0330 Subject: [PATCH] feat: image feature added --- app/Http/Controllers/ImageController.php | 123 +++++ app/Models/Image.php | 14 + composer.json | 1 + composer.lock | 449 +++++++++++++++++- .../2025_09_26_060648_create_images_table.php | 32 ++ routes/api.php | 15 +- 6 files changed, 632 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/ImageController.php create mode 100644 app/Models/Image.php create mode 100644 database/migrations/2025_09_26_060648_create_images_table.php diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php new file mode 100644 index 0000000..b55490a --- /dev/null +++ b/app/Http/Controllers/ImageController.php @@ -0,0 +1,123 @@ +validate([ + 'image' => 'required|image|max:2048', + 'title' => 'nullable|string|max:255', + 'description' => 'nullable|string|max:500', + 'type' => 'nullable|in:public,private', + ]); + + $path = $request->file('image')->store('images', 'public'); + + $image = Image::create([ + 'user_id' => auth()->id(), + 'path' => $path, + 'title' => $data['title'] ?? null, + 'description' => $data['description'] ?? null, + 'type' => $data['type'] ?? 'private', // default private + ]); + + return response()->json([ + 'message' => 'Image uploaded successfully', + 'image' => $image, + 'url' => asset('storage/' . $path), + ]); +} + + public function publicImages() +{ + $images = Image::where('type', 'public')->get(); + + return response()->json($images->map(function ($image) { + $image->url = asset('storage/' . $image->path); + return $image; + })); +} + +public function userImages() +{ + $images = Image::where('user_id', auth()->id()) + ->where('type', 'private') + ->get(); + + return response()->json($images->map(function ($image) { + $image->url = asset('storage/' . $image->path); + return $image; + })); +} +public function allImages() +{ + $userId = auth()->id(); + + $images = Image::where('type', 'public') + ->orWhere(function ($query) use ($userId) { + $query->where('type', 'private') + ->where('user_id', $userId); + }) + ->get(); + + return response()->json($images->map(function ($image) { + $image->url = asset('storage/' . $image->path); + return $image; + })); +} + // ✅ Update image + public function update(Request $request, $id) + { + $image = Image::where('id', $id) + ->where('user_id', auth()->id()) // only owner can update + ->firstOrFail(); + + $data = $request->validate([ + 'title' => 'nullable|string|max:255', + 'description' => 'nullable|string|max:500', + 'type' => 'nullable|in:public,private', + 'image' => 'nullable|image|max:2048', + ]); + + if ($request->hasFile('image')) { + // delete old image + Storage::disk('public')->delete($image->path); + + // store new one + $path = $request->file('image')->store('images', 'public'); + $image->path = $path; + } + + $image->title = $data['title'] ?? $image->title; + $image->description = $data['description'] ?? $image->description; + $image->type = $data['type'] ?? $image->type; + $image->save(); + + return response()->json([ + 'message' => 'Image updated successfully', + 'image' => $image, + 'url' => asset('storage/' . $image->path), + ]); + } + + // ✅ Delete image + public function destroy($id) + { + $image = Image::where('id', $id) + ->where('user_id', auth()->id()) // only owner can delete + ->firstOrFail(); + + Storage::disk('public')->delete($image->path); + $image->delete(); + + return response()->json(['message' => 'Image deleted successfully']); + } + +} \ No newline at end of file diff --git a/app/Models/Image.php b/app/Models/Image.php new file mode 100644 index 0000000..8aa3d28 --- /dev/null +++ b/app/Models/Image.php @@ -0,0 +1,14 @@ +belongsTo(User::class); + } +} diff --git a/composer.json b/composer.json index 77bbc87..0d83293 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ }, "require-dev": { "fakerphp/faker": "^1.23", + "knuckleswtf/scribe": "*", "laravel/pail": "^1.2.2", "laravel/pint": "^1.13", "laravel/sail": "^1.41", diff --git a/composer.lock b/composer.lock index 33c1c3f..66e4760 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": "b2928ef8514c9c8819a75d8ef288dcb2", + "content-hash": "a424d7cee6af46482fa10c8efef94a43", "packages": [ { "name": "brick/math", @@ -7259,6 +7259,56 @@ } ], "packages-dev": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-12-30T22:54:17+00:00" + }, { "name": "fakerphp/faker", "version": "v1.24.1", @@ -7444,6 +7494,100 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "knuckleswtf/scribe", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/knuckleswtf/scribe.git", + "reference": "380f9cd6de6d65d2e92930a8613d8a682e83e8c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knuckleswtf/scribe/zipball/380f9cd6de6d65d2e92930a8613d8a682e83e8c7", + "reference": "380f9cd6de6d65d2e92930a8613d8a682e83e8c7", + "shasum": "" + }, + "require": { + "erusev/parsedown": "1.7.4", + "ext-fileinfo": "*", + "ext-json": "*", + "ext-pdo": "*", + "fakerphp/faker": "^1.23.1", + "laravel/framework": "^9.0|^10.0|^11.0|^12.0", + "league/flysystem": "^3.0", + "mpociot/reflection-docblock": "^1.0.1", + "nikic/php-parser": "^5.0", + "nunomaduro/collision": "^6.0|^7.0|^8.0", + "php": ">=8.1", + "ramsey/uuid": "^4.2.2", + "shalvah/clara": "^3.1.0", + "shalvah/upgrader": ">=0.6.0", + "spatie/data-transfer-object": "^2.6|^3.0", + "symfony/var-exporter": "^6.0|^7.0", + "symfony/yaml": "^6.0|^7.0" + }, + "replace": { + "mpociot/laravel-apidoc-generator": "*" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^v0.5.0", + "laravel/legacy-factories": "^1.3.0", + "league/fractal": "^0.20", + "nikic/fast-route": "^1.3", + "orchestra/testbench": "^7.0|^8.0|^v9.10.0|^10.0", + "pestphp/pest": "^1.21|^2.0|^3.0", + "phpstan/phpstan": "^2.1.5", + "phpunit/phpunit": "^9.0|^10.0|^11.0", + "spatie/ray": "^1.41", + "symfony/css-selector": "^6.0|^7.0", + "symfony/dom-crawler": "^6.0|^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Knuckles\\Scribe\\ScribeServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Config/helpers.php" + ], + "psr-4": { + "Knuckles\\Camel\\": "camel/", + "Knuckles\\Scribe\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shalvah" + } + ], + "description": "Generate API documentation for humans from your Laravel codebase.✍", + "homepage": "http://github.com/knuckleswtf/scribe", + "keywords": [ + "api", + "documentation", + "laravel" + ], + "support": { + "issues": "https://github.com/knuckleswtf/scribe/issues", + "source": "https://github.com/knuckleswtf/scribe/tree/5.3.0" + }, + "funding": [ + { + "url": "https://patreon.com/shalvah", + "type": "patreon" + } + ], + "time": "2025-07-28T22:27:25+00:00" + }, { "name": "laravel/pail", "version": "v1.2.2", @@ -7734,6 +7878,59 @@ }, "time": "2024-05-16T03:13:13+00:00" }, + { + "name": "mpociot/reflection-docblock", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/mpociot/reflection-docblock.git", + "reference": "c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpociot/reflection-docblock/zipball/c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587", + "reference": "c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mpociot": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "issues": "https://github.com/mpociot/reflection-docblock/issues", + "source": "https://github.com/mpociot/reflection-docblock/tree/master" + }, + "time": "2016-06-20T20:53:12+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.0", @@ -9369,6 +9566,175 @@ ], "time": "2024-10-09T05:16:32+00:00" }, + { + "name": "shalvah/clara", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/shalvah/clara.git", + "reference": "cdbb5737cbdd101756d97dd2279a979a1af7710b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shalvah/clara/zipball/cdbb5737cbdd101756d97dd2279a979a1af7710b", + "reference": "cdbb5737cbdd101756d97dd2279a979a1af7710b", + "shasum": "" + }, + "require": { + "php": ">=7.4", + "symfony/console": "^4.0|^5.0|^6.0|^7.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^7.0", + "phpunit/phpunit": "^9.1" + }, + "type": "library", + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Shalvah\\Clara\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "🔊 Simple, pretty, testable console output for CLI apps.", + "keywords": [ + "cli", + "log", + "logging" + ], + "support": { + "issues": "https://github.com/shalvah/clara/issues", + "source": "https://github.com/shalvah/clara/tree/3.2.0" + }, + "time": "2024-02-27T20:30:59+00:00" + }, + { + "name": "shalvah/upgrader", + "version": "0.6.0", + "source": { + "type": "git", + "url": "https://github.com/shalvah/upgrader.git", + "reference": "d95ed17fe9f5e1ee7d47ad835595f1af080a867f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shalvah/upgrader/zipball/d95ed17fe9f5e1ee7d47ad835595f1af080a867f", + "reference": "d95ed17fe9f5e1ee7d47ad835595f1af080a867f", + "shasum": "" + }, + "require": { + "illuminate/support": ">=8.0", + "nikic/php-parser": "^5.0", + "php": ">=8.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.2.0", + "pestphp/pest": "^1.21", + "phpstan/phpstan": "^1.0", + "spatie/ray": "^1.33" + }, + "type": "library", + "autoload": { + "psr-4": { + "Shalvah\\Upgrader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shalvah", + "email": "hello@shalvah.me" + } + ], + "description": "Create automatic upgrades for your package.", + "homepage": "http://github.com/shalvah/upgrader", + "keywords": [ + "upgrade" + ], + "support": { + "issues": "https://github.com/shalvah/upgrader/issues", + "source": "https://github.com/shalvah/upgrader/tree/0.6.0" + }, + "funding": [ + { + "url": "https://patreon.com/shalvah", + "type": "patreon" + } + ], + "time": "2024-02-20T11:51:46+00:00" + }, + { + "name": "spatie/data-transfer-object", + "version": "3.9.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/data-transfer-object.git", + "reference": "1df0906c4e9e3aebd6c0506fd82c8b7d5548c1c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/data-transfer-object/zipball/1df0906c4e9e3aebd6c0506fd82c8b7d5548c1c8", + "reference": "1df0906c4e9e3aebd6c0506fd82c8b7d5548c1c8", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "illuminate/collections": "^8.36", + "jetbrains/phpstorm-attributes": "^1.0", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\DataTransferObject\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Data transfer objects with batteries included", + "homepage": "https://github.com/spatie/data-transfer-object", + "keywords": [ + "data-transfer-object", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/data-transfer-object/issues", + "source": "https://github.com/spatie/data-transfer-object/tree/3.9.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "abandoned": "spatie/laravel-data", + "time": "2022-09-16T13:34:38+00:00" + }, { "name": "staabm/side-effects-detector", "version": "1.0.5", @@ -9421,6 +9787,87 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-18T13:10:53+00:00" + }, { "name": "symfony/yaml", "version": "v7.2.5", diff --git a/database/migrations/2025_09_26_060648_create_images_table.php b/database/migrations/2025_09_26_060648_create_images_table.php new file mode 100644 index 0000000..77225f3 --- /dev/null +++ b/database/migrations/2025_09_26_060648_create_images_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('path'); // مسیر ذخیره فایل + $table->string('title')->nullable(); + $table->string('description')->nullable(); + $table->enum('type', ['public', 'private'])->default('private'); // 🔑 type of image + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('images'); + } +}; diff --git a/routes/api.php b/routes/api.php index b979086..1007087 100644 --- a/routes/api.php +++ b/routes/api.php @@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Hash; use App\Http\Controllers\QuestionController; use App\Http\Controllers\SliderController; +use App\Http\Controllers\ImageController; Route::get('/test-hash', function() { $plain = 'amnk1380'; @@ -105,7 +106,19 @@ /// slider feature - Route::get('/slider', [SliderController::class, 'index']); + /// images + + Route::post('/images', [ImageController::class, 'store']); + Route::get('/images/private', [ImageController::class, 'userImages']); + Route::get('/images/all', [ImageController::class, 'allImages']); + Route::get('/images/public', [ImageController::class, 'publicImages']); + Route::put('/images/{id}', [ImageController::class, 'update']); // ✅ update + Route::delete('/images/{id}', [ImageController::class, 'destroy']); // ✅ delete + + + + + }); \ No newline at end of file