Compare commits

...

59 Commits

Author SHA1 Message Date
kusowl
0d2ba32fc6 refactor: update controller namespaces 2026-03-26 10:39:28 +05:30
kusowl
79f012e6d4 WIP: initial model, request and migration setup for Coupons 2026-03-25 18:40:34 +05:30
kusowl
5d6f75bdc2 fix: refactor code to group into folder 2026-03-25 17:17:35 +05:30
kusowl
ca0aaaa84b feature: implement payment verification, make cart as converted after successful payment 2026-03-25 15:25:11 +05:30
kusowl
8e1fe1336e chore: format, add LaraDumps and ArchTests 2026-03-24 18:52:49 +05:30
kusowl
f5927b8d08 feature: implement payment method selector and checkout confirmation page 2026-03-24 18:52:06 +05:30
kusowl
14cb5a36ae feature: implement stripe webhook 2026-03-24 18:50:24 +05:30
kusowl
a51a2cd436 chore: ide helpers and formatting 2026-03-23 17:29:55 +05:30
kusowl
2aa76db042 feature: implement payment gateway
- implement stripe checkout gateway
- add payment gateway factory and service
2026-03-23 17:29:24 +05:30
kusowl
0799965212 wip: stripe implementation
- add model, dto
2026-03-20 19:03:33 +05:30
kusowl
783ae6925b Refactor: update address when order creation request is being sent with different address id. 2026-03-19 16:38:34 +05:30
kusowl
d065ef1db9 feature: order create endpoint 2026-03-18 18:59:45 +05:30
kusowl
a0e5cda432 Merge branch 'feature/address-ui' into staging 2026-03-17 18:22:42 +05:30
kusowl
11115c8dc0 Merge branch 'feature/address-api' into staging 2026-03-17 18:22:35 +05:30
kusowl
bb3aafd89e feature: show order summary on address page 2026-03-17 17:05:49 +05:30
kusowl
419e8281e2 BREAKING CHANGE: change obervable name from cartItem$ to cartItems$ 2026-03-17 16:10:05 +05:30
kusowl
63b3d06d3a BREAKING CHANGE: remove wrapping for individual address resource 2026-03-17 11:00:32 +05:30
kusowl
24bdfe9cc6 feature: fetch, edit and add address
- fetch existing addresses from api,
- user can edit existing address
- user can add new address
2026-03-17 10:58:22 +05:30
kusowl
3059a923b4 refactor: move auth service to core 2026-03-16 13:05:06 +05:30
kusowl
6d1cb81e6b wip: checkout page
- add button to go address page in cart ui
- add template for address page
2026-03-13 18:54:48 +05:30
kusowl
3ae3374eec chore: formatting
- updated format via pint
2026-03-13 18:09:06 +05:30
kusowl
c27ae1969f feature: add address api
- users can save, edi and delete addresses
- each user can have multiple address
- used shallow routes for address
2026-03-13 18:08:45 +05:30
kusowl
61ecbec994 chore: add command to generate dto
- add make:dto command which generates dto in App\Data namespace
- varient: --input (default), --output
2026-03-13 11:28:22 +05:30
kusowl
03f044b8d3 chore: add ide helper
- add laravel ide helper package for better LSP support
- update dependencies
2026-03-13 10:08:55 +05:30
kusowl
136d6cf97e Merge branch 'backend' into staging 2026-03-12 10:47:06 +05:30
kusowl
3b5bf80f39 Merge branch 'frontend' into staging 2026-03-12 10:45:49 +05:30
kusowl
50c956c051 fix: make header logo navigation by router 2026-03-12 10:43:38 +05:30
kusowl
ad957efcf0 feature: add to cart
- make the cart service dependable on BehavorialSubject, migrated from
siganls
- implement add to cart service
2026-03-11 19:00:24 +05:30
kusowl
27a04c6458 minor: some quick design and color changes
make the dropdown hover color from gradient to simple gray shade
make the button 3d
change the product card design in home page
add add to cart button in the home page
change design of button ghost
2026-03-11 11:25:07 +05:30
kusowl
2b88cee10b feature: user can change quantity and remove products 2026-03-10 19:07:42 +05:30
kusowl
5bbec0ee2b feature: update quantity and remove product from cart
add endpoint for update quantity of products (min:1, max:10)
add endpoint for removing product from cart
2026-03-10 19:01:52 +05:30
kusowl
1656739ecd feature: add and fetch products in cart api
- schema for cart and product
- define relationship, DTO, Resource and API collections
- Add post and get endpoint for cart api
2026-03-09 19:07:27 +05:30
kusowl
9000ea0052 feature: fetch cart products from api
- show total cart item count on header
- fetch and show cart items on cart modal
2026-03-09 19:04:31 +05:30
kusowl
0faccba476 Merge branch 'frontend' into staging 2026-03-05 18:28:08 +05:30
kusowl
3c2233d53e fix: make favorite button in product show page sync with db 2026-03-05 18:08:52 +05:30
kusowl
95afd46406 fix: add isFavorite on Product show response 2026-03-05 18:07:46 +05:30
kusowl
0f56303d59 fix sanctum and session environment variables 2026-03-05 14:43:21 +05:30
kusowl
a4eebef321 Merge branch 'feature/products' into staging 2026-03-05 13:48:30 +05:30
kusowl
a57566c1fe Merge branch 'backend' into staging 2026-03-05 13:48:24 +05:30
kusowl
7e1ecf35b9 make favorite state persistant with api 2026-03-05 13:34:37 +05:30
kusowl
ae008fbc9c chore: add isFavorite in produtcs response
- refactor code to use query
- add active column in products
2026-03-05 13:32:49 +05:30
kusowl
8ef4383bd9 feature: endpoint to favorite product 2026-03-05 10:32:40 +05:30
kusowl
b575b42f22 Merge remote-tracking branch 'origin/feature/products' into fix/history-issue
# Conflicts:
#	src/app/features/product/components/product-card/product-card.html
#	src/app/features/product/components/product-card/product-card.ts
#	src/app/features/product/services/product-service.ts
2026-03-03 17:40:12 +05:30
kusowl
553637d8e2 fix: commit whole changes 2026-03-03 17:27:30 +05:30
kusowl
f5393f5110 feature: show products on the home page and add individual product page 2026-03-02 18:47:43 +05:30
kusowl
068975d3b0 Merge branch 'backend' 2026-02-27 18:22:36 +05:30
kusowl
a34bea34d4 Merge branch 'feature/add-product' 2026-02-27 18:17:35 +05:30
kusowl
8b1b831ea2 feature: authorization - add role guard and protect products route 2026-02-27 13:22:02 +05:30
kusowl
617053c0ee feature: upload images and show alert after successfull product creation 2026-02-26 19:02:39 +05:30
kusowl
bb05fb7747 feature: add product page
- add UI
- add dialog for preview selected images
2026-02-25 19:04:00 +05:30
kusowl
03525280db Merge branch 'feature/main/login' 2026-02-24 18:49:45 +05:30
kusowl
6e2fd45803 Merge branch 'backend' 2026-02-24 18:49:33 +05:30
kusowl
4a4c8bd4e3 feature: user logout and auth states
added s authState which helps conditonaly render components based on this state

stored user details in localStoarge so that server side end point does not get hit in every page load.

add a guard which protects routes and redirects to login if user is not logged in.

