diff --git a/.gitignore b/.gitignore index d6d3ea8d..473babcb 100755 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,4 @@ yarn-error.log docker-compose.yml /public/uploads ResponseHTTP_OK, -can \ No newline at end of file +can diff --git a/app/Http/Controllers/Api/V1/ProductController.php b/app/Http/Controllers/Api/V1/ProductController.php index 2c9d1e55..8f5d3dee 100755 --- a/app/Http/Controllers/Api/V1/ProductController.php +++ b/app/Http/Controllers/Api/V1/ProductController.php @@ -19,10 +19,12 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use App\Http\Resources\ProductResource; +use App\Models\Order; class ProductController extends Controller { + public function search(Request $request) { $validator = Validator::make($request->all(), [ @@ -202,33 +204,29 @@ public function store(CreateProductRequest $request, $org_id) } $imageUrl = null; - if ($request->hasFile('image')) { - $imagePath = $request->file('image')->store('product_images', 'public'); + if ($request->hasFile('image_url')) { + $imagePath = $request->file('image_url')->store('product_images', 'public'); $imageUrl = Storage::url($imagePath); } $product = Product::create([ - 'name' => $request->input('title'), + 'name' => $request->input('name'), 'description' => $request->input('description'), 'slug' => Carbon::now(), 'tags' => $request->input('category'), + 'category' => $request->input('category'), 'price' => $request->input('price'), 'imageUrl' => $imageUrl, 'user_id' => auth()->id(), 'org_id' => $org_id ]); - CategoryProduct::create([ - 'category_id' => $request->input('category'), - 'product_id' => $product->product_id - ]); - - $standardSize = Size::where('size', 'standard')->first('id'); - + $standardSize = Size::where('size', 'standard')->value('id'); + // dd($standardSize); $productVariant = ProductVariant::create([ 'product_id' => $product->product_id, - 'stock' => $request->input('stock'), - 'stock_status' => $request->input('stock') > 0 ? 'in_stock' : 'out_stock', + 'stock' => $request->input('quantity'), + 'stock_status' => $request->input('quantity') > 0 ? 'in_stock' : 'out_stock', 'price' => $request->input('price'), 'size_id' => $standardSize->id, ]); diff --git a/app/Http/Controllers/SqueezePageUserController.php b/app/Http/Controllers/SqueezePageUserController.php new file mode 100644 index 00000000..3028dfd4 --- /dev/null +++ b/app/Http/Controllers/SqueezePageUserController.php @@ -0,0 +1,71 @@ +validate([ + 'firstname' => 'required|string|max:255', + 'lastname' => 'required|string|max:255', + 'email' => 'required|string|email|max:255', + 'title' => 'required|string|max:255', + ]); + + $squeezePageUser = SqueezePageUser::create([ + 'firstname' => $validatedData['firstname'], + 'lastname' => $validatedData['lastname'], + 'email' => $validatedData['email'], + 'title' => $validatedData['title'], + ]); + + return response()->json([ + 'message' => 'User details successfully added to squeeze_pages_user.', + 'data' => $squeezePageUser, + 'status_code' => 201, + ], 201); + } + + public function index(Request $request) + { + if (auth()->user()->role !== 'admin') { + return response()->json([ + 'success' => false, + 'message' => 'Only admin users can get users.', + ], Response::HTTP_FORBIDDEN); + } + + $request->validate([ + 'page' => 'integer|min:1', + 'limit' => 'integer|min:1', + ]); + + $page = $request->input('page', 1); + $limit = $request->input('limit', 10); + + $offset = ($page - 1) * $limit; + + $squeezePageUsers = SqueezePageUser::select('firstname', 'lastname', 'email', 'title', 'created_at') + ->offset($offset) + ->limit($limit) + ->get(); + + $totalItems = SqueezePageUser::count(); + $totalPages = ceil($totalItems / $limit); + + return response()->json([ + 'message' => 'User details retrieved successfully.', + 'data' => $squeezePageUsers, + 'pagination' => [ + 'totalItems' => $totalItems, + 'totalPages' => $totalPages, + 'currentPage' => $page, + ], + 'status_code' => 200, + ], 200); + } +} diff --git a/app/Http/Requests/CreateProductRequest.php b/app/Http/Requests/CreateProductRequest.php index 461a08f3..3b798e81 100755 --- a/app/Http/Requests/CreateProductRequest.php +++ b/app/Http/Requests/CreateProductRequest.php @@ -22,12 +22,12 @@ public function authorize(): bool public function rules(): array { return [ - 'title' => 'required|string|max:255', + 'name' => 'required|string|max:255', 'description' => 'required|string', - 'category' => 'required|uuid|exists:categories,id', + 'category' => 'required|string', 'price' => 'required|numeric', - 'stock' => 'required|integer', - 'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:1024', + 'quantity' => 'required|integer', + 'image_url' => 'required|image|mimes:jpeg,png,jpg,gif|max:1024', ]; } } diff --git a/app/Models/Size.php b/app/Models/Size.php index 0935f3a7..62394cae 100644 --- a/app/Models/Size.php +++ b/app/Models/Size.php @@ -22,7 +22,7 @@ public function ProductVariant() return $this->belongsTo(ProductVariant::class); } - public function productVariantsSize(): HasMany + public function productVariantsSize() { return $this->hasMany(ProductVariantSize::class); } diff --git a/app/Models/SqueezePage.php b/app/Models/SqueezePage.php index 6b84da3f..e4db4e76 100644 --- a/app/Models/SqueezePage.php +++ b/app/Models/SqueezePage.php @@ -21,6 +21,4 @@ class SqueezePage extends Model 'hero_image', 'content', ]; - - } diff --git a/app/Models/SqueezePageUser.php b/app/Models/SqueezePageUser.php new file mode 100644 index 00000000..c9fed192 --- /dev/null +++ b/app/Models/SqueezePageUser.php @@ -0,0 +1,19 @@ + + */ +class SqueezePageUserFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + protected $model = SqueezePageUser::class; + + public function definition(): array + { + return [ + 'firstname' => $this->faker->firstName, + 'lastname' => $this->faker->lastName, + 'email' => $this->faker->safeEmail, + 'title' => $this->faker->sentence(3), + ]; + } +} diff --git a/database/migrations/2024_08_08_155430_create_squeeze_pages_user_table.php b/database/migrations/2024_08_08_155430_create_squeeze_pages_user_table.php new file mode 100644 index 00000000..96a4ea1c --- /dev/null +++ b/database/migrations/2024_08_08_155430_create_squeeze_pages_user_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('firstname'); + $table->string('lastname'); + $table->string('email'); + $table->string('title'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('squeeze_pages_user'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 42c36070..dbf3173b 100755 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -60,6 +60,7 @@ public function run(): void $this->call([ArticlesTableSeeder::class]); $this->call(UserJobSeeder::class); $this->call(BlogSeeder::class); + $this->call(SqueezePageUserSeeder::class); UserSubscription::factory()->create(); diff --git a/database/seeders/SqueezePageSeeder.php b/database/seeders/SqueezePageSeeder.php index 69ad4411..a36ee599 100644 --- a/database/seeders/SqueezePageSeeder.php +++ b/database/seeders/SqueezePageSeeder.php @@ -6,13 +6,16 @@ use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; + class SqueezePageSeeder extends Seeder { /** * Run the database seeds. */ + public function run(): void { + $squeezePages = [ [ 'title' => 'Digital Marketing', diff --git a/database/seeders/SqueezePageUserSeeder.php b/database/seeders/SqueezePageUserSeeder.php new file mode 100644 index 00000000..b5053093 --- /dev/null +++ b/database/seeders/SqueezePageUserSeeder.php @@ -0,0 +1,18 @@ +count(15)->create(); + } +} diff --git a/public/uploads/1723194360.jpg b/public/uploads/1723194360.jpg new file mode 100644 index 00000000..b0079762 Binary files /dev/null and b/public/uploads/1723194360.jpg differ diff --git a/resources/docs/documentation.json b/resources/docs/documentation.json index 2fd7f173..87764d94 100644 --- a/resources/docs/documentation.json +++ b/resources/docs/documentation.json @@ -5408,20 +5408,6 @@ } } }, - "Organisation": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - } - }, "OrganisationInput": { "required": [ "name" diff --git a/routes/api.php b/routes/api.php index 8cbbd0d1..fe1471ab 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\SqueezePageUserController; use App\Http\Controllers\Api\V1\NewsletterSubscriptionController; /* @@ -156,6 +157,9 @@ Route::post('/products', [SuperAdminProductController::class, 'store']); Route::patch('/products/{productId}', [SuperAdminProductController::class, 'update']); Route::delete('/products/{productId}', [SuperAdminProductController::class, 'destroy']); + + + Route::get('/squeeze-pages-users', [SqueezePageUserController::class, 'index']); }); Route::middleware(['auth:api', 'admin'])->group(function () { @@ -313,6 +317,9 @@ // quest Route::get('/quests/{id}/messages', [QuestController::class, 'getQuestMessages']); + Route::post('/squeeze-user', [SqueezePageUserController::class, 'store']); + + //Newsletter Subscription Route::post('newsletter-subscription', [NewsletterSubscriptionController::class, 'store']); -}); +}); \ No newline at end of file diff --git a/tests/Feature/OrganisationDeletionTest.php b/tests/Feature/OrganisationDeletionTest.php index 20c6c48c..e7536785 100755 --- a/tests/Feature/OrganisationDeletionTest.php +++ b/tests/Feature/OrganisationDeletionTest.php @@ -1,10 +1,11 @@ -create(); + public function it_requires_authentication(){ + $org = Organisation::factory()->create(); - $response = $this->deleteJson('/api/v1/organisations/'. $org->org_id); + $response = $this->deleteJson('/api/v1/organisations/'. $org->org_id); - $response->assertStatus(401); -} + $response->assertStatus(401); + } /** @test */ - public function it_requires_the_user_to_be_the_owner(){ - $user = User::factory()->create(); - $owner = User::factory()->create(); + public function it_requires_the_user_to_be_the_owner(){ + $user = User::factory()->create(); + $owner = User::factory()->create(); - $org = Organisation::factory()->create(['user_id' => $owner->id]); + $org = Organisation::factory()->create(['user_id' => $owner->id]); - $token = JWTAuth::fromUser($user); - $response = $this->withHeaders(['Authorization' => "Bearer $token"]) - ->deleteJson('/api/v1/organisations/' . $org->org_id); + $token = JWTAuth::fromUser($user); + $response = $this->withHeaders(['Authorization' => "Bearer $token"]) + ->deleteJson('/api/v1/organisations/' . $org->org_id); - $response->assertStatus(401); -} + $response->assertStatus(401); + } /** @test */ public function it_marks_the_organisation_as_deleted() @@ -52,4 +53,23 @@ public function it_marks_the_organisation_as_deleted() $this->assertSoftDeleted('organisations', ['org_id' => $org->org_id]); } -} \ No newline at end of file + + /** @test */ + public function cannot_delete_nonexistent_organisation() + { + $user = User::factory()->create(); + $org = Organisation::factory()->create(); + $org->users()->attach($user->id); + + $token = JWTAuth::fromUser($user); + $uuid = Str::uuid(); + $response = $this->withHeaders(['Authorization' => "Bearer $token"]) + ->deleteJson('/api/v1/organisations/' . $uuid); + + $response->assertStatus(Response::HTTP_NOT_FOUND); + + $this->assertDatabaseMissing('organisations', [ + 'org_id' => $uuid, + ]); + } +} diff --git a/tests/Feature/OrganisationRemoveUserTest.php b/tests/Feature/OrganisationRemoveUserTest.php index 197634b2..0f89b887 100755 --- a/tests/Feature/OrganisationRemoveUserTest.php +++ b/tests/Feature/OrganisationRemoveUserTest.php @@ -10,6 +10,7 @@ use App\Models\Organisation; use App\Models\Role; use Tymon\JWTAuth\Facades\JWTAuth; +use Illuminate\Support\Str; class OrganisationRemoveUserTest extends TestCase { @@ -19,7 +20,10 @@ class OrganisationRemoveUserTest extends TestCase /** @test */ public function it_test_unauthenticated_user_cannot_remove_user() { - $response = $this->delete('/api/v1/organisations/44572ad3-2efe-463c-9531-8b879ef2bfa5/users/44572ad3-2efe-463c-9531-8b879ef2bfa5', [], [ + $uuid1 = Str::uuid(); + $uuid2 = Str::uuid(); + + $response = $this->delete('/api/v1/organisations/' . $uuid1 . '/users/'. $uuid2, [], [ 'accept' => 'application/json', ]); $response->assertStatus(401) diff --git a/tests/Feature/OrganisationTest.php b/tests/Feature/OrganisationTest.php index 57d1488e..ed54ce95 100755 --- a/tests/Feature/OrganisationTest.php +++ b/tests/Feature/OrganisationTest.php @@ -9,6 +9,7 @@ //use Laravel\Passport\Passport; use Tymon\JWTAuth\Facades\JWTAuth; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\Response; class OrganisationTest extends TestCase { @@ -41,77 +42,87 @@ public function test_user_create_organisation() } public function test_authenticated_user_can_retrieve_organisations() -{ - $user = User::factory()->create(); - $token = JWTAuth::fromUser($user); + { + $user = User::factory()->create(); + $token = JWTAuth::fromUser($user); + + $response = $this->withHeader('Authorization', "Bearer $token") + ->getJson("/api/v1/{$user->id}/organisations"); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'status', + 'message', + 'status_code', + 'data' => [ + 'organisations' => [ + '*' => [ + 'org_id', + 'name', + 'email', + 'description', + 'industry', + 'type', + 'country', + 'address', + 'state', + 'created_at', + 'updated_at', + ] + ] + ] + ]); + } - $response = $this->withHeader('Authorization', "Bearer $token") - ->getJson("/api/v1/{$user->id}/organisations"); + public function test_unauthenticated_user_cant_retrieve_organisations() + { + $user = User::factory()->create(); + + $response = $this->getJson("/api/v1/{$user->id}/organisations"); - $response->assertStatus(200) - ->assertJsonStructure([ - 'status', + $response->assertStatus(Response::HTTP_UNAUTHORIZED)->assertJsonStructure([ 'message', - 'status_code', - 'data' => [ - 'organisations' => [ - '*' => [ - 'org_id', - 'name', - 'email', - 'description', - 'industry', - 'type', - 'country', - 'address', - 'state', - 'created_at', - 'updated_at', - ] - ] - ] ]); -} + } + public function test_authenticated_user_with_no_organisations() + { + $user = User::factory()->create(); -public function test_authenticated_user_with_no_organisations() -{ - $user = User::factory()->create(); - - $token = JWTAuth::fromUser($user); - $response = $this->withHeader('Authorization', "Bearer $token") - ->getJson("/api/v1/{$user->id}/organisations"); - - $response->assertStatus(200) - ->assertJson([ - 'status' => 'success', - 'message' => 'No organisations available', - 'data' => [ - 'organisations' => [] - ] - ]); -} + $token = JWTAuth::fromUser($user); + $response = $this->withHeader('Authorization', "Bearer $token") + ->getJson("/api/v1/{$user->id}/organisations"); -public function test_user_cannot_access_another_users_organisations() -{ - $user1 = User::factory()->create(['password' => bcrypt('password')]); - $user2 = User::factory()->create(['password' => bcrypt('password')]); + $response->assertStatus(200) + ->assertJson([ + 'status' => 'success', + 'message' => 'No organisations available', + 'data' => [ + 'organisations' => [] + ] + ]); + } - $organisation = Organisation::factory()->create(); - $user2->organisations()->attach($organisation); + public function test_user_cannot_access_another_users_organisations() + { + $user1 = User::factory()->create(['password' => bcrypt('password')]); + $user2 = User::factory()->create(['password' => bcrypt('password')]); - $token = JWTAuth::attempt(['email' => $user1->email, 'password' => 'password']); - $response = $this->withHeader('Authorization', "Bearer $token") - ->getJson("/api/v1/{$user2->id}/organisations"); // Accessing user2's organisations + $organisation = Organisation::factory()->create(); + $user2->organisations()->attach($organisation); - $response->assertStatus(403) - ->assertJson([ - 'status' => 'error', - 'message' => 'Forbidden', - 'status_code' => '403', + $token = JWTAuth::attempt(['email' => $user1->email, 'password' => 'password']); + $response = $this->withHeader('Authorization', "Bearer $token") + ->getJson("/api/v1/{$user2->id}/organisations"); // Accessing user2's organisations - ]); -} + $response->assertStatus(403) + ->assertJson([ + 'status' => 'error', + 'message' => 'Forbidden', + 'status_code' => '403', + + ]); + } } diff --git a/tests/Feature/ProductCreateTest.php b/tests/Feature/ProductCreateTest.php index d57b08bf..96033f0a 100644 --- a/tests/Feature/ProductCreateTest.php +++ b/tests/Feature/ProductCreateTest.php @@ -45,12 +45,12 @@ public function it_can_create_a_product() $image = UploadedFile::fake()->image('test_image.jpg'); $payload = [ - 'title' => 'Test Product', + 'name' => 'Test Product', 'description' => 'Test Description', - 'category' => $category->id, // Use the created category ID + 'category' => 'Hello', // Use the created category ID 'price' => 100, - 'stock' => 10, - 'image' => $image, + 'quantity' => 10, + 'image_url' => $image, ]; // Send POST request to create product @@ -77,16 +77,12 @@ public function it_can_create_a_product() $this->assertDatabaseHas('products', [ 'name' => 'Test Product', 'description' => 'Test Description', - 'tags' => $category->id, + 'tags' => 'Hello', 'price' => 100, 'user_id' => $user->id ]); - // Assert the category_product relationship is created - $this->assertDatabaseHas('category_product', [ - 'category_id' => $category->id, - 'product_id' => Product::first()->product_id - ]); + // Assert the product variant is created with the correct size $this->assertDatabaseHas('product_variants', [ @@ -116,7 +112,7 @@ public function it_cannot_create_a_product_if_not_an_owner() $size = Size::create(['size' => 'standard']); // Create a category - $category = Category::create(['name' => 'Test Category', 'slug' => 'test-category', 'description' => 'Testing']); + // $category = Category::create(['name' => 'Test Category', 'slug' => 'test-category', 'description' => 'Testing']); // Create an organisation and get the first instance $organisation = Organisation::factory()->create(); @@ -126,12 +122,12 @@ public function it_cannot_create_a_product_if_not_an_owner() $image = UploadedFile::fake()->image('test_image.jpg'); $payload = [ - 'title' => 'Unauthorized Product', + 'name' => 'Unauthorized Product', 'description' => 'Unauthorized Description', - 'category' => $category->id, // Use the created category ID + 'category' => 'hello', // Use the created category ID 'price' => 100, - 'stock' => 10, - 'image' => $image, + 'quantity' => 10, + 'image_url' => $image, ]; // Send POST request to create product diff --git a/tests/Feature/SqueezePageUserControllerTest.php b/tests/Feature/SqueezePageUserControllerTest.php new file mode 100644 index 00000000..8c9edc7b --- /dev/null +++ b/tests/Feature/SqueezePageUserControllerTest.php @@ -0,0 +1,83 @@ +adminUser = User::factory()->create(['role' => 'admin']); + $this->regularUser = User::factory()->create(['role' => 'user']); + $this->adminToken = JWTAuth::fromUser($this->adminUser); + $this->regularToken = JWTAuth::fromUser($this->regularUser); + } + + /** @test */ + public function it_can_store_user_details() + { + $data = [ + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'john.doe@example.com', + 'title' => 'Mr.', + ]; + + $response = $this->postJson('/api/v1/squeeze-user', $data); + + $response->assertStatus(201) + ->assertJson([ + 'message' => 'User details successfully added to squeeze_pages_user.', + 'data' => [ + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'john.doe@example.com', + 'title' => 'Mr.', + ], + 'status_code' => 201, + ]); + + $this->assertDatabaseHas('squeeze_pages_user', $data); + } + + /** @test */ + public function it_can_fetch_user_details_with_pagination() + { + SqueezePageUser::factory()->count(15)->create(); + + $response = $this->withHeaders(['Authorization' => "Bearer $this->adminToken"]) + ->getJson('/api/v1/squeeze-pages-users?page=1&limit=10'); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data' => [ + '*' => ['firstname', 'lastname', 'email', 'title', 'created_at'], + ], + 'pagination' => [ + 'totalItems', + 'totalPages', + 'currentPage', + ], + 'status_code', + ]); + + + $response = $this->withHeaders(['Authorization' => "Bearer $this->adminToken"]) + ->getJson('/api/v1/squeeze-pages-users?page=2&limit=10'); + + } +}