From b7e0daaab0ecfa046dd2f0420da73d195112704f Mon Sep 17 00:00:00 2001 From: Amowogbaje Gideon Date: Wed, 7 Aug 2024 10:45:05 +0100 Subject: [PATCH 01/15] verify re-write --- app/Http/Controllers/Api/V1/PaymentController.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/PaymentController.php b/app/Http/Controllers/Api/V1/PaymentController.php index 8f23dfb1..4eae235a 100644 --- a/app/Http/Controllers/Api/V1/PaymentController.php +++ b/app/Http/Controllers/Api/V1/PaymentController.php @@ -114,9 +114,10 @@ public function handlePaystackCallback($organisation_id, $id, Request $request) } $payment->save(); + $user_id = Organisation::find($organisation_id)->first()->user_id; $userSubscription = new UserSubscription; - $userSubscription->user_id = auth()->user()->id; + $userSubscription->user_id = $user_id; $userSubscription->subscription_plan_id = $id; $userSubscription->org_id = $organisation_id; $userSubscription->save(); @@ -229,8 +230,10 @@ public function handleFlutterwaveCallback($organisation_id, $id, Request $reques } $payment->save(); + $user_id = Organisation::find($organisation_id)->first()->user_id; + $userSubscription = new UserSubscription; - $userSubscription->user_id = auth()->user()->id; + $userSubscription->user_id = $user_id; $userSubscription->subscription_plan_id = $id; $userSubscription->org_id = $organisation_id; $userSubscription->save(); From 5cdd7c718569c61dc9d4d7bd44fb89a330b31d28 Mon Sep 17 00:00:00 2001 From: bhimbho Date: Wed, 7 Aug 2024 22:08:22 +0100 Subject: [PATCH 02/15] feat: implement recent sales --- .../Api/V1/User/DashboardController.php | 15 +++++++++++++++ app/Models/Order.php | 11 +++++++++++ database/seeders/OrderSeeder.php | 2 +- routes/api.php | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/User/DashboardController.php b/app/Http/Controllers/Api/V1/User/DashboardController.php index f8b2cd9b..ef5d233c 100644 --- a/app/Http/Controllers/Api/V1/User/DashboardController.php +++ b/app/Http/Controllers/Api/V1/User/DashboardController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api\V1\User; use App\Http\Controllers\Controller; +use App\Models\Order; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -106,6 +107,20 @@ public function index() ]); } + public function recent_sales() + { + $user = auth()->user(); + $orders = Order::whereHas('product', function ($query) use ($user) { + $query->where('user_id', $user->id); + })->with('user')->get(); + + return response()->json([ + 'message' => 'Recent sales retrieved successfully', + 'status_code' => Response::HTTP_OK, + 'data' => $orders, + ]); + } + /** * Show the form for creating a new resource. */ diff --git a/app/Models/Order.php b/app/Models/Order.php index 747ba981..8ca3fa44 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -5,9 +5,20 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Concerns\HasUuids; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class Order extends Model { use HasUuids; use HasFactory; + + public function product(): BelongsTo + { + return $this->belongsTo(Product::class, 'product_id'); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } } diff --git a/database/seeders/OrderSeeder.php b/database/seeders/OrderSeeder.php index 5fd30d41..8f144ab9 100644 --- a/database/seeders/OrderSeeder.php +++ b/database/seeders/OrderSeeder.php @@ -14,6 +14,6 @@ class OrderSeeder extends Seeder { public function run() { - // Order::factory()->count(10)->create(); + Order::factory()->count(10)->create(); } } diff --git a/routes/api.php b/routes/api.php index abf7f32e..d4cb493d 100755 --- a/routes/api.php +++ b/routes/api.php @@ -250,6 +250,7 @@ Route::get('/user/preferences', [PreferenceController::class, 'index']); Route::delete('/user/preferences/{id}', [PreferenceController::class, 'delete']); Route::get('/user-statistics', [DashboardController::class, 'index']); + Route::get('/user-sales', [DashboardController::class, 'recent_sales']); }); From 9b1261b0de48beb79ef878b939b1cfe66a2ef633 Mon Sep 17 00:00:00 2001 From: Amowogbaje Gideon Date: Wed, 7 Aug 2024 22:41:02 +0100 Subject: [PATCH 03/15] create langauges --- .../Controllers/Api/V1/LanguageController.php | 45 +++++++++++++++++++ .../Controllers/Api/V1/PaymentController.php | 18 +++++++- ...024_08_07_213218_alter_languages_table.php | 28 ++++++++++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/LanguagesTableSeeder.php | 37 +++++++++++++++ routes/api.php | 7 ++- 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 app/Http/Controllers/Api/V1/LanguageController.php create mode 100644 database/migrations/2024_08_07_213218_alter_languages_table.php create mode 100644 database/seeders/LanguagesTableSeeder.php diff --git a/app/Http/Controllers/Api/V1/LanguageController.php b/app/Http/Controllers/Api/V1/LanguageController.php new file mode 100644 index 00000000..266de186 --- /dev/null +++ b/app/Http/Controllers/Api/V1/LanguageController.php @@ -0,0 +1,45 @@ +all(), [ + 'language' => 'required|string|unique:languages', + 'code' => 'required|string|unique:languages', + 'description' => 'string|nullable', + ]); + + if ($validator->fails()) { + return response()->json([ + 'status_code' => 400, + 'message' => 'Validation Error', + 'errors' => $validator->errors() + ], 400); + } + + $language = Language::create([ + 'id' => Str::uuid(), + 'language' => $request->language, + 'code' => $request->code, + 'description' => $request->description, + ]); + + return response()->json([ + 'status_code' => 201, + 'message' => 'Language Created Successfully', + 'language' => $language + ], 201); + } +} + diff --git a/app/Http/Controllers/Api/V1/PaymentController.php b/app/Http/Controllers/Api/V1/PaymentController.php index 4eae235a..73050af0 100644 --- a/app/Http/Controllers/Api/V1/PaymentController.php +++ b/app/Http/Controllers/Api/V1/PaymentController.php @@ -114,7 +114,14 @@ public function handlePaystackCallback($organisation_id, $id, Request $request) } $payment->save(); - $user_id = Organisation::find($organisation_id)->first()->user_id; + $user = Organisation::find($organisation_id)->first(); + if(!$user) { + return response()->json([ + 'status' => 404, + 'message' => 'User Not found' + ], 404); + } + $user_id = $user->id; $userSubscription = new UserSubscription; $userSubscription->user_id = $user_id; @@ -230,7 +237,14 @@ public function handleFlutterwaveCallback($organisation_id, $id, Request $reques } $payment->save(); - $user_id = Organisation::find($organisation_id)->first()->user_id; + $user = Organisation::find($organisation_id)->first(); + if(!$user) { + return response()->json([ + 'status' => 404, + 'message' => 'User Not found' + ], 404); + } + $user_id = $user->id; $userSubscription = new UserSubscription; $userSubscription->user_id = $user_id; diff --git a/database/migrations/2024_08_07_213218_alter_languages_table.php b/database/migrations/2024_08_07_213218_alter_languages_table.php new file mode 100644 index 00000000..e06a22dc --- /dev/null +++ b/database/migrations/2024_08_07_213218_alter_languages_table.php @@ -0,0 +1,28 @@ +string('language')->unique()->after('id'); + $table->string('code')->unique()->after('language'); + $table->text('description')->nullable()->after('code'); + $table->dropColumn('name'); + }); + } + + public function down() + { + Schema::table('languages', function (Blueprint $table) { + $table->dropUnique(['language']); + $table->dropUnique(['code']); + $table->dropColumn(['language', 'code', 'description']); + $table->string('name')->after('id'); + }); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index f9b59886..78bd4e9b 100755 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -79,6 +79,7 @@ public function run(): void OrderSeeder::class, SqueezePageSeeder::class, TimezoneSeeder::class, + LanguagesTableSeeder::class, ]); $this->call([ diff --git a/database/seeders/LanguagesTableSeeder.php b/database/seeders/LanguagesTableSeeder.php new file mode 100644 index 00000000..50c4cd02 --- /dev/null +++ b/database/seeders/LanguagesTableSeeder.php @@ -0,0 +1,37 @@ + (string) Str::uuid(), 'language' => 'English', 'code' => 'en', 'description' => 'English'], + ['id' => (string) Str::uuid(), 'language' => 'Spanish', 'code' => 'es', 'description' => 'Español'], + ['id' => (string) Str::uuid(), 'language' => 'French', 'code' => 'fr', 'description' => 'Français'], + ['id' => (string) Str::uuid(), 'language' => 'German', 'code' => 'de', 'description' => 'Deutsch'], + ['id' => (string) Str::uuid(), 'language' => 'Chinese', 'code' => 'zh', 'description' => '中文'], + ['id' => (string) Str::uuid(), 'language' => 'Japanese', 'code' => 'ja', 'description' => '日本語'], + ['id' => (string) Str::uuid(), 'language' => 'Russian', 'code' => 'ru', 'description' => 'Русский'], + ['id' => (string) Str::uuid(), 'language' => 'Portuguese', 'code' => 'pt', 'description' => 'Português'], + ['id' => (string) Str::uuid(), 'language' => 'Arabic', 'code' => 'ar', 'description' => 'العربية'], + ['id' => (string) Str::uuid(), 'language' => 'Hindi', 'code' => 'hi', 'description' => 'हिन्दी'], + ['id' => (string) Str::uuid(), 'language' => 'Bengali', 'code' => 'bn', 'description' => 'বাংলা'], + ['id' => (string) Str::uuid(), 'language' => 'Korean', 'code' => 'ko', 'description' => '한국어'], + ['id' => (string) Str::uuid(), 'language' => 'Italian', 'code' => 'it', 'description' => 'Italiano'], + ['id' => (string) Str::uuid(), 'language' => 'Turkish', 'code' => 'tr', 'description' => 'Türkçe'], + ['id' => (string) Str::uuid(), 'language' => 'Dutch', 'code' => 'nl', 'description' => 'Nederlands'], + ['id' => (string) Str::uuid(), 'language' => 'Greek', 'code' => 'el', 'description' => 'Ελληνικά'], + ['id' => (string) Str::uuid(), 'language' => 'Swedish', 'code' => 'sv', 'description' => 'Svenska'], + ['id' => (string) Str::uuid(), 'language' => 'Danish', 'code' => 'da', 'description' => 'Dansk'], + ['id' => (string) Str::uuid(), 'language' => 'Finnish', 'code' => 'fi', 'description' => 'Suomi'], + ['id' => (string) Str::uuid(), 'language' => 'Polish', 'code' => 'pl', 'description' => 'Polski'], + ]; + + + DB::table('languages')->insert($languages); + } +} diff --git a/routes/api.php b/routes/api.php index d05283cb..a188d4c5 100755 --- a/routes/api.php +++ b/routes/api.php @@ -96,6 +96,9 @@ Route::get('/products/{product_id}', [ProductController::class, 'show']); Route::get('/billing-plans', [BillingPlanController::class, 'index']); Route::get('/billing-plans/{id}', [BillingPlanController::class, 'getBillingPlan']); + Route::get('/payments/paystack/{organisation_id}/verify/{id}', [PaymentController::class, 'handlePaystackCallback']); + Route::get('/payments/flutterwave/{organisation_id}/verify/{id}', [PaymentController::class, 'handleFlutterwaveCallback']); + Route::post('/languages', [LanguageController::class, 'create']); Route::middleware('throttle:10,1')->get('/topics/search', [ArticleController::class, 'search']); @@ -157,9 +160,8 @@ Route::apiResource('/features', FeatureController::class); Route::apiResource('/plans', SubscriptionController::class); Route::post('/payments/paystack', [PaymentController::class, 'initiatePaymentForPayStack']); - Route::get('/payments/paystack/{organisation_id}/verify/{id}', [PaymentController::class, 'handlePaystackCallback']); + Route::post('/payments/flutterwave', [PaymentController::class, 'initiatePaymentForFlutterWave']); - Route::get('/payments/flutterwave/{organisation_id}/verify/{id}', [PaymentController::class, 'handleFlutterwaveCallback']); Route::get('/payments/cancel', [PaymentController::class, 'cancel'])->name('payment.cancel'); Route::post('/users/plans/{user_subscription}/cancel', [\App\Http\Controllers\Api\V1\User\SubscriptionController::class, 'destroy']); Route::get('/users/plan', [\App\Http\Controllers\Api\V1\User\SubscriptionController::class, 'userPlan']); @@ -254,6 +256,7 @@ Route::get('/notifications', [UserNotificationController::class, 'getByUser'])->middleware('auth.jwt'); //Timezone Route::get('/timezones', [TimezoneController::class, 'index']); + From dcaf4c4cf3f10665fe842a529a0f1c5192ece922 Mon Sep 17 00:00:00 2001 From: Amowogbaje Gideon Date: Wed, 7 Aug 2024 22:58:55 +0100 Subject: [PATCH 04/15] create langauge working fine --- app/Http/Controllers/Api/V1/LanguageController.php | 4 ++-- database/seeders/LanguagesTableSeeder.php | 1 + routes/api.php | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/LanguageController.php b/app/Http/Controllers/Api/V1/LanguageController.php index 266de186..7940b438 100644 --- a/app/Http/Controllers/Api/V1/LanguageController.php +++ b/app/Http/Controllers/Api/V1/LanguageController.php @@ -22,7 +22,7 @@ public function create(Request $request) if ($validator->fails()) { return response()->json([ - 'status_code' => 400, + 'status' => 400, 'message' => 'Validation Error', 'errors' => $validator->errors() ], 400); @@ -36,7 +36,7 @@ public function create(Request $request) ]); return response()->json([ - 'status_code' => 201, + 'status' => 201, 'message' => 'Language Created Successfully', 'language' => $language ], 201); diff --git a/database/seeders/LanguagesTableSeeder.php b/database/seeders/LanguagesTableSeeder.php index 50c4cd02..cfd07942 100644 --- a/database/seeders/LanguagesTableSeeder.php +++ b/database/seeders/LanguagesTableSeeder.php @@ -1,4 +1,5 @@ Date: Wed, 7 Aug 2024 23:07:13 +0100 Subject: [PATCH 05/15] feat: implement all month revenue for graph --- .../Api/V1/User/DashboardController.php | 35 +++++++++++++++++++ app/Models/Order.php | 11 ++++++ routes/api.php | 1 + 3 files changed, 47 insertions(+) diff --git a/app/Http/Controllers/Api/V1/User/DashboardController.php b/app/Http/Controllers/Api/V1/User/DashboardController.php index f8b2cd9b..6e885cd7 100644 --- a/app/Http/Controllers/Api/V1/User/DashboardController.php +++ b/app/Http/Controllers/Api/V1/User/DashboardController.php @@ -3,8 +3,11 @@ namespace App\Http\Controllers\Api\V1\User; use App\Http\Controllers\Controller; +use App\Models\Order; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; class DashboardController extends Controller { @@ -106,6 +109,38 @@ public function index() ]); } + public function user_analytics() + { + $user = Auth::user(); + + $monthlyRevenue = Order::whereHas('product', function ($query) use ($user) { + $query->where('user_id', $user->id); + }) + ->select( + DB::raw('EXTRACT(MONTH FROM created_at) as month'), + DB::raw('SUM(total_amount) as total_revenue') + ) + ->groupBy('month') + ->orderBy('month') + ->get() + ->pluck('total_revenue', 'month') + ->toArray(); + + $revenueByMonth = collect(range(1, 12))->mapWithKeys(function ($month) use ($monthlyRevenue) { + $monthName = date('M', mktime(0, 0, 0, $month, 1)); + $revenue = $monthlyRevenue[$month] ?? 0; + return [$monthName => $revenue]; + }); + + return response()->json([ + 'message' => 'User analytics retrieved successfully', + 'status_code' => Response::HTTP_OK, + 'data' => [ + $revenueByMonth, + ] + ]); + } + /** * Show the form for creating a new resource. */ diff --git a/app/Models/Order.php b/app/Models/Order.php index 747ba981..8ca3fa44 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -5,9 +5,20 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Concerns\HasUuids; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class Order extends Model { use HasUuids; use HasFactory; + + public function product(): BelongsTo + { + return $this->belongsTo(Product::class, 'product_id'); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } } diff --git a/routes/api.php b/routes/api.php index abf7f32e..40039c84 100755 --- a/routes/api.php +++ b/routes/api.php @@ -250,6 +250,7 @@ Route::get('/user/preferences', [PreferenceController::class, 'index']); Route::delete('/user/preferences/{id}', [PreferenceController::class, 'delete']); Route::get('/user-statistics', [DashboardController::class, 'index']); + Route::get('/user-analytics', [DashboardController::class, 'user_analytics']); }); From d10e6a607967a2dece088caadb9843c2dc502432 Mon Sep 17 00:00:00 2001 From: Amowogbaje Gideon Date: Wed, 7 Aug 2024 23:14:29 +0100 Subject: [PATCH 06/15] added auth check --- app/Http/Controllers/Api/V1/LanguageController.php | 7 +++++++ app/Http/Controllers/Api/V1/PaymentController.php | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/Http/Controllers/Api/V1/LanguageController.php b/app/Http/Controllers/Api/V1/LanguageController.php index 7940b438..7f639226 100644 --- a/app/Http/Controllers/Api/V1/LanguageController.php +++ b/app/Http/Controllers/Api/V1/LanguageController.php @@ -14,6 +14,13 @@ class LanguageController extends Controller { public function create(Request $request) { + if (!auth()->check()) { + return response()->json([ + 'status' => 401, + 'message' => 'Unauthorized' + ], 401); + } + $validator = Validator::make($request->all(), [ 'language' => 'required|string|unique:languages', 'code' => 'required|string|unique:languages', diff --git a/app/Http/Controllers/Api/V1/PaymentController.php b/app/Http/Controllers/Api/V1/PaymentController.php index 73050af0..d67a822f 100644 --- a/app/Http/Controllers/Api/V1/PaymentController.php +++ b/app/Http/Controllers/Api/V1/PaymentController.php @@ -25,6 +25,12 @@ public function __construct(PaymentService $paymentService) public function initiatePaymentForPayStack(Request $request) { + if (!auth()->check()) { + return response()->json([ + 'status_code' => 401, + 'message' => 'Unauthorized' + ], 401); + } // return response()->json(['h'=> 'ng']); $validator = Validator::make($request->all(), [ 'organisation_id' => 'required', @@ -142,6 +148,13 @@ public function handlePaystackCallback($organisation_id, $id, Request $request) public function initiatePaymentForFlutterWave(Request $request) { + if (!auth()->check()) { + return response()->json([ + 'status_code' => 401, + 'message' => 'Unauthorized' + ], 401); + } + $validator = Validator::make($request->all(), [ 'organisation_id' => 'required', 'plan_id' =>'required', From 7f5a6fcfc5f124ad979b1fdbc793dd08ec844136 Mon Sep 17 00:00:00 2001 From: Amowogbaje Gideon Date: Wed, 7 Aug 2024 23:34:56 +0100 Subject: [PATCH 07/15] feat:get languages endpoint --- .../Controllers/Api/V1/LanguageController.php | 20 +++++++++ database/seeders/LanguagesTableSeeder.php | 41 ++++++++++--------- routes/api.php | 1 + 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/app/Http/Controllers/Api/V1/LanguageController.php b/app/Http/Controllers/Api/V1/LanguageController.php index 7f639226..05a78285 100644 --- a/app/Http/Controllers/Api/V1/LanguageController.php +++ b/app/Http/Controllers/Api/V1/LanguageController.php @@ -48,5 +48,25 @@ public function create(Request $request) 'language' => $language ], 201); } + + public function index() + { + // if (!auth()->check()) { + // return response()->json([ + // 'status' => 401, + // 'message' => 'Unauthorized' + // ], 401); + // } + + + + $languages = Language::select('language', 'code')->get(); + + return response()->json([ + 'status' => 200, + 'message' => 'Languages fetched successfully', + 'languages' => $languages + ], 200); + } } diff --git a/database/seeders/LanguagesTableSeeder.php b/database/seeders/LanguagesTableSeeder.php index cfd07942..8d597804 100644 --- a/database/seeders/LanguagesTableSeeder.php +++ b/database/seeders/LanguagesTableSeeder.php @@ -10,27 +10,28 @@ class LanguagesTableSeeder extends Seeder public function run() { $languages = [ - ['id' => (string) Str::uuid(), 'language' => 'English', 'code' => 'en', 'description' => 'English'], - ['id' => (string) Str::uuid(), 'language' => 'Spanish', 'code' => 'es', 'description' => 'Español'], - ['id' => (string) Str::uuid(), 'language' => 'French', 'code' => 'fr', 'description' => 'Français'], - ['id' => (string) Str::uuid(), 'language' => 'German', 'code' => 'de', 'description' => 'Deutsch'], - ['id' => (string) Str::uuid(), 'language' => 'Chinese', 'code' => 'zh', 'description' => '中文'], - ['id' => (string) Str::uuid(), 'language' => 'Japanese', 'code' => 'ja', 'description' => '日本語'], - ['id' => (string) Str::uuid(), 'language' => 'Russian', 'code' => 'ru', 'description' => 'Русский'], - ['id' => (string) Str::uuid(), 'language' => 'Portuguese', 'code' => 'pt', 'description' => 'Português'], - ['id' => (string) Str::uuid(), 'language' => 'Arabic', 'code' => 'ar', 'description' => 'العربية'], - ['id' => (string) Str::uuid(), 'language' => 'Hindi', 'code' => 'hi', 'description' => 'हिन्दी'], - ['id' => (string) Str::uuid(), 'language' => 'Bengali', 'code' => 'bn', 'description' => 'বাংলা'], - ['id' => (string) Str::uuid(), 'language' => 'Korean', 'code' => 'ko', 'description' => '한국어'], - ['id' => (string) Str::uuid(), 'language' => 'Italian', 'code' => 'it', 'description' => 'Italiano'], - ['id' => (string) Str::uuid(), 'language' => 'Turkish', 'code' => 'tr', 'description' => 'Türkçe'], - ['id' => (string) Str::uuid(), 'language' => 'Dutch', 'code' => 'nl', 'description' => 'Nederlands'], - ['id' => (string) Str::uuid(), 'language' => 'Greek', 'code' => 'el', 'description' => 'Ελληνικά'], - ['id' => (string) Str::uuid(), 'language' => 'Swedish', 'code' => 'sv', 'description' => 'Svenska'], - ['id' => (string) Str::uuid(), 'language' => 'Danish', 'code' => 'da', 'description' => 'Dansk'], - ['id' => (string) Str::uuid(), 'language' => 'Finnish', 'code' => 'fi', 'description' => 'Suomi'], - ['id' => (string) Str::uuid(), 'language' => 'Polish', 'code' => 'pl', 'description' => 'Polski'], + ['id' => (string) Str::uuid(), 'language' => 'English', 'code' => 'en', 'description' => 'English Language'], + ['id' => (string) Str::uuid(), 'language' => 'Español (Spanish)', 'code' => 'es', 'description' => 'Spanish Language'], + ['id' => (string) Str::uuid(), 'language' => 'Français (French)', 'code' => 'fr', 'description' => 'French Language'], + ['id' => (string) Str::uuid(), 'language' => 'Deutsch (German)', 'code' => 'de', 'description' => 'German Language'], + ['id' => (string) Str::uuid(), 'language' => '中文 (Chinese)', 'code' => 'zh', 'description' => 'Chinese Language'], + ['id' => (string) Str::uuid(), 'language' => '日本語 (Japanese)', 'code' => 'ja', 'description' => 'Japanese Language'], + ['id' => (string) Str::uuid(), 'language' => 'Русский (Russian)', 'code' => 'ru', 'description' => 'Russian Language'], + ['id' => (string) Str::uuid(), 'language' => 'Português (Portuguese)', 'code' => 'pt', 'description' => 'Portuguese Language'], + ['id' => (string) Str::uuid(), 'language' => 'العربية (Arabic)', 'code' => 'ar', 'description' => 'Arabic Language'], + ['id' => (string) Str::uuid(), 'language' => 'हिन्दी (Hindi)', 'code' => 'hi', 'description' => 'Hindi Language'], + ['id' => (string) Str::uuid(), 'language' => 'বাংলা (Bengali)', 'code' => 'bn', 'description' => 'Bengali Language'], + ['id' => (string) Str::uuid(), 'language' => '한국어 (Korean)', 'code' => 'ko', 'description' => 'Korean Language'], + ['id' => (string) Str::uuid(), 'language' => 'Italiano (Italian)', 'code' => 'it', 'description' => 'Italian Language'], + ['id' => (string) Str::uuid(), 'language' => 'Türkçe (Turkish)', 'code' => 'tr', 'description' => 'Turkish Language'], + ['id' => (string) Str::uuid(), 'language' => 'Nederlands (Dutch)', 'code' => 'nl', 'description' => 'Dutch Language'], + ['id' => (string) Str::uuid(), 'language' => 'Ελληνικά (Greek)', 'code' => 'el', 'description' => 'Greek Language'], + ['id' => (string) Str::uuid(), 'language' => 'Svenska (Swedish)', 'code' => 'sv', 'description' => 'Swedish Language'], + ['id' => (string) Str::uuid(), 'language' => 'Dansk (Danish)', 'code' => 'da', 'description' => 'Danish Language'], + ['id' => (string) Str::uuid(), 'language' => 'Suomi (Finnish)', 'code' => 'fi', 'description' => 'Finnish Language'], + ['id' => (string) Str::uuid(), 'language' => 'Polski (Polish)', 'code' => 'pl', 'description' => 'Polish Language'], ]; + DB::table('languages')->insert($languages); diff --git a/routes/api.php b/routes/api.php index d2c352d5..341f7b8c 100755 --- a/routes/api.php +++ b/routes/api.php @@ -100,6 +100,7 @@ Route::get('/payments/paystack/{organisation_id}/verify/{id}', [PaymentController::class, 'handlePaystackCallback']); Route::get('/payments/flutterwave/{organisation_id}/verify/{id}', [PaymentController::class, 'handleFlutterwaveCallback']); Route::post('/languages', [LanguageController::class, 'create']); + Route::get('/languages', [LanguageController::class, 'index']); Route::middleware('throttle:10,1')->get('/topics/search', [ArticleController::class, 'search']); From 7ca762a3305a11ce183e92a80910fb5f0833ca31 Mon Sep 17 00:00:00 2001 From: Amowogbaje Gideon Date: Thu, 8 Aug 2024 00:44:41 +0100 Subject: [PATCH 08/15] quests seeder mismatch --- database/seeders/DatabaseSeeder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 2c24409b..42c36070 100755 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -83,7 +83,7 @@ public function run(): void ]); $this->call([ - QuestsSeeder::class, + // QuestsSeeder::class, QuestSeeder::class, QuestMessageSeeder::class, ]); From 9de8547ab311164f53efb7e4706f36eedeb12279 Mon Sep 17 00:00:00 2001 From: Ibrahim Adedayo Date: Thu, 8 Aug 2024 01:09:46 +0100 Subject: [PATCH 09/15] fix: added endpoint for google --- .../Api/V1/Auth/SocialAuthController.php | 87 +++++++++++++++++++ routes/api.php | 1 + tests/Unit/RegistrationTest.php | 24 +++++ 3 files changed, 112 insertions(+) diff --git a/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php b/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php index b5d55151..cbba2db9 100644 --- a/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php +++ b/app/Http/Controllers/Api/V1/Auth/SocialAuthController.php @@ -10,6 +10,7 @@ use App\Models\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; +use Illuminate\Support\Facades\Validator; class SocialAuthController extends Controller { @@ -111,6 +112,92 @@ public function saveGoogleRequest(Request $request) return response()->json($response); } + public function saveGoogleRequestPost(Request $request) + { + // Validate the incoming request + $validator = Validator::make($request->all(), [ + 'token' => 'required|string', + 'email' => 'required|email:rfc', + ]); + + if ($validator->fails()) { + return response()->json([ + 'status_code' => 422, + 'message' => $validator->errors() + ], 422); + } + + // Extract Google user data from the request + $google_token = $request->input('token'); + $email = $request->input('email'); + + + try { + // Retrieve user information from Google + $googleUser = Socialite::driver('google')->userFromToken($google_token); + + // Check if the token email matches the email provided + if ($googleUser->email !== $email) { + return response()->json([ + 'status_code' => 400, + "error" => "Bad Request", + 'message' => 'Invalid Google token' + ], 401); + } + + // Create or update the user + $user = User::updateOrCreate( + ['email' => $email], + [ + 'password' => Hash::make(Str::random(12)), // Random password for social sign-ins + 'social_id' => $googleUser->id, + 'is_verified' => true, + 'signup_type' => 'Google', + 'is_active' => true, + ] + ); + + // Handle profile update or creation + if ($user->profile) { + $user->profile->update([ + 'first_name' => $googleUser->user['given_name'], + 'last_name' => $googleUser->user['family_name'], + 'avatar_url' => $googleUser->user['picture'], + ]); + } else { + $user->profile()->create([ + 'first_name' => $googleUser->user['given_name'], + 'last_name' => $googleUser->user['family_name'], + 'avatar_url' => $googleUser->user['picture'], + ]); + } + + // Generate JWT token + $token = JWTAuth::fromUser($user); + + return response()->json([ + 'status_code' => 200, + 'message' => 'User Created Successfully', + 'access_token' => $token, + 'data' => [ + 'user' => [ + 'id' => $user->id, + 'email' => $user->email, + 'first_name' => $googleUser->user['given_name'], + 'last_name' => $googleUser->user['family_name'], + 'fullname' => $googleUser->user['given_name'].' '.$googleUser->user['family_name'], + 'role' => $user->role, + ] + ] + ], 200); + } catch (Exception $e) { + return response()->json([ + 'status_code' => 500, + 'message' => $e->getMessage() + ], 500); + } + } + public function loginUsingFacebook() { return Socialite::driver('facebook')->stateless()->redirect(); diff --git a/routes/api.php b/routes/api.php index abf7f32e..e0bd52bc 100755 --- a/routes/api.php +++ b/routes/api.php @@ -72,6 +72,7 @@ Route::get('/auth/login-google', [SocialAuthController::class, 'redirectToGoogle']); Route::get('/auth/google/callback', [SocialAuthController::class, 'handleGoogleCallback']); Route::post('/auth/google/callback', [SocialAuthController::class, 'saveGoogleRequest']); + Route::post('/auth/google', [SocialAuthController::class, 'saveGoogleRequestPost']); /* Forget and Reset Password using OTP */ Route::post('/auth/forgot-password', [ForgotResetPasswordController::class, 'forgetPassword']); Route::post('/auth/reset-forgot-password', [ForgotResetPasswordController::class, 'resetPassword']); diff --git a/tests/Unit/RegistrationTest.php b/tests/Unit/RegistrationTest.php index a71deac7..fc8cff4d 100755 --- a/tests/Unit/RegistrationTest.php +++ b/tests/Unit/RegistrationTest.php @@ -263,4 +263,28 @@ public function facebook_login_creates_or_updates_user_and_profile_with_post() $this->assertEquals('Doe', $user->profile->last_name); $this->assertEquals($facebookUser['avatar'], $user->profile->avatar_url); } + + /** @test */ + public function google_login_creates_or_updates_user_and_profile_with_post2() + { + // Mock Google user response + + $response = $this->postJson('/api/v1/auth/google', [ + 'token' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2RlcGxveW1lbnQuYXBpLXBocC5ib2lsZXJwbGF0ZS5obmcudGVjaC9hcGkvdjEvYXV0aC9nb29nbGUvY2FsbGJhY2siLCJpYXQiOjE3MjIyNTEwMzksImV4cCI6MTcyMjI1NDYzOSwibmJmIjoxNzIyMjUxMDM5LCJqdGkiOiJHVUYzSVJUNWVBMFgxcDJnIiwic3ViIjoiOWNhMzQ4MjQtMDFlMC00YjAyLWExMGUtMGU0ZTdhMzM3ZGU1IiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.m8XxKDdKWVp08vjUghXPPu7fi22Fg0RdupaKSoQ80g0', + 'email' => 'tulbadex@gmail.com' + ]); + + dd($response); + + $response->assertStatus(Response::HTTP_OK); + + // Verify user in the database + $user = User::where('email', $facebookUser['email'])->first(); + $this->assertNotNull($user); + $this->assertEquals($facebookUser['id'], $user->social_id); + $this->assertEquals('Facebook', $user->signup_type); + $this->assertEquals('John', $user->profile->first_name); + $this->assertEquals('Doe', $user->profile->last_name); + $this->assertEquals($facebookUser['avatar'], $user->profile->avatar_url); + } } From 40c3b232ce53f94470baf16db6f7f50e6638df2f Mon Sep 17 00:00:00 2001 From: Ibrahim Adedayo Date: Thu, 8 Aug 2024 01:16:13 +0100 Subject: [PATCH 10/15] fix: added endpoint for google --- public/uploads/1723076130.jpg | Bin 0 -> 695 bytes tests/Unit/RegistrationTest.php | 24 ------------------------ 2 files changed, 24 deletions(-) create mode 100644 public/uploads/1723076130.jpg diff --git a/public/uploads/1723076130.jpg b/public/uploads/1723076130.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b00797624aa746a4a3578303e4fa34d50be62291 GIT binary patch literal 695 zcmex=_1P|rX?qqI0P zFI~aY%U!`Mz|~!$%)&rZMU_b4?usLlYAdd38%$3nLpnV-q8g zA&i`yoIKn-61=<;Mv5|uMkIs(2N(o77`Pa?m>HEAm;@P_1sVSzVUTBFU}OdQ7UW?l zU}R!uVP#|I;N;>4D%dK(z{JSR%*4XX%F4n5R9y>{XJ8Rz6;d>GWD^cdWLGK_F>0K+ zkVDyN<3Z7&iyu^slZu)+xx~aJB&Af<)HO7&0Dr^+rDGx zu0w~996fgY#K}{aE?>EN?fQ+Iw;n!v{N(Ag=PzEq`uOSdm#^Qx|M>X}>z(JGL-`{vmgtrq9L1*V<3BCp|FxsBZr97#DyCVaw;1KeGpA5 xy2vG_V)9V+BgkuDpAqM=CbE16_ZY%ow-|Vs8G(_assertEquals('Doe', $user->profile->last_name); $this->assertEquals($facebookUser['avatar'], $user->profile->avatar_url); } - - /** @test */ - public function google_login_creates_or_updates_user_and_profile_with_post2() - { - // Mock Google user response - - $response = $this->postJson('/api/v1/auth/google', [ - 'token' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2RlcGxveW1lbnQuYXBpLXBocC5ib2lsZXJwbGF0ZS5obmcudGVjaC9hcGkvdjEvYXV0aC9nb29nbGUvY2FsbGJhY2siLCJpYXQiOjE3MjIyNTEwMzksImV4cCI6MTcyMjI1NDYzOSwibmJmIjoxNzIyMjUxMDM5LCJqdGkiOiJHVUYzSVJUNWVBMFgxcDJnIiwic3ViIjoiOWNhMzQ4MjQtMDFlMC00YjAyLWExMGUtMGU0ZTdhMzM3ZGU1IiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.m8XxKDdKWVp08vjUghXPPu7fi22Fg0RdupaKSoQ80g0', - 'email' => 'tulbadex@gmail.com' - ]); - - dd($response); - - $response->assertStatus(Response::HTTP_OK); - - // Verify user in the database - $user = User::where('email', $facebookUser['email'])->first(); - $this->assertNotNull($user); - $this->assertEquals($facebookUser['id'], $user->social_id); - $this->assertEquals('Facebook', $user->signup_type); - $this->assertEquals('John', $user->profile->first_name); - $this->assertEquals('Doe', $user->profile->last_name); - $this->assertEquals($facebookUser['avatar'], $user->profile->avatar_url); - } } From 104cc93cd593a6d48030a79f5c90ed6203610cea Mon Sep 17 00:00:00 2001 From: Alemoh Rapheal Date: Thu, 8 Aug 2024 00:08:27 -0700 Subject: [PATCH 11/15] feat: retrieve all faqs for admin with paginated and unpaginated option --- .../Api/V1/Admin/FaqController.php | 40 ++++++++++++-- routes/api.php | 2 +- tests/Feature/FaqControllerTest.php | 53 +++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/Feature/FaqControllerTest.php diff --git a/app/Http/Controllers/Api/V1/Admin/FaqController.php b/app/Http/Controllers/Api/V1/Admin/FaqController.php index 9ba7d499..e8398749 100644 --- a/app/Http/Controllers/Api/V1/Admin/FaqController.php +++ b/app/Http/Controllers/Api/V1/Admin/FaqController.php @@ -5,21 +5,52 @@ use App\Http\Controllers\Controller; use App\Models\Faq; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Validator; + class FaqController extends Controller { /** * Display a listing of the resource. */ - public function index() + public function index(Request $request) { - $faq = Faq::where('status', 1)->get(); + $validator = Validator::make($request->all(), [ + 'page' => 'nullable|integer|min:1', + 'size' => 'nullable|integer|min:1', + ]); + + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid input parameters.', + 'errors' => $validator->errors(), + ], Response::HTTP_BAD_REQUEST); + } + + $perPage = $request->input('size', 15); + + $faqs = Faq::where('status', 1)->paginate($perPage); return response()->json([ 'status_code' => 200, 'message' => "Faq returned successfully", - 'data' => $faq - ]); + 'data' => collect($faqs->items())->map(function ($faq) { + return [ + 'id' => $faq->id, + 'question' => $faq->question, + 'answer' => $faq->answer, + ]; + }), + 'pagination' => [ + 'current_page' => $faqs->currentPage(), + 'total_pages' => $faqs->lastPage(), + 'page_size' => $faqs->perPage(), + 'total_items' => $faqs->total(), + ], + ], Response::HTTP_OK); } /** @@ -70,7 +101,6 @@ public function update(Request $request, Faq $faq) 'message' => "Faq updated successfully", 'data' => $faq ], 200); - } /** diff --git a/routes/api.php b/routes/api.php index 66138f1b..6e1203fb 100755 --- a/routes/api.php +++ b/routes/api.php @@ -219,10 +219,10 @@ Route::delete('/blogs/{id}', [BlogController::class, 'destroy']); Route::get('/waitlists', [WaitListController::class, 'index']); Route::apiResource('squeeze-pages', SqueezePageCoontroller::class); + Route::apiResource('faqs', FaqController::class); }); Route::post('/waitlists', [WaitListController::class, 'store']); - Route::apiResource('faqs', FaqController::class); Route::get('/blogs/{id}', [BlogController::class, 'show']); diff --git a/tests/Feature/FaqControllerTest.php b/tests/Feature/FaqControllerTest.php new file mode 100644 index 00000000..f3ae4bc2 --- /dev/null +++ b/tests/Feature/FaqControllerTest.php @@ -0,0 +1,53 @@ +adminUser = User::factory()->create(['role' => 'admin']); + $this->adminToken = JWTAuth::fromUser($this->adminUser); + } + + public function test_index_returns_paginated_faqs() + { + $response = $this->withHeaders(['Authorization' => "Bearer $this->adminToken"]) + ->getJson('/api/v1/faqs?page=1&size=5'); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data' => [ + '*' => ['id', 'question', 'answer'] + ], + 'pagination' => ['current_page', 'total_pages', 'page_size', 'total_items'] + ]) + ->assertJsonCount(5, 'data'); + } + + public function test_index_returns_faqs_without_pagination() + { + $response = $this->withHeaders(['Authorization' => "Bearer $this->adminToken"]) + ->getJson('/api/v1/faqs'); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data' => [ + '*' => ['id', 'question', 'answer'] + ], + ]); + } +} From 8eaf925fe0173824a8c82fd4cec49cf825c6c088 Mon Sep 17 00:00:00 2001 From: Alemoh Rapheal Date: Thu, 8 Aug 2024 00:17:52 -0700 Subject: [PATCH 12/15] fix: add check for unathorised access to faqs list --- tests/Feature/FaqControllerTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Feature/FaqControllerTest.php b/tests/Feature/FaqControllerTest.php index f3ae4bc2..25e36565 100644 --- a/tests/Feature/FaqControllerTest.php +++ b/tests/Feature/FaqControllerTest.php @@ -50,4 +50,12 @@ public function test_index_returns_faqs_without_pagination() ], ]); } + + + public function test_if_it_fails_for_unathorised_access_to_faqs() + { + + $response = $this->getJson('/api/v1/faqs'); + $response->assertStatus(401); + } } From 8d5591c21ec10f0e2ea30dc0ea54119771ed9483 Mon Sep 17 00:00:00 2001 From: Alemoh Rapheal Date: Thu, 8 Aug 2024 00:24:53 -0700 Subject: [PATCH 13/15] fix: add check for unathorised access to faqs list --- tests/Feature/FaqControllerTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Feature/FaqControllerTest.php b/tests/Feature/FaqControllerTest.php index 25e36565..f1a15b86 100644 --- a/tests/Feature/FaqControllerTest.php +++ b/tests/Feature/FaqControllerTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature; use App\Models\User; +use Database\Seeders\FaqSeeder; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; @@ -23,6 +24,8 @@ public function setUp(): void public function test_index_returns_paginated_faqs() { + $this->seed(FaqSeeder::class); + $response = $this->withHeaders(['Authorization' => "Bearer $this->adminToken"]) ->getJson('/api/v1/faqs?page=1&size=5'); From 55c25fcbd1aa47f617a8f83fbd39718c57573a32 Mon Sep 17 00:00:00 2001 From: Alemoh Rapheal Date: Thu, 8 Aug 2024 00:26:00 -0700 Subject: [PATCH 14/15] fix: add check for unathorised access to faqs list --- tests/Feature/FaqControllerTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Feature/FaqControllerTest.php b/tests/Feature/FaqControllerTest.php index f1a15b86..3c64010a 100644 --- a/tests/Feature/FaqControllerTest.php +++ b/tests/Feature/FaqControllerTest.php @@ -11,6 +11,9 @@ class FaqControllerTest extends TestCase { + use RefreshDatabase; + + protected $adminUser; protected $adminToken; From c8098c26d945e2205cfbd33fb4c726bba26aeb3a Mon Sep 17 00:00:00 2001 From: Alemoh Rapheal Date: Thu, 8 Aug 2024 00:29:22 -0700 Subject: [PATCH 15/15] fix: add check for unathorised access to faqs list --- tests/Feature/FaqControllerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Feature/FaqControllerTest.php b/tests/Feature/FaqControllerTest.php index 3c64010a..cdf103ad 100644 --- a/tests/Feature/FaqControllerTest.php +++ b/tests/Feature/FaqControllerTest.php @@ -13,7 +13,6 @@ class FaqControllerTest extends TestCase { use RefreshDatabase; - protected $adminUser; protected $adminToken;