create a logout route
2026-02-24 18:14:21 +05:30
kusowl
043d54bcd0 user can login via frontend 2026-02-23 18:53:54 +05:30
kusowl
0427d1c62d feature: user can login to backend 2026-02-23 18:46:23 +05:30
kusowl
aee7e4fd89 navigate user after successful registration.
- data is passed via state
2026-02-23 16:10:59 +05:30
kusowl
4aba99fcb5 chore: format 2026-02-23 15:08:06 +05:30
kusowl
78bf326622 add client side validation errors 2026-02-23 15:07:43 +05:30
kusowl
77532aaac2 Show server side validation errors 2026-02-23 12:17:14 +05:30
263 changed files with 37967 additions and 505 deletions

12
.ai/mcp/mcp.json Normal file
View File

@ -0,0 +1,12 @@
{
"mcpServers": {
"angular-cli": {
"command": "npx",
"args": [
"-y",
"@angular/cli",
"mcp"
]
}
}
}

2
.gitignore vendored
View File

@ -38,6 +38,8 @@ yarn-error.log
testem.log testem.log
/typings /typings
__screenshots__/ __screenshots__/
*.cache
.php-cs-fixer.dist.php
# System files # System files
.DS_Store .DS_Store

3
.oxfmtrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"ignorePatterns": ["backend/**", "*.min.js"]
}

3
.phpactor.json Normal file
View File

@ -0,0 +1,3 @@
{
"indexer.exclude_patterns": ["/node_modules/**/*", "/backend/**/*"]
}

1
.rgignore Normal file
View File

@ -0,0 +1 @@
backend/

View File

@ -2,7 +2,8 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1, "version": 1,
"cli": { "cli": {
"packageManager": "npm" "packageManager": "npm",
"analytics": false
}, },
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {

View File

@ -31,7 +31,7 @@ SESSION_DRIVER=database
SESSION_LIFETIME=120 SESSION_LIFETIME=120
SESSION_ENCRYPT=false SESSION_ENCRYPT=false
SESSION_PATH=/ SESSION_PATH=/
SESSION_DOMAIN=null SESSION_DOMAIN=localhost
BROADCAST_CONNECTION=log BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local FILESYSTEM_DISK=local
@ -64,3 +64,7 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}"
FRONTEND_URL=http://localhost:4200 FRONTEND_URL=http://localhost:4200
SANCTUM_STATEFUL_DOMAINS=localhost:4200
STRIPE_SECRET_KEY=sk_test_51TCvFrJG0RVtUg4VTqHZC2szosam9Mf0Nq0Sh71tdIKdld5DnOUhUl4VvFBZVRWPM9G5hLPNVmH8YNXqm2R6fR5U00fsEsLb1d
STRIPE_WEBHOOK_KEY=

1
backend/.gitignore vendored
View File

@ -22,3 +22,4 @@
Homestead.json Homestead.json
Homestead.yaml Homestead.yaml
Thumbs.db Thumbs.db
laradumps.yaml

2355
backend/.phpstorm.meta.php Normal file

File diff suppressed because it is too large Load Diff

