diff --git a/.env.example b/.env.example index 73fc6a54..7b181d79 100755 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=base64:vC0yZGIN9SZps1rwMBcl/jeiBfnpApiJYeJZuj1B5hU= +APP_KEY= APP_DEBUG=true APP_URL=http://localhost @@ -70,5 +70,3 @@ RESET_PASSWORD_LINK= # For token test in registrationtest file TOKEN_AUTH='' - -JWT_SECRET=HSPiEuyVveI9JKUu1z2N8TRt5rHGqiGgxPQ0nHloikZtk91fC9DyqCp6fAigZ5Ii diff --git a/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php b/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php index f45059eb..e9e55a8d 100644 --- a/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php +++ b/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php @@ -114,7 +114,7 @@ public function saveGoogleRequest(Request $request) public function saveGoogleRequestPost(Request $request) { - // Validate the incoming request + // Validate the incoming request working $validator = Validator::make($request->all(), [ 'id_token' => 'required|string', ]); @@ -127,8 +127,7 @@ public function saveGoogleRequestPost(Request $request) } // Extract Google user data from the request - $google_token = $request->input('id_token'); - + $google_token = $request->id_token; try { // Retrieve user information from Google diff --git a/app/Http/Controllers/Api/V1/CommentController.php b/app/Http/Controllers/Api/V1/CommentController.php index 5b615899..5e87294a 100644 --- a/app/Http/Controllers/Api/V1/CommentController.php +++ b/app/Http/Controllers/Api/V1/CommentController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Models\Blog; use App\Models\Comment; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; @@ -37,7 +38,7 @@ public function createComment(Request $request, $blogId) ], 201); } catch (\Exception $e) { Log::error('Error creating reply comment:', ['exception' => $e->getMessage()]); - + return response()->json([ 'status' => 500, 'message' => 'Failed to create comment', @@ -52,7 +53,7 @@ public function replyComment(Request $request, $commentId) try { Log::info('Reached the replyComment method'); Log::info('Request data:', $request->all()); - + $user = auth('api')->user(); $request->validate([ 'content' => 'required|string' @@ -78,7 +79,7 @@ public function replyComment(Request $request, $commentId) \Exception $e ) { Log::error('Error creating reply comment:', ['exception' => $e->getMessage()]); - + return response()->json([ 'status' => 500, 'message' => 'Failed to create reply', @@ -183,10 +184,17 @@ public function deleteComment($commentId) 'message' => 'Comment deleted successfully', ], 200); } catch (\Exception $e) { - return response()->json([ - 'status' => 500, - 'message' => 'Failed to delete comment', - ], 500); + + if($e instanceof ModelNotFoundException) { + return response()->json([ + 'status' => 404, + 'message' => 'Comment not found', + ], 404); + } + return response()->json([ + 'status' => 500, + 'message' => 'Failed to delete comment', + ], 500); } } diff --git a/app/Http/Controllers/Api/V1/NewsletterSubscriptionController.php b/app/Http/Controllers/Api/V1/NewsletterSubscriptionController.php new file mode 100644 index 00000000..01df42b3 --- /dev/null +++ b/app/Http/Controllers/Api/V1/NewsletterSubscriptionController.php @@ -0,0 +1,53 @@ +validator = $validator; + } + + public function store(Request $request) + { + $validator = $this->validator->make($request->all(), [ + 'email' => 'required|email|unique:newsletter_subscriptions,email' + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation failed.', + 'errors' => $validator->errors() + ], Response::HTTP_BAD_REQUEST); + } + + try { + $newsletter_subscription = NewsletterSubscription::create([ + 'email' => $request->email + ]); + + return response()->json([ + 'data' => $newsletter_subscription, + 'message' => 'Newsletter Subscribed Successfully', + 'status_code' => 201 + ], Response::HTTP_CREATED); + } catch (Exception $exception) { + return response()->json([ + 'message' => 'Internal Server Error', + 'status_code' => 500 + ], 500); + } + } +} + diff --git a/app/Http/Controllers/Api/V1/PreferenceController.php b/app/Http/Controllers/Api/V1/PreferenceController.php index a387fd45..fded0ddd 100755 --- a/app/Http/Controllers/Api/V1/PreferenceController.php +++ b/app/Http/Controllers/Api/V1/PreferenceController.php @@ -33,13 +33,13 @@ public function index() 'message' => 'Languages fetched successfully', 'preferences' => $preferences ], 200); - } // - - public function store(StorePreferenceRequest $request){ + + public function store(StorePreferenceRequest $request) + { if (!auth()->check()) { return response()->json([ 'status_code' => 401, @@ -48,7 +48,7 @@ public function store(StorePreferenceRequest $request){ } $validated = $request->validated(); - + if (!$validated) { return response()->json([ 'status_code' => 400, @@ -56,12 +56,12 @@ public function store(StorePreferenceRequest $request){ 'errors' => $validator->errors() ], 400); } - + try { $preference = Auth::user()->preferences()->create($request->all()); - + Log::info('Preference created', ['user_id' => Auth::id(), 'preference' => $preference]); - + return response()->json([ 'status_code' => 201, 'message' => 'Preference created successfully', @@ -71,10 +71,9 @@ public function store(StorePreferenceRequest $request){ 'value' => $preference->value, ] ], 201); - } catch (\Exception $e) { Log::error("Error creating preference: {$e->getMessage()}"); - + return response()->json([ 'status_code' => 500, 'message' => 'Internal Server Error', @@ -93,7 +92,7 @@ public function update(UpdatePreferenceRequest $request, $id) } $validated = $request->validated(); - + if (!$validated) { return response()->json([ 'status_code' => 400, @@ -105,7 +104,7 @@ public function update(UpdatePreferenceRequest $request, $id) try { $preference = Auth::user()->preferences()->findOrFail($id); - if(!$preference) { + if (!$preference) { return response()->json([ 'status_code' => 404, 'message' => 'Preference not found', @@ -134,7 +133,7 @@ public function update(UpdatePreferenceRequest $request, $id) ], 500); } } - + public function delete(DeletePreferenceRequest $request, $id) { $preference = Auth::user()->preferences()->find($id); @@ -155,5 +154,29 @@ public function delete(DeletePreferenceRequest $request, $id) 'message' => 'Preference deleted successfully.', ], 200); } - +//show regions + public function showRegion($user_id) + { + $preference = Preference::where('user_id', $user_id)->first(); + if ($preference) { + if ($preference->region) { + return response()->json([ + 'status' => 200, + 'message' => "Region retrieved successfully", + 'data' => $preference->region, + ], 200); + } else { + return response()->json([ + 'status' => 200, + 'message' => "Region has not been set", + 'data' => [], + ], 200); + } + } else { + return response()->json([ + 'status' => 404, + 'message' => 'Preference not found for user', + ], 404); + } + } } diff --git a/app/Http/Controllers/Api/V1/SqueezePageCoontroller.php b/app/Http/Controllers/Api/V1/SqueezePageCoontroller.php index b47b9274..e7d771bd 100644 --- a/app/Http/Controllers/Api/V1/SqueezePageCoontroller.php +++ b/app/Http/Controllers/Api/V1/SqueezePageCoontroller.php @@ -3,9 +3,11 @@ namespace App\Http\Controllers\Api\V1; use App\Http\Controllers\Controller; +use App\Http\Requests\FilterSqueezeRequest; use App\Models\SqueezePage; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Validation\ValidationException; class SqueezePageCoontroller extends Controller { @@ -76,4 +78,55 @@ public function destroy(string $id) { // } + + public function search(Request $request) + { + $request->validate([ + 'q' => 'required|string|max:255', + ]); + + $query = SqueezePage::query(); + + if ($searchTerm = $request->input('q')) { + $query->where(function ($q) use ($searchTerm) { + $q->where('headline', 'LIKE', "%{$searchTerm}%") + ->orWhere('sub_headline', 'LIKE', "%{$searchTerm}%") + ->orWhere('content', 'LIKE', "%{$searchTerm}%") + ->orWhere('title', 'LIKE', "%{$searchTerm}%") + ->orWhere('slug', 'LIKE', "%{$searchTerm}%"); + }); + } + + $results = $query->get(); + + return response()->json([ + 'status' => Response::HTTP_OK, + 'message' => 'Search result retrieved', + 'data' => $results, + ]); + } + + public function filter(FilterSqueezeRequest $request) + { + $query = SqueezePage::query(); + + // Filter by status if provided + if ($status = $request->input('status')) { + $query->where('status', $status); + } + + // Filter by slug if provided + if ($slug = $request->input('slug')) { + $query->where('slug', $slug); + } + + // Get the results + $results = $query->get(); + + return response()->json([ + 'status' => Response::HTTP_OK, + 'message' => 'Filtered results', + 'data' => $results, + ]); + } } diff --git a/app/Http/Controllers/Api/V1/User/UserController.php b/app/Http/Controllers/Api/V1/User/UserController.php index 17a0ebc4..24fa352f 100755 --- a/app/Http/Controllers/Api/V1/User/UserController.php +++ b/app/Http/Controllers/Api/V1/User/UserController.php @@ -9,33 +9,38 @@ class UserController extends Controller { - /** - * Display a listing of the resource. - */ - public function index() - { - $users = User::latest()->paginate(); + public function stats() + { $totalUsers = User::count(); $totalDeletedUsers = User::onlyTrashed()->count(); $totalActiveUsers = User::where('is_active', 1)->count() - $totalDeletedUsers; $totalInActiveUsers = User::where('is_active', 0)->count(); - dd($users); - - // $users = [ - // 'name' => - // ]; + return response()->json( + [ + "status_code" => 200, + "message" => "User statistics retrieved successfully", + "total_users" => $totalUsers, + "deleted_users" => $totalDeletedUsers, + "active_users" => $totalActiveUsers, + "in_active_users" => $totalInActiveUsers, + ], + 200 + ); + } + /** + * Display a listing of the resource. + */ + public function index() + { + $users = User::latest()->paginate(); return response()->json( [ "status_code" => 200, "message" => "Users returned successfully", - "total_users" => $totalUsers, - "total_deleted_users" => $totalDeletedUsers, - "total_active_users" => $totalActiveUsers, - "total_inActive_users" => $totalInActiveUsers, "data" =>$users ], 200 diff --git a/app/Http/Requests/FilterSqueezeRequest.php b/app/Http/Requests/FilterSqueezeRequest.php new file mode 100644 index 00000000..51bfa945 --- /dev/null +++ b/app/Http/Requests/FilterSqueezeRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + 'status' => 'required|string|in:online,offline', + ]; + } +} diff --git a/app/Models/NewsletterSubscription.php b/app/Models/NewsletterSubscription.php new file mode 100644 index 00000000..32af26d1 --- /dev/null +++ b/app/Models/NewsletterSubscription.php @@ -0,0 +1,21 @@ +=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -5000,7 +5046,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.8" + "source": "https://github.com/symfony/css-selector/tree/v7.1.1" }, "funding": [ { @@ -5016,7 +5062,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5162,24 +5208,24 @@ }, { "name": "symfony/event-dispatcher", - "version": "v6.4.8", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -5188,13 +5234,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5222,7 +5268,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" }, "funding": [ { @@ -5238,7 +5284,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6675,20 +6721,20 @@ }, { "name": "symfony/string", - "version": "v6.4.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ccf9b30251719567bfd46494138327522b9a9446" + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ccf9b30251719567bfd46494138327522b9a9446", - "reference": "ccf9b30251719567bfd46494138327522b9a9446", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -6698,11 +6744,12 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6741,7 +6788,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.10" + "source": "https://github.com/symfony/string/tree/v7.1.3" }, "funding": [ { @@ -6757,7 +6804,7 @@ "type": "tidelift" } ], - "time": "2024-07-22T10:21:14+00:00" + "time": "2024-07-22T10:25:37+00:00" }, { "name": "symfony/translation", @@ -10204,26 +10251,25 @@ }, { "name": "symfony/var-exporter", - "version": "v6.4.9", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e" + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10261,7 +10307,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.9" + "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" }, "funding": [ { @@ -10277,32 +10323,31 @@ "type": "tidelift" } ], - "time": "2024-06-24T15:53:56+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.8", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9" + "reference": "fa34c77015aa6720469db7003567b9f772492bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -10333,7 +10378,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.8" + "source": "https://github.com/symfony/yaml/tree/v7.1.1" }, "funding": [ { @@ -10349,7 +10394,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "theseer/tokenizer", diff --git a/database/factories/RegionFactory.php b/database/factories/RegionFactory.php new file mode 100644 index 00000000..73697c94 --- /dev/null +++ b/database/factories/RegionFactory.php @@ -0,0 +1,28 @@ + + */ + public function definition(): array + { + return [ + 'id' => $this->faker->uuid(), + 'name' => $this->faker->city(), + + ]; + } +} diff --git a/database/migrations/2024_08_08_115517_create_newsletter_subscriptions_table.php b/database/migrations/2024_08_08_115517_create_newsletter_subscriptions_table.php new file mode 100644 index 00000000..6c9e490f --- /dev/null +++ b/database/migrations/2024_08_08_115517_create_newsletter_subscriptions_table.php @@ -0,0 +1,29 @@ +uuid('id')->primary(); + $table->text('email'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('newsletter_subscriptions'); + } +}; diff --git a/database/seeders/RegionSeeder.php b/database/seeders/RegionSeeder.php new file mode 100644 index 00000000..61d3ed29 --- /dev/null +++ b/database/seeders/RegionSeeder.php @@ -0,0 +1,18 @@ +count(10)->create(); // Creates 10 regions + } +} diff --git a/public/uploads/1723122054.jpg b/public/uploads/1723122054.jpg new file mode 100644 index 00000000..b0079762 Binary files /dev/null and b/public/uploads/1723122054.jpg differ diff --git a/public/uploads/1723124275.jpg b/public/uploads/1723124275.jpg new file mode 100644 index 00000000..b0079762 Binary files /dev/null and b/public/uploads/1723124275.jpg differ diff --git a/routes/api.php b/routes/api.php index 188ee3a8..6e0b42f7 100755 --- a/routes/api.php +++ b/routes/api.php @@ -48,6 +48,7 @@ use App\Http\Controllers\UserNotificationController; use Illuminate\Support\Facades\Route; use App\Http\Controllers\Api\V1\Admin\AdminDashboardController; +use App\Http\Controllers\Api\V1\NewsletterSubscriptionController; /* |-------------------------------------------------------------------------- @@ -87,8 +88,11 @@ Route::get('/auth/facebook/callback', [SocialAuthController::class, 'callbackFromFacebook']); Route::post('/auth/facebook/callback', [SocialAuthController::class, 'saveFacebookRequest']); + + Route::get('/users/stats', [UserController::class, 'stats']); Route::apiResource('/users', UserController::class); + //jobs Route::get('/jobs', [JobController::class, 'index']); Route::get('/jobs/search', [JobSearchController::class, 'search']); @@ -104,7 +108,7 @@ Route::get('/payments/flutterwave/{organisation_id}/verify/{id}', [PaymentController::class, 'handleFlutterwaveCallback']); Route::post('/languages', [LanguageController::class, 'create']); Route::get('/languages', [LanguageController::class, 'index']); - Route::put('/languages/{id}', [LanguageController::class, 'update']); + Route::put('/languages/{id}', [LanguageController::class, 'update']); Route::middleware('throttle:10,1')->get('/topics/search', [ArticleController::class, 'search']); @@ -246,14 +250,16 @@ Route::patch('/blogs/edit/{id}', [BlogController::class, 'update'])->name('admin.blogs.update'); Route::delete('/blogs/{id}', [BlogController::class, 'destroy']); Route::get('/waitlists', [WaitListController::class, 'index']); + Route::get('/squeeze-pages/search', [SqueezePageCoontroller::class, 'search']); + Route::get('/squeeze-pages/filter', [SqueezePageCoontroller::class, 'filter']); Route::apiResource('squeeze-pages', SqueezePageCoontroller::class); Route::get('/dashboard/statistics', [AdminDashboardController::class, 'getStatistics']); - Route::apiResource('faqs', FaqController::class); - Route::get('/dashboard/top-products', [AdminDashboardController::class, 'getTopProducts']); + Route::apiResource('faqs', FaqController::class); + Route::get('/dashboard/top-products', [AdminDashboardController::class, 'getTopProducts']); Route::get('/dashboard/all-top-products', [AdminDashboardController::class, 'getAllProductsSortedBySales']); - + }); Route::post('/waitlists', [WaitListController::class, 'store']); @@ -272,6 +278,10 @@ Route::get('/user-sales', [DashboardController::class, 'recent_sales']); }); + //region get and update + Route::group(['middleware' => ['auth:api']], function () { + Route::get('/regions/{user_id}', [PreferenceController::class, 'showRegion']); + }); // Notification settings Route::patch('/notification-settings/{user_id}', [NotificationPreferenceController::class, 'update']); @@ -302,4 +312,7 @@ // quest Route::get('/quests/{id}/messages', [QuestController::class, 'getQuestMessages']); + + //Newsletter Subscription + Route::post('newsletter-subscription', [NewsletterSubscriptionController::class, 'store']); }); diff --git a/tests/Feature/CommentControllerTest.php b/tests/Feature/CommentControllerTest.php index 0a00eed8..7c7c379e 100644 --- a/tests/Feature/CommentControllerTest.php +++ b/tests/Feature/CommentControllerTest.php @@ -7,8 +7,11 @@ use App\Models\User; use App\Models\Blog; use App\Models\Comment; +use Illuminate\Http\Response; use Tymon\JWTAuth\Facades\JWTAuth; +use Illuminate\Support\Str; + class CommentControllerTest extends TestCase { use RefreshDatabase; @@ -125,6 +128,25 @@ public function testEditComment() ] ]); } + + public function testAnotherUserCantEditComment() + { + $user1 = User::factory()->create(); + $user2 = User::factory()->create(); + $comment = Comment::factory()->create(['user_id' => $user1->id]); + $token1 = JWTAuth::fromUser($user1); + $token2 = JWTAuth::fromUser($user2); + + $response = $this->withHeaders(['Authorization' => "Bearer $token2"]) + ->patchJson("/api/v1/comments/edit/{$comment->id}", [ + 'content' => 'Edited content' + ]); + + $response->assertStatus(Response::HTTP_FORBIDDEN)->assertJsonStructure([ + 'message', + ]); + } + public function testDeleteComment() { $user = User::factory()->create(); @@ -141,6 +163,23 @@ public function testDeleteComment() ]); } + public function testDeleteCommentThatDontExist() + { + $user = User::factory()->create(); + $comment = Comment::factory()->create(['user_id' => $user->id]); + $token = JWTAuth::fromUser($user); + + $uuid = Str::uuid(); + + $response = $this->withHeaders(['Authorization' => "Bearer $token"]) + ->deleteJson("/api/v1/comments/{$uuid}"); + + $response->assertStatus(Response::HTTP_NOT_FOUND); + $this->assertDatabaseMissing('comments', [ + 'id' => $uuid, + ]); + } + public function testGetCommentsForBlog() { diff --git a/tests/Feature/PreferenceControllerTest.php b/tests/Feature/PreferenceControllerTest.php new file mode 100644 index 00000000..18643922 --- /dev/null +++ b/tests/Feature/PreferenceControllerTest.php @@ -0,0 +1,82 @@ +user = User::factory()->create(); + $this->preference = Preference::factory()->create(['user_id' => $this->user->id]); + $this->region = Region::factory()->create(); + } + + public function test_get_region() + { + // Authenticate the user with a valid JWT token + $token = JWTAuth::fromUser($this->user); + + // Scenario: Region is set + $this->preference->region_id = $this->region->id; + $this->preference->save(); + + $response = $this->withHeaders([ + 'Authorization' => "Bearer $token", + ])->getJson("/api/v1/regions/{$this->user->id}"); + + $response->assertStatus(200) + ->assertJson([ + 'status' => 200, + 'message' => 'Region retrieved successfully', + 'data' => [ + 'id' => $this->region->id, + 'name' => $this->region->name, + ], + ]); + + // Scenario: Region is not set + $this->preference->region_id = null; + $this->preference->save(); + + $response = $this->withHeaders([ + 'Authorization' => "Bearer $token", + ])->getJson("/api/v1/regions/{$this->user->id}"); + + $response->assertStatus(200) + ->assertJson([ + 'status' => 200, + 'message' => 'Region has not been set', + 'data' => [], + ]); + + // Scenario: Preference not found + $newUser = User::factory()->create(); + $newToken = JWTAuth::fromUser($newUser); + + $response = $this->withHeaders([ + 'Authorization' => "Bearer $newToken", + ])->getJson("/api/v1/regions/{$newUser->id}"); + + $response->assertStatus(404) + ->assertJson([ + 'status' => 404, + 'message' => 'Preference not found for user', + ]); + } +} diff --git a/tests/Feature/SearchAndFilterSqueezeTest.php b/tests/Feature/SearchAndFilterSqueezeTest.php new file mode 100644 index 00000000..41870964 --- /dev/null +++ b/tests/Feature/SearchAndFilterSqueezeTest.php @@ -0,0 +1,116 @@ +create([ + 'role' => $role, + 'is_active' => true + ]); + + $token = JWTAuth::fromUser($user); + + return [$user, $token]; + } + + /** @test */ + public function it_can_search_and_filter_squeeze_pages() + { + [$admin, $token] = $this->getAuthenticatedUser('admin'); + + // Create some squeeze pages + SqueezePage::factory()->create([ + 'title' => 'Digital Marketing', + 'slug' => 'digital-marketing', + 'status' => 'online', + 'activate' => true, + 'headline' => 'Master Digital Marketing', + 'sub_headline' => 'Unlock the Secrets of Online Success', + 'hero_image' => 'digital_marketing.jpg', + 'content' => 'Learn the best strategies to excel in digital marketing...', + ]); + + SqueezePage::factory()->create([ + 'title' => 'Conversion Secrets', + 'slug' => 'conversion-secrets', + 'status' => 'online', + 'activate' => true, + 'headline' => 'Increase Your Conversions', + 'sub_headline' => 'Discover Proven Techniques', + 'hero_image' => 'conversion_secrets.jpg', + 'content' => 'Find out how to turn visitors into customers...', + ]); + + // Test search functionality + $searchResponse = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/json', + ])->getJson('/api/v1/squeeze-pages/search?q=Digital'); + + $searchResponse->assertStatus(200) + ->assertJson([ + 'status' => 200, + 'message' => 'Search result retrieved', + ])->assertJsonFragment([ + 'title' => 'Digital Marketing' + ])->assertJsonMissing([ + 'title' => 'Content Marketing' + ]); + + // Test filter functionality + $filterResponse = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/json', + ])->getJson('/api/v1/squeeze-pages/filter?status=online'); + + $filterResponse->assertStatus(200) + ->assertJson([ + 'status' => 200, + 'message' => 'Filtered results', + ])->assertJsonFragment([ + 'title' => 'Digital Marketing' + ])->assertJsonMissing([ + 'title' => 'Content Marketing' + ]); + } + + /** @test */ + public function it_requires_q_parameter_for_search() + { + [$admin, $token] = $this->getAuthenticatedUser('admin'); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/json', + ])->getJson('/api/v1/squeeze-pages/search'); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['q']); + } + + /** @test */ + public function it_requires_status_parameter_for_filter() + { + [$admin, $token] = $this->getAuthenticatedUser('admin'); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/json', + ])->getJson('/api/v1/squeeze-pages/filter'); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['status']); + } +} diff --git a/tests/Unit/NewsletterSubscriptionCreateTest.php b/tests/Unit/NewsletterSubscriptionCreateTest.php new file mode 100644 index 00000000..bfd6d7d1 --- /dev/null +++ b/tests/Unit/NewsletterSubscriptionCreateTest.php @@ -0,0 +1,77 @@ +shouldReceive('fails')->andReturn(true); + $validatorMock->shouldReceive('errors')->andReturn(collect(['email' => 'Invalid email'])); + + $validationFactoryMock = Mockery::mock(ValidationFactory::class); + $validationFactoryMock->shouldReceive('make')->andReturn($validatorMock); + + // Create the Request + $request = Request::create('/api/v1/newsletter-subscription', 'POST', [ + 'email' => 'invalid-email' + ]); + + // Create the Controller and Inject the Mock + $controller = new V1NewsletterSubscriptionController($validationFactoryMock); + + // Call the store method + $response = $controller->store($request); + + // Assertions + $this->assertEquals(400, $response->getStatusCode()); + $this->assertJson($response->getContent()); + $this->assertStringContainsString('Validation failed.', $response->getContent()); + } + + public function test_store_method_creates_newsletter_subscription() + { + // Mock the Validator + $validatorMock = Mockery::mock(Validator::class); + $validatorMock->shouldReceive('fails')->andReturn(false); + + $validationFactoryMock = Mockery::mock(ValidationFactory::class); + $validationFactoryMock->shouldReceive('make')->andReturn($validatorMock); + + // Create the Request + $request = Request::create('/api/v1/newsletter-subscription', 'POST', [ + 'email' => 'test@example.com' + ]); + + // Create the Controller and Inject the Mock + $controller = new V1NewsletterSubscriptionController($validationFactoryMock); + + // Call the store method + $response = $controller->store($request); + + // Assertions + $this->assertEquals(201, $response->getStatusCode()); + $this->assertJson($response->getContent()); + $this->assertStringContainsString('test@example.com', $response->getContent()); + + $this->assertDatabaseHas('newsletter_subscriptions', [ + 'email' => 'test@example.com' + ]); + } +}