28657
backend/_ide_helper.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,307 @@
<?php
// @formatter:off
// phpcs:ignoreFile
/**
* A helper file for your Eloquent Models
* Copy the phpDocs from this file to the correct Model,
* And remove them from this file, to prevent double declarations.
*
* @author Barry vd. Heuvel <barryvdh@gmail.com>
*/
namespace App\Models{
/**
* @property int $id
* @property string $first_name
* @property string $last_name
* @property string $street
* @property string $city
* @property string $state
* @property string $pin
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read int|null $users_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereCity($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereFirstName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereLastName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address wherePin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereState($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereStreet($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperAddress {}
}
namespace App\Models{
/**
* @property int $id
* @property int $user_id
* @property \App\Enums\Cart\CartStatus $status
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Order|null $order
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Product> $products
* @property-read int|null $products_count
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart active()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Cart withProducts()
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperCart {}
}
namespace App\Models{
/**
* @property int $id
* @property int $user_id
* @property int $cart_id
* @property string $status
* @property string $shipping_first_name
* @property string $shipping_last_name
* @property string $shipping_street
* @property string $shipping_city
* @property string $shipping_state
* @property string $shipping_pin
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Cart $cart
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read int|null $payments_count
* @property-read \App\Models\StripeSession|null $stripeSession
* @property-read mixed $total_amount
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereCartId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereShippingCity($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereShippingFirstName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereShippingLastName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereShippingPin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereShippingState($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereShippingStreet($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Order whereUserId($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperOrder {}
}
namespace App\Models{
/**
* @property int $id
* @property int $order_id
* @property string $transaction_id
* @property int $amount
* @property string $currency
* @property string $payment_method
* @property int $payment_status_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Order|null $order
* @property-read \App\Models\PaymentStatus|null $paymentStatus
* @property-read mixed $status
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereAmount($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereCurrency($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereOrderId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment wherePaymentMethod($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment wherePaymentStatusId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereTransactionId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperPayment {}
}
namespace App\Models{
/**
* @property int $id
* @property string $name
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentStatus newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentStatus newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentStatus query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentStatus whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentStatus whereName($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperPaymentStatus {}
}
namespace App\Models{
/**
* @property int $id
* @property string $title
* @property string|null $slug
* @property string $description
* @property numeric $actual_price
* @property numeric $list_price
* @property int $product_category_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property bool $is_active
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Cart> $carts
* @property-read int|null $carts_count
* @property-read \App\Models\ProductCategory|null $category
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $favoritedBy
* @property-read int|null $favorited_by_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProductImage> $images
* @property-read int|null $images_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product active()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereActualPrice($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereDescription($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereIsActive($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereListPrice($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereProductCategoryId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereSlug($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereTitle($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperProduct {}
}
namespace App\Models{
/**
* @property int $id
* @property string $name
* @property string $slug
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory whereSlug($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductCategory whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperProductCategory {}
}
namespace App\Models{
/**
* @property int $id
* @property string $path
* @property int $product_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Product|null $product
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage wherePath($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage whereProductId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ProductImage whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperProductImage {}
}
namespace App\Models{
/**
* @property int $id
* @property string $session_id
* @property int $order_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Order|null $order
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession whereOrderId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession whereSessionId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StripeSession whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperStripeSession {}
}
namespace App\Models{
/**
* @property int $id
* @property string $name
* @property string $email
* @property \Illuminate\Support\Carbon|null $email_verified_at
* @property string $password
* @property string|null $remember_token
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property string $mobile_number
* @property string $city
* @property \App\Enums\User\UserRoles $role
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Address> $addresses
* @property-read int|null $addresses_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Cart> $carts
* @property-read int|null $carts_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Product> $favoriteProducts
* @property-read int|null $favorite_products_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Order> $orders
* @property-read int|null $orders_count
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCity($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereMobileNumber($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRole($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
* @mixin \Eloquent
*/
#[\AllowDynamicProperties]
class IdeHelperUser {}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Actions\Address;
use App\Models\Address;
final readonly class DeleteUserAddressAction
{
/**
* Execute the action.
*/
public function execute(Address $address)
{
$address->users()->detach();
$address->delete();
return response()->noContent();
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Actions\Address;
use App\Data\Address\AddUserAddressRequestDTO;
use App\Data\Address\UserAddressResponseDTO;
use App\Models\User;
final readonly class SaveUserAddressAction
{
/**
* Execute the action.
*/
public function execute(AddUserAddressRequestDTO $data, User $user)
{
return UserAddressResponseDTO::fromModel(
$user
->addresses()
->create($data->toArray())
);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Actions\Address;
use App\Data\Address\UpdateUserAddressRequestDTO;
use App\Data\Address\UserAddressResponseDTO;
use App\Models\Address;
final readonly class UpdateUserAddressAction
{
/**
* Execute the action.
*/
public function execute(UpdateUserAddressRequestDTO $data, Address $address)
{
$address->update($data->toArray());
$address->refresh();
return UserAddressResponseDTO::fromModel($address);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Actions\Cart;
use App\Data\Cart\AddToCartDTO;
use App\Data\Cart\CartDTO;
use App\Enums\Cart\CartStatus;
use App\Models\Product;
use App\Models\User;
final readonly class AddProductToCartAction
{
/**
* Execute the action.
*/
public function execute(AddToCartDTO $cartData, User $user)
{
$cart = $user->carts()->active()->firstOrCreate([
'status' => CartStatus::Active,
]);
$price = Product::query()->whereId($cartData->productId)->value('actual_price');
$productInCart = $cart->products()->find($cartData->productId);
if ($productInCart) {
$newQuantity = $productInCart->pivot->quantity + $cartData->quantity;
$cart->products()->updateExistingPivot($cartData->productId, [
'quantity' => $newQuantity,
'price' => $price,
]);
} else {
$cart->products()->attach($cartData->productId, [
'quantity' => $cartData->quantity,
'price' => $price,
]);
}
return CartDTO::fromModel($cart->load(['products' => function ($query) {
$query->withPivot('quantity', 'price');
}]));
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Actions\Cart;
use App\Data\Cart\CartDTO;
use App\Models\User;
final readonly class GetActiveUserCartAction
{
/**
* Execute the action.
*/
public function execute(User $user)
{
return CartDTO::fromModel($user->carts()->active()->withProducts()->first());
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Actions\Cart;
use App\Enums\Cart\CartStatus;
use App\Models\Cart;
final readonly class MarkCartAsConvertedAction
{
/**
* Execute the action.
*/
public function execute(Cart $cart): Cart
{
$cart->status = CartStatus::Converted;
$cart->save();
return $cart;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Actions\Cart;
use App\Models\User;
final readonly class RemoveProductFromCartAction
{
public function __construct(private GetActiveUserCartAction $activeCartAction) {}
/**
* Execute the action.
*/
public function execute(int $productId, User $user)
{
$cart = $user->carts()->active()->sole();
$cart->products()->detach($productId);
return $this->activeCartAction->execute($user);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Actions\Cart;
use App\Data\Cart\AddToCartDTO;
use App\Data\Cart\CartDTO;
use App\Models\User;
use InvalidArgumentException;
final readonly class UpdateProductInCartAction
{
/**
* Execute the action.
*
* @throws InvalidArgumentException
*/
public function execute(AddToCartDTO $cartData, User $user)
{
$cart = $user->carts()->active()->sole();
$productInCart = $cart->products()->find($cartData->productId);
throw_if($productInCart === null, new InvalidArgumentException('Product not found'));
$cart->products()->updateExistingPivot($cartData->productId, [
'quantity' => $cartData->quantity,
]);
return CartDTO::fromModel($cart->load(['products' => function ($query) {
$query->withPivot('quantity', 'price');
}]));
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Actions\Order;
use App\Data\Order\OrderRequestDTO;
use App\Enums\Cart\CartStatus;
use App\Exceptions\StaleCartException;
use App\Models\Address;
use App\Models\Cart;
use App\Models\Order;
use App\Models\User;
final readonly class CreateOrderAction
{
/**
* Execute the action.
*
* @throws StaleCartException
*/
public function execute(OrderRequestDTO $dto, User $user): Order
{
/** @var Cart $cart */
$cart = $user->carts()->where('id', $dto->cartId)->sole();
if ($cart->status !== CartStatus::Active) {
throw new StaleCartException(userId: $user->id, cartId: $cart->id);
}
/** @var Address $address */
$address = $user->addresses()->where('id', $dto->addressId)->sole();
// Check if user has already created an order with the same cart. If yes, then take that order.
/** @var Order $order */
$order = $user->orders()->firstOrNew(['cart_id' => $cart->id]);
$order->cart_id = $cart->id;
$order->shipping_first_name = $address->first_name;
$order->shipping_last_name = $address->last_name;
$order->shipping_street = $address->street;
$order->shipping_city = $address->city;
$order->shipping_state = $address->state;
$order->shipping_pin = $address->pin;
$order->save();
return $order;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Actions\Payment;
use App\Actions\Cart\MarkCartAsConvertedAction;
use App\Enums\Payment\PaymentStatusEnum;
use App\Models\Payment;
use App\Models\PaymentStatus;
use DB;
use Log;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Throwable;
final readonly class MarkPaymentAsPaidAction
{
public function __construct(private MarkCartAsConvertedAction $cartAsConvertedAction) {}
/**
* Execute the action.
*
* @throws NotFoundResourceException|Throwable
*/
public function execute(Payment $payment): bool
{
try {
DB::beginTransaction();
// get the cart and make the status to converted
$cart = $payment->order->cart;
$this->cartAsConvertedAction->execute($cart);
$status = PaymentStatus::whereName(PaymentStatusEnum::Paid->value)->value('id');
if (! $status) {
throw new NotFoundResourceException('Paid Status not found');
}
$payment->payment_status_id = $status;
$payment->save();
DB::commit();
return true;
} catch (Throwable $e) {
Log::error('Cannot mark order payment as paid', [$e->getMessage()]);
DB::rollBack();
return false;
}
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Actions\Payment;
use App\Data\Payment\PaymentResponseDTO;
use App\Enums\Payment\PaymentModes;
use App\Enums\Payment\PaymentStatusEnum;
use App\Models\Order;
use App\Models\PaymentStatus;
use App\Services\Payment\PaymentGatewayFactory;
use DB;
use Log;
use Throwable;
final readonly class ProcessOrderPaymentAction
{
public function __construct(
private PaymentGatewayFactory $paymentGatewayFactory,
) {}
/**
* Execute the action.
*/
public function execute(Order $order, PaymentModes $mode): PaymentResponseDTO
{
$gateway = $this->paymentGatewayFactory->make($mode);
try {
DB::beginTransaction();
$response = $gateway->charge($order);
if ($response->isSuccess) {
$order->payments()->create(
[
'transaction_id' => $response->transactionId,
'amount' => $response->amount,
'currency' => $response->currency,
'payment_method' => $response->method->value,
'payment_status_id' => PaymentStatus::getIdByName(PaymentStatusEnum::Unpaid->value),
]
);
}
DB::commit();
return $response;
} catch (Throwable $e) {
DB::rollBack();
Log::error('Error occurred while processing the payment.', [$e->getMessage()]);
abort(500, 'Something went wrong. Please try again or contact us to get in touch with our support team. ');
}
}
}

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Actions; namespace App\Actions\Product;
use App\Data\UploadImageDTO; use App\Data\Upload\UploadImageDTO;
final readonly class CreateProductAction final readonly class CreateProductAction
{ {

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Actions; namespace App\Actions\Product;
use App\Data\ProductCategoryDTO; use App\Data\Product\ProductCategoryDTO;
use App\Models\ProductCategory; use App\Models\ProductCategory;
final readonly class GetAllProductCategory final readonly class GetAllProductCategory

View File

@ -0,0 +1,57 @@
<?php
namespace App\Actions\Stripe;
use App\Data\Payment\VerifiedCheckoutResponseDTO;
use App\Models\User;
use Exception;
use Log;
use Stripe\Exception\ApiErrorException;
use Stripe\StripeClient;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
final readonly class VerifyStripeSessionAction
{
public function __construct(private StripeClient $stripe) {}
/**
* Execute the action.
*/
public function execute(int $orderId, string $stripeSessionId, User $user): VerifiedCheckoutResponseDTO
{
/**
* Check if the order is actually made by user
*/
$order = $user->orders()
->whereId($orderId)
->first();
if (! $order) {
throw new AccessDeniedHttpException('Order is not made by you');
}
if (! $order->stripeSession()->where('session_id', $stripeSessionId)->exists()) {
throw new AccessDeniedHttpException('Stripe session is not made by you');
}
try {
$session = $this->stripe->checkout->sessions->retrieve($stripeSessionId);
} catch (ApiErrorException $e) {
Log::error('Stripe api is not available: ', [$e->getMessage()]);
throw new ServiceUnavailableHttpException('Stripe api is not available');
} catch (Exception $e) {
throw new NotFoundHttpException('Invalid Stripe session id');
}
if ($session->payment_status !== 'paid' || $session->status !== 'complete') {
return VerifiedCheckoutResponseDTO::failure('Payment Unsuccessful');
}
return VerifiedCheckoutResponseDTO::success(
message: 'Payment Successful',
amount: $session->amount_total,
transactionId: $session->payment_intent,
mode: $session->payment_method_types[0],
);
}
}

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Actions; namespace App\Actions\Upload;
use App\Data\UploadImageDTO; use App\Data\Upload\UploadImageDTO;
final readonly class UploadImageAction final readonly class UploadImageAction
{ {

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Actions; namespace App\Actions\User;
use App\Data\RegisterDTO; use App\Data\User\RegisterDTO;
use App\Models\User; use App\Models\User;
final readonly class CreateUserAction final readonly class CreateUserAction

View File

@ -0,0 +1,63 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class MakeDtoCommand extends GeneratorCommand
{
/**
* The console command name and signature.
*
* @var string
*/
protected $name = 'make:dto';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Data Transfer Object class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'DTO';
/**
* Get the stub file for the generator.
*/
protected function getStub(): string
{
if ($this->option('output')) {
return base_path('stubs/dto.output.stub');
}
return base_path('stubs/dto.input.stub');
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
*/
protected function getDefaultNamespace($rootNamespace): string
{
return $rootNamespace.'\Data';
}
/**
* Get the console command options.
*/
protected function getOptions(): array
{
return [
['input', 'i', InputOption::VALUE_NONE, 'Generate an Input DTO (default)'],
['output', 'o', InputOption::VALUE_NONE, 'Generate an Output DTO'],
];
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Contracts;
use App\Data\Payment\PaymentResponseDTO;
use App\Models\Order;
interface PaymentGateway
{
public function charge(Order $order): PaymentResponseDTO;
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Data\Address;
use App\Contracts\InputDataTransferObject;
use Illuminate\Foundation\Http\FormRequest;
final readonly class AddUserAddressRequestDTO implements InputDataTransferObject
{
public function __construct(
public string $firstName,
public string $lastName,
public string $street,
public string $city,
public string $state,
public string $pinCode
) {}
public static function fromRequest(FormRequest $request): InputDataTransferObject
{
return new self(
firstName: $request->firstName,
lastName: $request->lastName,
street: $request->street,
city: $request->city,
state: $request->state,
pinCode: $request->pinCode
);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'first_name' => $this->firstName,
'last_name' => $this->lastName,
'street' => $this->street,
'city' => $this->city,
'state' => $this->state,
'pin' => $this->pinCode,
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Data\Address;
use App\Contracts\InputDataTransferObject;
use Illuminate\Foundation\Http\FormRequest;
final readonly class UpdateUserAddressRequestDTO implements InputDataTransferObject
{
public function __construct(
public ?string $firstName = null,
public ?string $lastName = null,
public ?string $street = null,
public ?string $city = null,
public ?string $state = null,
public ?string $pinCode = null,
) {}
public static function fromRequest(FormRequest $request): InputDataTransferObject
{
return new self(
firstName: $request->input('firstName'),
lastName: $request->input('lastName'),
street: $request->input('street'),
city: $request->input('city'),
state: $request->input('state'),
pinCode: $request->input('pinCode'),
);
}
/**
* @return array<string = null = null, mixed>
*/
public function toArray(): array
{
$data = [
'first_name' => $this->firstName,
'last_name' => $this->lastName,
'street' => $this->street,
'city' => $this->city,
'state' => $this->state,
'pin' => $this->pinCode,
];
return array_filter($data, fn ($value) => $value !== null);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Data\Address;
use App\Contracts\OutputDataTransferObject;
use App\Models\Address;
final readonly class UserAddressResponseDTO implements OutputDataTransferObject
{
public function __construct(
public int $id,
public string $firstName,
public string $lastName,
public string $street,
public string $city,
public string $state,
public string $pinCode
) {}
public static function fromModel(Address $address): OutputDataTransferObject
{
return new self(
id: $address->id,
firstName: $address->first_name,
lastName: $address->last_name,
street: $address->street,
city: $address->city,
state: $address->state,
pinCode: $address->pin
);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'id' => $this->id,
'firstName' => $this->firstName,
'lastName' => $this->lastName,
'street' => $this->street,
'city' => $this->city,
'state' => $this->state,
'pinCode' => $this->pinCode,
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Data\Cart;
use App\Contracts\InputDataTransferObject;
use Illuminate\Foundation\Http\FormRequest;
final readonly class AddToCartDTO implements InputDataTransferObject
{
public function __construct(
public int $productId,
public int $quantity
) {}
public static function fromRequest(FormRequest $request): InputDataTransferObject
{
return new self(
productId: $request->productId,
quantity: $request->quantity
);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'productId' => $this->productId,
'quantity' => $this->quantity,
];
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Data\Cart;
use App\Contracts\OutputDataTransferObject;
use App\Models\Cart;
final readonly class CartDTO implements OutputDataTransferObject
{
/**
* @param CartItemDTO[] $items
*/
public function __construct(
public int $id,
public ?int $itemsCount = null,
public ?int $totalPrice = null,
public array $items = []
) {}
public static function fromModel(Cart $cart)
{
return new self(
id: $cart->id,
itemsCount: $cart->products->count(),
totalPrice: $cart->products->sum(fn ($product) => $product->pivot->price * $product->pivot->quantity),
items: $cart->products->map(fn ($product) => new CartItemDTO(
id: $product->id,
title: $product->title,
quantity: $product->pivot->quantity,
price: $product->actual_price,
subtotal: $product->actual_price * $product->pivot->quantity,
image: $product->images->first()->path,
))->toArray()
);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'id' => $this->id,
'itemsCount' => $this->itemsCount,
'totalPrice' => $this->totalPrice,
'items' => $this->items,
];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Data\Cart;
use App\Contracts\OutputDataTransferObject;
final readonly class CartItemDTO implements OutputDataTransferObject
{
public function __construct(
public int $id,
public string $title,
public int $quantity,
public float $price,
public float $subtotal,
public string $image
) {}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'quantity' => $this->quantity,
'price' => $this->price,
'subtotal' => $this->subtotal,
'image' => $this->image,
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Data\Order;
use App\Contracts\OutputDataTransferObject;
use Illuminate\Database\Eloquent\Model;
final readonly class OrderCreatedResponseDTO implements OutputDataTransferObject
{
public function __construct(
// TODO: Define your properties here
) {}
public static function fromModel(Model $model): OutputDataTransferObject
{
return new self;
// TODO: Map model data to properties
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
// TODO: Map properties to array
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Data\Order;
use App\Contracts\InputDataTransferObject;
use Illuminate\Foundation\Http\FormRequest;
final readonly class OrderRequestDTO implements InputDataTransferObject
{
public function __construct(
public int $cartId,
public string $addressId,
) {}
public static function fromRequest(FormRequest $request): OrderRequestDTO
{
return new self(
cartId: $request->cartId,
addressId: $request->addressId,
);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'cart_id' => $this->cartId,
'address_id' => $this->addressId,
];
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Data\Payment;
use App\Contracts\OutputDataTransferObject;
use App\Enums\Payment\PaymentModes;
final readonly class PaymentResponseDTO implements OutputDataTransferObject
{
public function __construct(
public bool $isSuccess,
public int $amount,
public string $currency,
public PaymentModes $method,
public ?string $transactionId = null,
public ?string $errorMessage = null,
public ?string $redirectUrl = null,
) {}
public static function success(
string $transactionId,
int $amount,
string $currency,
PaymentModes $method,
?string $redirectUrl = null
): self {
return new self(
isSuccess: true,
amount: $amount,
currency: $currency,
method: $method,
transactionId: $transactionId,
redirectUrl: $redirectUrl,
);
}
public static function failure(int $amount, string $currency, PaymentModes $method, string $errorMessage): self
{
return new self(
isSuccess: false,
amount: $amount,
currency: $currency,
method: $method,
errorMessage: $errorMessage,
);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'isSuccess' => $this->isSuccess,
'transactionId' => $this->transactionId,
'amount' => $this->amount,
'currency' => $this->currency,
'method' => $this->method,
'redirectUrl' => $this->redirectUrl,
'errorMessage' => $this->errorMessage,
];
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Data\Payment;
use App\Contracts\OutputDataTransferObject;
final readonly class VerifiedCheckoutResponseDTO implements OutputDataTransferObject
{
public function __construct(
public bool $isSuccess,
public string $message,
public ?int $amount = null,
public ?string $transactionId = null,
public ?string $mode = null
) {}
public static function failure(string $message): VerifiedCheckoutResponseDTO
{
return new self(isSuccess: false, message: $message);
}
public static function success(string $message, int $amount, string $transactionId, string $mode): VerifiedCheckoutResponseDTO
{
return new self(isSuccess: true, message: $message, amount: $amount, transactionId: $transactionId,
mode: $mode);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'isSuccess' => $this->isSuccess,
'message' => $this->message,
'amount' => $this->amount,
'transactionId' => $this->transactionId,
'paymentMethod' => $this->mode,
];
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Data; namespace App\Data\Product;
use App\Contracts\OutputDataTransferObject; use App\Contracts\OutputDataTransferObject;
use App\Models\ProductCategory; use App\Models\ProductCategory;
@ -13,6 +13,15 @@ public function __construct(
public string $slug, public string $slug,
) {} ) {}
public static function fromModel(ProductCategory $category): self
{
return new self(
id: $category->id,
name: $category->name,
slug: $category->slug,
);
}
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
@ -24,13 +33,4 @@ public function toArray(): array
'slug' => $this->slug, 'slug' => $this->slug,
]; ];
} }
public static function fromModel(ProductCategory $category): self
{
return new self(
id: $category->id,
name: $category->name,
slug: $category->slug,
);
}
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Data; namespace App\Data\Product;
use App\Contracts\OutputDataTransferObject; use App\Contracts\OutputDataTransferObject;
use App\Models\Product; use App\Models\Product;
@ -22,8 +22,27 @@ public function __construct(
public array $productImages, public array $productImages,
public ?string $updatedAt = null, public ?string $updatedAt = null,
public ?string $createdAt = null, public ?string $createdAt = null,
public ?bool $isFavorite = null
) {} ) {}
public static function fromModel(Product $product): self
{
return new self(
id: $product->id,
title: $product->title,
slug: $product->slug,
description: $product->description,
actualPrice: $product->actual_price,
listPrice: $product->list_price,
category: ProductCategoryDTO::fromModel($product->category),
productImages: $product->images->map(fn (ProductImage $productImage) => ProductImageDTO::fromModel($productImage))->all(),
updatedAt: $product->updated_at,
createdAt: $product->created_at,
// this column is added by where exists query
isFavorite: $product->favorited_by_exists,
);
}
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
@ -41,22 +60,7 @@ public function toArray(): array
$this->productImages), $this->productImages),
'updatedAt' => $this->updatedAt, 'updatedAt' => $this->updatedAt,
'createdAt' => $this->createdAt, 'createdAt' => $this->createdAt,
'isFavorite' => $this->isFavorite,
]; ];
} }
public static function fromModel(Product $product): self
{
return new self(
id: $product->id,
title: $product->title,
slug: $product->slug,
description: $product->description,
actualPrice: $product->actual_price,
listPrice: $product->list_price,
category: ProductCategoryDTO::fromModel($product->category),
productImages: $product->images->map(fn (ProductImage $productImage) => ProductImageDTO::fromModel($productImage))->all(),
updatedAt: $product->updated_at,
createdAt: $product->created_at,
);
}
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Data; namespace App\Data\Product;
use App\Contracts\OutputDataTransferObject; use App\Contracts\OutputDataTransferObject;
use App\Models\ProductImage; use App\Models\ProductImage;
@ -13,6 +13,15 @@ public function __construct(
public ?int $productId = null, public ?int $productId = null,
) {} ) {}
public static function fromModel(ProductImage $productImage): self
{
return new self(
$productImage->id,
$productImage->path,
$productImage->product_id
);
}
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
@ -24,13 +33,4 @@ public function toArray(): array
'productId' => $this->productId, 'productId' => $this->productId,
]; ];
} }
public static function fromModel(ProductImage $productImage): self
{
return new self(
$productImage->id,
$productImage->path,
$productImage->product_id
);
}
} }

View File

@ -0,0 +1,35 @@
<?php
namespace App\Data\Stripe;
use App\Contracts\OutputDataTransferObject;
use App\Enums\Stripe\StripeCurrency;
final readonly class StripeLineItemDTO implements OutputDataTransferObject
{
public function __construct(
public StripeCurrency $currency,
public int $price,
public string $productName,
public string $productDescription,
public int $quantity
) {}
/**
* @return array<string, int|string>
*/
public function toArray(): array
{
return [
'price_data' => [
'currency' => $this->currency->value,
'unit_amount' => $this->price,
'product_data' => [
'name' => $this->productName,
'description' => $this->productDescription,
],
],
'quantity' => $this->quantity,
];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Data\Stripe;
use App\Contracts\OutputDataTransferObject;
use App\Enums\Stripe\StripePaymentMode;
final readonly class StripeSessionDataDTO implements OutputDataTransferObject
{
/**
* @param StripeLineItemDTO[] $lineItems
*/
public function __construct(
public array $lineItems,
public StripePaymentMode $mode,
public string $successUrl,
public string $cancelUrl
) {}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'line_items' => array_map(fn (StripeLineItemDTO $dto) => $dto->toArray(), $this->lineItems),
'mode' => $this->mode->value,
'success_url' => $this->successUrl,
'cancel_url' => $this->cancelUrl,
];
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Data; namespace App\Data\Upload;
use App\Models\Product; use App\Models\Product;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Data; namespace App\Data\User;
use App\Contracts\InputDataTransferObject; use App\Contracts\InputDataTransferObject;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Data; namespace App\Data\User;
use App\Contracts\OutputDataTransferObject; use App\Contracts\OutputDataTransferObject;

View File

@ -0,0 +1,11 @@
<?php
namespace App\Enums\Campaign;
enum CampaignStatuses: string
{
case Active = 'active';
case Draft = 'draft';
case Paused = 'paused';
case Expired = 'expired';
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Enums\Campaign;
enum DiscountTypes: string
{
case Percentage = 'percentage';
case FixedAmount = 'fixed_amount';
case FreeShipping = 'free-shipping';
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Enums\Cart;
enum CartStatus: string
{
case Active = 'active'; // freshly created
case Converted = 'converted'; // user ordered
case Abandoned = 'abandoned'; // older than 24hrs
case Expired = 'expired'; // left for a long period
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Enums\Order;
/**
* Open -> The order was placed or created. There is work to do for the order, which can include processing payment, fulfilling, or processing returns.
* Archived -> The order was manually or automatically archived. Usually, this means the order was fulfilled.
* Canceled -> The order was canceled. If a canceled order was not fully refunded, then there might be work remaining for the order.
*/
enum OrderStatus: string
{
case Open = 'open';
case Archived = 'archived';
case Closed = 'closed';
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums\Payment;
enum PaymentModes: string
{
case StripeCheckout = 'stripeCheckout';
case CashOnDelivery = 'cashOnDelivery';
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Enums\Payment;
enum PaymentStatusEnum: string
{
case Unpaid = 'unpaid';
case Paid = 'paid';
case Refunded = 'refunded';
case Failed = 'failed';
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums\Stripe;
enum StripeCurrency: string
{
case INR = 'inr';
case USD = 'usd';
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Enums\Stripe;
enum StripeEventType: string
{
case CheckoutSessionCompleted = 'checkout.session.completed';
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Enums\Stripe;
enum StripePaymentMode: string
{
case Payment = 'payment';
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Enums; namespace App\Enums\User;
enum UserRoles: string enum UserRoles: string
{ {

View File

@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Http\JsonResponse;
class StaleCartException extends Exception
{
public function __construct(
public readonly int|string $userId,
public readonly int|string $cartId,
public $message = 'Attempt to create a order with a stale cart',
) {
parent::__construct($message);
}
public function context(): array
{
return [
'user_id' => $this->userId,
'cart_id' => $this->cartId,
];
}
public function render(): JsonResponse
{
return response()->json(['message' => 'Cart is stale'], 409);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Campaign;
use App\Http\Controllers\Controller;
use App\Http\Requests\Campaign\CampaignRequest;
use App\Http\Resources\Campaign\CampaignResource;
use App\Models\Campaign;
class CampaignController extends Controller
{
public function index()
{
return CampaignResource::collection(Campaign::all());
}
public function store(CampaignRequest $request)
{
return new CampaignResource(Campaign::create($request->validated()));
}
public function show(Campaign $campaign)
{
return new CampaignResource($campaign);
}
public function update(CampaignRequest $request, Campaign $campaign)
{
$campaign->update($request->validated());
return new CampaignResource($campaign);
}
public function destroy(Campaign $campaign)
{
$campaign->delete();
return response()->json();
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Cart;
use App\Actions\Cart\AddProductToCartAction;
use App\Actions\Cart\GetActiveUserCartAction;
use App\Actions\Cart\RemoveProductFromCartAction;
use App\Actions\Cart\UpdateProductInCartAction;
use App\Data\Cart\AddToCartDTO;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Log;
use App\Http\Controllers\ModelNotFoundException;
use App\Http\Controllers\MultipleRecordsFoundException;
use App\Http\Requests\Cart\AddProductToCartRequest;
use App\Http\Requests\Cart\RemoveProductFromCartRequest;
use App\Http\Requests\Cart\UpdateProductInCartRequest;
use App\Http\Resources\Cart\CartResource;
use Illuminate\Support\Facades\Auth;
class CartController extends Controller
{
/**
* Store a newly created resource in storage.
*/
public function store(AddProductToCartRequest $request, AddProductToCartAction $addProductAction)
{
$addToCartData = AddToCartDTO::fromRequest($request);
$cart = $addProductAction->execute($addToCartData, Auth::user());
return new CartResource($cart);
}
/**
* Display the specified resource.
*/
public function show(GetActiveUserCartAction $action)
{
return new CartResource($action->execute(Auth::user()));
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateProductInCartRequest $request, UpdateProductInCartAction $action)
{
$updateCartData = AddToCartDTO::fromRequest($request);
$cart = $action->execute($updateCartData, $request->user());
return new CartResource($cart);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(RemoveProductFromCartRequest $request, RemoveProductFromCartAction $action)
{
$user = $request->user();
try {
$cart = $action->execute($request->productId, $user);
return new CartResource($cart);
} catch (ModelNotFoundException $e) {
Log::error('No active cart found when removing a product from cart.', [
'user' => $user->id,
'error' => $e->getMessage(),
]);
return response()->json([
'message' => 'No active cart found.',
], 404);
} catch (MultipleRecordsFoundException $e) {
Log::error('Multiple active carts found for the user', [
'user' => $user->id,
'error' => $e->getMessage(),
]);
return response()->json([
'message' => 'Multiple Active carts found.',
], 409);
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Coupon;
use App\Http\Controllers\Controller;
use App\Http\Requests\Campaign\CouponRequest;
use App\Http\Resources\Campaign\CouponResource;
use App\Models\Coupon;
class CouponController extends Controller
{
public function index()
{
return CouponResource::collection(Coupon::all());
}
public function store(CouponRequest $request)
{
return new CouponResource(Coupon::create($request->validated()));
}
public function show(Coupon $coupon)
{
return new CouponResource($coupon);
}
public function update(CouponRequest $request, Coupon $coupon)
{
$coupon->update($request->validated());
return new CouponResource($coupon);
}
public function destroy(Coupon $coupon)
{
$coupon->delete();
return response()->json();
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Order;
use App\Actions\Order\CreateOrderAction;
use App\Data\Order\OrderRequestDTO;
use App\Exceptions\StaleCartException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Order\StoreOrderRequest;
use App\Http\Requests\Order\UpdateOrderRequest;
use App\Models\Order;
class OrderController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Store a newly created resource in storage.
*
* @throws StaleCartException
*/
public function store(StoreOrderRequest $request, CreateOrderAction $action)
{
$order = $action->execute(OrderRequestDTO::fromRequest($request), $request->user());
return response()->json([
'message' => 'Order created successfully',
'orderId' => $order->id,
], 201);
}
/**
* Display the specified resource.
*/
public function show(Order $order)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateOrderRequest $request, Order $order)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Order $order)
{
//
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Payment;
use App\Actions\Payment\ProcessOrderPaymentAction;
use App\Actions\Stripe\VerifyStripeSessionAction;
use App\Enums\Payment\PaymentModes;
use App\Http\Controllers\Controller;
use App\Http\Requests\Payment\PaymentRequest;
use App\Http\Requests\Payment\VerifyPaymentRequest;
use App\Http\Resources\Payment\PaymentResource;
use App\Models\Order;
use App\Models\Payment;
class PaymentController extends Controller
{
public function index()
{
return PaymentResource::collection(Payment::all());
}
public function store(PaymentRequest $request, Order $order, ProcessOrderPaymentAction $action)
{
$response = $action->execute($order, PaymentModes::tryFrom($request->mode));
return new PaymentResource($response);
}
public function verify(VerifyPaymentRequest $request, VerifyStripeSessionAction $action)
{
return $action->execute($request->orderId, $request->sessionId, $request->user())->toArray();
}
public function show(Payment $payment)
{
return new PaymentResource($payment);
}
public function update(PaymentRequest $request, Payment $payment)
{
$payment->update($request->validated());
return new PaymentResource($payment);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Product;
use App\Http\Controllers\Controller;
use App\Http\Resources\Product\FavouriteProductResource;
use App\Models\FavouriteProduct;
use App\Models\Product;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class FavouriteProductController extends Controller
{
public function index()
{
return FavouriteProductResource::collection(FavouriteProduct::all());
}
public function toggle(Request $request, Product $product)
{
/**
* @var User $user
*/
$user = $request->user();
$changes = $user->favoriteProducts()->toggle($product);
Log::info('hi again');
// If changes has any item, that means a product has been attached.
$isFavorite = count($changes['attached']) > 0;
return response()->json([
'message' => $isFavorite ? 'Product added to favorites' : 'Product removed from favorites',
'isFavorite' => $isFavorite,
]);
}
}

View File

@ -1,8 +1,9 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\Product;
use App\Actions\GetAllProductCategory; use App\Actions\Product\GetAllProductCategory;
use App\Http\Controllers\Controller;
class ProductCategoryController extends Controller class ProductCategoryController extends Controller
{ {

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Product;
use App\Data\Product\ProductDTO;
use App\Http\Controllers\Controller;
use App\Http\Requests\Product\CreateProductRequest;
use App\Http\Resources\Product\ProductResource;
use App\Models\Product;
use App\Queries\GetProductsQuery;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ProductController extends Controller
{
public function index(GetProductsQuery $getProductsQuery)
{
$products = $getProductsQuery->get(Auth::user());
$paginatedDtos = $products->through(fn ($product) => ProductDTO::fromModel($product));
return ProductResource::collection($paginatedDtos);
}
public function store(CreateProductRequest $request)
{
return Product::create($request->validated());
}
public function show(string $slug)
{
$product = Product::where('slug', $slug)
->with(['category:id,name,slug', 'images:id,path,product_id'])
->withExists('favoritedBy')
->firstOrFail();
return new ProductResource(ProductDTO::fromModel($product));
}
public function update(Request $request, Product $product) {}
public function destroy(Product $product) {}
}

View File

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\Product;
use App\Actions\UploadImageAction; use App\Actions\Upload\UploadImageAction;
use App\Data\UploadImageDTO; use App\Data\Upload\UploadImageDTO;
use App\Http\Requests\UploadImageRequest; use App\Http\Controllers\Controller;
use App\Http\Requests\Upload\UploadImageRequest;
use App\Models\ProductImages; use App\Models\ProductImages;
class ProductImagesController extends Controller class ProductImagesController extends Controller

View File

@ -1,36 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Data\ProductDTO;
use App\Http\Requests\CreateProductRequest;
use App\Http\Resources\ProductResource;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index()
{
$paginator = Product::query()->with(['category:id,name,slug', 'images:id,path,product_id'])->paginate();
$paginatedDtos = $paginator->through(fn ($product) => ProductDTO::fromModel($product));
return ProductResource::collection($paginatedDtos);
}
public function store(CreateProductRequest $request)
{
return Product::create($request->validated());
}
public function show(string $slug)
{
$product = Product::where('slug', $slug)->with(['category:id,name,slug', 'images:id,path,product_id'])->firstOrFail();
return new ProductResource(ProductDTO::fromModel($product));
}
public function update(Request $request, Product $product) {}
public function destroy(Product $product) {}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Stripe;
use App\Actions\Payment\MarkPaymentAsPaidAction;
use App\Enums\Stripe\StripeEventType;
use App\Http\Controllers\Controller;
use App\Models\Payment;
use Illuminate\Http\Request;
use Log;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Webhook;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnexpectedValueException;
class StripeWebhookController extends Controller
{
public function __construct(private readonly MarkPaymentAsPaidAction $paidAction) {}
public function __invoke(Request $request)
{
$payload = $request->getContent();
$sigHeader = $request->header('Stripe-Signature');
try {
$event = Webhook::constructEvent($payload, $sigHeader, config('services.stripe.webhook'));
} catch (SignatureVerificationException|UnexpectedValueException $e) {
Log::error('Stripe webhook signature verification error.', [$e->getMessage()]);
throw new BadRequestHttpException('Invalid Signature');
}
if ($event->type === StripeEventType::CheckoutSessionCompleted->value) {
$sessionId = $event->data->object->id ?? null;
if ($sessionId) {
$this->handleCheckoutSessionCompleted($sessionId);
} else {
throw new NotFoundHttpException('Session id not found in event');
}
}
}
private function handleCheckoutSessionCompleted(string $sessionId): void
{
$payment = Payment::where('transaction_id', $sessionId)->first();
if (! $payment) {
Log::error('Stripe Webhook: Payment record not found.', ['session_id' => $sessionId]);
throw new NotFoundHttpException('Payment record not found');
}
$this->paidAction->execute($payment);
Log::info('Stripe Webhook: Payment successfully marked as paid', ['order_id' => $payment->order_id]);
}
}

View File

@ -1,8 +1,9 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use App\Data\UserDTO; use App\Data\User\UserDTO;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;

View File

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers\User;
use App\Actions\CreateUserAction; use App\Actions\User\CreateUserAction;
use App\Data\RegisterDTO; use App\Data\User\RegisterDTO;
use App\Http\Requests\RegisterUserRequest; use App\Http\Controllers\Controller;
use App\Http\Requests\User\RegisterUserRequest;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
class RegisteredUserController extends Controller class RegisteredUserController extends Controller

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\User;
use App\Actions\Address\DeleteUserAddressAction;
use App\Actions\Address\SaveUserAddressAction;
use App\Actions\Address\UpdateUserAddressAction;
use App\Data\Address\AddUserAddressRequestDTO;
use App\Data\Address\UpdateUserAddressRequestDTO;
use App\Data\Address\UserAddressResponseDTO;
use App\Http\Controllers\Controller;
use App\Http\Requests\Address\AddUserAddressRequest;
use App\Http\Requests\Address\UpdateUserAddressRequest;
use App\Http\Resources\Address\AddressResource;
use App\Models\Address;
use Illuminate\Http\Request;
class UserAddressController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$addresses = $request->user()->addresses;
$data = $addresses->map(fn ($address) => UserAddressResponseDTO::fromModel($address));
return AddressResource::collection($data);
}
/**
* Store a newly created resource in storage.
*/
public function store(AddUserAddressRequest $request, SaveUserAddressAction $action)
{
return new AddressResource($action->execute(AddUserAddressRequestDTO::fromRequest($request), $request->user()));
}
/**
* Display the specified resource.
*/
public function show(Address $address)
{
return new AddressResource(UserAddressResponseDTO::fromModel($address));
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateUserAddressRequest $request, Address $address, UpdateUserAddressAction $action)
{
return new AddressResource($action->execute(UpdateUserAddressRequestDTO::fromRequest($request), $address));
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Address $address, DeleteUserAddressAction $action)
{
return $action->execute($address);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Address;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class AddUserAddressRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'firstName' => 'required|string|min:3|max:50',
'lastName' => 'required|string|min:3|max:50',
'street' => 'required|string|min:3|max:100',
'city' => 'required|string|min:3|max:20',
'state' => 'required|string|min:3|max:40',
'pinCode' => 'required|string|min:3|max:10',
];
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests\Address;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserAddressRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'firstName' => 'sometimes|string|min:3|max:50',
'lastName' => 'sometimes|string|min:3|max:50',
'street' => 'sometimes|string|min:3|max:100',
'city' => 'sometimes|string|min:3|max:20',
'state' => 'sometimes|string|min:3|max:40',
'pinCode' => 'sometimes|string|min:3|max:10',
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Campaign;
use Illuminate\Foundation\Http\FormRequest;
class CampaignRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required'],
'discount_type_id' => ['required', 'exists:discount_types'],
'discount_value' => ['required', 'numeric'],
'max_discount' => ['nullable', 'numeric'],
'min_order_value' => ['nullable', 'numeric'],
'start_time' => ['required', 'date'],
'end_time' => ['required', 'date'],
'total_usage_limit' => ['nullable', 'integer'],
'per_user_limit' => ['nullable', 'integer'],
'campaign_status_id' => ['required', 'exists:campaign_statuses'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests\Campaign;
use Illuminate\Foundation\Http\FormRequest;
class CouponRequest extends FormRequest
{
public function rules(): array
{
return [
'campaign_id' => ['required', 'exists:campaigns'],
'code' => ['required'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Cart;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class AddProductToCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'productId' => 'required|exists:products,id',
'quantity' => 'required|numeric|min:1',
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Cart;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class RemoveProductFromCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'productId' => ['required', 'exists:products,id'],
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Cart;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class UpdateProductInCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'productId' => ['required', 'exists:products,id'],
'quantity' => ['required', 'min:0', 'max:10'],
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Order;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class StoreOrderRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'cartId' => 'required|exists:carts,id',
'addressId' => 'required|exists:addresses,id',
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Order;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class UpdateOrderRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Requests\Payment;
use App\Enums\Payment\PaymentModes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class PaymentRequest extends FormRequest
{
public function rules(): array
{
return [
'mode' => ['required', Rule::enum(PaymentModes::class)],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Payment;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class VerifyPaymentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'sessionId' => 'required|exists:stripe_sessions,session_id',
'orderId' => 'required|exists:orders,id',
];
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\Product;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

View File

@ -1,7 +1,8 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\Upload;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class UploadImageRequest extends FormRequest class UploadImageRequest extends FormRequest
@ -17,7 +18,7 @@ public function authorize(): bool
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> * @return array<string, ValidationRule|array<mixed>|string>
*/ */
public function rules(): array public function rules(): array
{ {

View File

@ -1,7 +1,8 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\User;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
@ -18,7 +19,7 @@ public function authorize(): bool
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> * @return array<string, ValidationRule|array<mixed>|string>
*/ */
public function rules(): array public function rules(): array
{ {

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources\Address;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class AddressCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Resources\Address;
use App\Data\Address\UserAddressResponseDTO;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @property UserAddressResponseDTO $resource
*/
class AddressResource extends JsonResource
{
public static $wrap = null;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->resource->id,
'firstName' => $this->resource->firstName,
'lastName' => $this->resource->lastName,
'street' => $this->resource->street,
'city' => $this->resource->city,
'state' => $this->resource->state,
'pinCode' => $this->resource->pinCode,
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources\Campaign;
use App\Models\Campaign;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin Campaign */
class CampaignResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'discount_value' => $this->dicount_value,
'max_discount' => $this->max_discount,
'min_order_value' => $this->min_order_value,
'start_time' => $this->start_time,
'end_time' => $this->end_time,
'total_usage_limit' => $this->total_usage_limit,
'per_user_limit' => $this->per_user_limit,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'discount_type_id' => $this->discount_type_id,
'campaign_status_id' => $this->campaign_status_id,
];
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Resources\Campaign;
use App\Models\Coupon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin Coupon */
class CouponResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'code' => $this->code,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'campaign_id' => $this->campaign_id,
'campaign' => new CampaignResource($this->whenLoaded('campaign')),
];
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources\Cart;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CartItemCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources\Cart;
use App\Data\Cart\CartItemDTO;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Storage;
/**
* @property CartItemDTO $resource
*/
class CartItemResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->resource->id,
'title' => $this->resource->title,
'quantity' => $this->resource->quantity,
'price' => $this->resource->price,
'subtotal' => $this->resource->subtotal,
'image' => Storage::disk('public')->url($this->resource->image),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Resources\Cart;
use App\Data\Cart\CartDTO;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @property CartDTO $resource
*/
class CartResource extends JsonResource
{
public static $wrap = null;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->resource->id,
'itemsCount' => $this->resource->itemsCount,
'totalPrice' => $this->resource->totalPrice,
'items' => CartItemResource::collection($this->resource->items),
];
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources\Order;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Resources\Payment;
use App\Data\Payment\PaymentResponseDTO;
use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Payment
*
* @property PaymentResponseDTO $resource
*/
class PaymentResource extends JsonResource
{
public static $wrap = null;
public function toArray(Request $request): array
{
return [
'success' => $this->resource->isSuccess,
'amount' => $this->resource->amount,
'currency' => $this->resource->currency,
'method' => $this->resource->method,
'redirectUrl' => $this->resource->redirectUrl,
'errorMessage' => $this->resource->errorMessage,
];
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Resources\Product;
use App\Models\FavouriteProduct;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin FavouriteProduct */
class FavouriteProductResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'user_id' => $this->user_id,
'product_id' => $this->product_id,
];
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Resources; namespace App\Http\Resources\Product;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Http\Resources\Json\ResourceCollection;

View File

@ -1,9 +1,9 @@
<?php <?php
namespace App\Http\Resources; namespace App\Http\Resources\Product;
use App\Data\ProductDTO; use App\Data\Product\ProductDTO;
use App\Data\ProductImageDTO; use App\Data\Product\ProductImageDTO;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -30,6 +30,7 @@ public function toArray(Request $request): array
return Storage::disk('public')->url($productImage->path); return Storage::disk('public')->url($productImage->path);
}, $this->resource->productImages), }, $this->resource->productImages),
'updatedAt' => $this->resource->updatedAt, 'updatedAt' => $this->resource->updatedAt,
'isFavorite' => $this->resource->isFavorite,
]; ];
} }
} }

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @mixin IdeHelperAddress
*/
class Address extends Model
{
protected $fillable = ['first_name', 'last_name', 'street', 'city', 'state', 'pin'];
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Campaign extends Model
{
protected $fillable = [
'name',
'discount_type_id',
'discount_value',
'max_discount',
'min_order_value',
'start_time',
'end_time',
'total_usage_limit',
'per_user_limit',
'campaign_status_id',
];
public function discountType(): BelongsTo
{
return $this->belongsTo(DiscountType::class);
}
public function campaignStatus(): BelongsTo
{
return $this->belongsTo(CampaignStatus::class);
}
protected function casts(): array
{
return [
'start_time' => 'datetime',
'end_time' => 'datetime',
];
}
}

Some files were not shown because too many files have changed in this diff Show More