bega updates
This commit is contained in:
@@ -12,6 +12,21 @@ use Illuminate\Support\Facades\Http;
|
|||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate
|
||||||
|
*/
|
||||||
|
function tr(string $text, string $locale = 'tk'): string
|
||||||
|
{
|
||||||
|
$text = json_decode($text);
|
||||||
|
|
||||||
|
if ($text) {
|
||||||
|
return $text->{$locale} ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
if (! function_exists('modules_path')) {
|
if (! function_exists('modules_path')) {
|
||||||
/**
|
/**
|
||||||
* Single translation for all locales
|
* Single translation for all locales
|
||||||
@@ -32,20 +47,6 @@ if (! function_exists('translatable')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Translate
|
|
||||||
*/
|
|
||||||
function tr(string $text, string $locale = 'tk'): string
|
|
||||||
{
|
|
||||||
$text = json_decode($text);
|
|
||||||
|
|
||||||
if ($text) {
|
|
||||||
return $text->{$locale} ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! function_exists('removeWhiteSpace')) {
|
if (! function_exists('removeWhiteSpace')) {
|
||||||
/**
|
/**
|
||||||
* Remove white sapce from string
|
* Remove white sapce from string
|
||||||
@@ -105,22 +106,59 @@ if (! function_exists('sendSMS')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! function_exists('normalizeTurkmenPhoneNumber')) {
|
||||||
|
/**
|
||||||
|
* Normalize a Turkmen phone number to the local 8-digit format.
|
||||||
|
*/
|
||||||
|
function normalizeTurkmenPhoneNumber(null|string|int $phone): null|string
|
||||||
|
{
|
||||||
|
if ($phone === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$phone = preg_replace('/\D+/', '', (string) $phone);
|
||||||
|
|
||||||
|
if (str_starts_with($phone, '993') && strlen($phone) === 11) {
|
||||||
|
return substr($phone, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $phone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('isOtpBypass')) {
|
||||||
|
/**
|
||||||
|
* Fixed OTP bypass for the approved test phone number.
|
||||||
|
*/
|
||||||
|
function isOtpBypass(null|string|int $phone, null|string|int $code): bool
|
||||||
|
{
|
||||||
|
return normalizeTurkmenPhoneNumber($phone) === '65222548' && (string) $code === '12212';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! function_exists('sendSMSVerification')) {
|
if (! function_exists('sendSMSVerification')) {
|
||||||
/**
|
/**
|
||||||
* Send a sms verification code
|
* Send a sms verification code
|
||||||
*
|
*
|
||||||
* @return App\Models\Verification | null
|
* @return \App\Models\Verification | null
|
||||||
*/
|
*/
|
||||||
function sendSMSVerification(string|int $phone_number): ?Verification
|
function sendSMSVerification(string|int $phone_number): ?Verification
|
||||||
{
|
{
|
||||||
|
$phone_number = normalizeTurkmenPhoneNumber($phone_number);
|
||||||
|
|
||||||
/* for apple testing */
|
/* for apple testing */
|
||||||
$phone_code = ($phone_number == '61126667') ? 77777 : rand(10000, 99999);
|
$phone_code = ($phone_number == '61126667') ? 77777 : rand(10000, 99999);
|
||||||
|
$phone_code = ($phone_number == '65222548') ? 12212 : $phone_code;
|
||||||
|
|
||||||
$verification = Verification::where(['username' => $phone_number])->first();
|
$verification = Verification::where(['username' => $phone_number])->first();
|
||||||
$verification
|
$verification
|
||||||
? $verification->update(['code' => $phone_code])
|
? $verification->update(['code' => $phone_code])
|
||||||
: Verification::create(['username' => $phone_number, 'code' => $phone_code]);
|
: Verification::create(['username' => $phone_number, 'code' => $phone_code]);
|
||||||
|
|
||||||
|
if (isOtpBypass($phone_number, $phone_code)) {
|
||||||
|
return Verification::where(['username' => $phone_number])->first();
|
||||||
|
}
|
||||||
|
|
||||||
sendSMS($phone_number, 'Tassyklaýyş belgi: '.$phone_code);
|
sendSMS($phone_number, 'Tassyklaýyş belgi: '.$phone_code);
|
||||||
|
|
||||||
return $verification;
|
return $verification;
|
||||||
@@ -181,7 +219,7 @@ if (! function_exists('tmpostChannel')) {
|
|||||||
/**
|
/**
|
||||||
* Default channel
|
* Default channel
|
||||||
*
|
*
|
||||||
* @return App\Models\Shop\Channel
|
* @return \App\Models\Shop\Channel
|
||||||
*/
|
*/
|
||||||
function tmpostChannel(): Channel
|
function tmpostChannel(): Channel
|
||||||
{
|
{
|
||||||
@@ -337,7 +375,7 @@ if (! function_exists('orderAdminNumber')) {
|
|||||||
*/
|
*/
|
||||||
function orderAdminNumber(): int
|
function orderAdminNumber(): int
|
||||||
{
|
{
|
||||||
return 61126667;
|
return 65223722;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\V1\Auth\Register;
|
namespace App\Http\Controllers\Api\V1\Auth\Register;
|
||||||
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class AuthRegisterRequest extends FormRequest
|
class AuthRegisterRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->merge([
|
||||||
|
'phone_number' => normalizeTurkmenPhoneNumber($this->phone_number),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, ValidationRule|array|string>
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ class EntrepreneurAuthController extends Controller
|
|||||||
{
|
{
|
||||||
public function register(Request $request)
|
public function register(Request $request)
|
||||||
{
|
{
|
||||||
|
$request->merge([
|
||||||
|
'phone_number' => normalizeTurkmenPhoneNumber($request->phone_number),
|
||||||
|
]);
|
||||||
|
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'first_name' => ['required', 'string'],
|
'first_name' => ['required', 'string'],
|
||||||
'phone_number' => ['required', 'integer', 'between:61000000,71999999', 'unique:users,phone_number'],
|
'phone_number' => ['required', 'integer', 'between:61000000,71999999', 'unique:users,phone_number'],
|
||||||
@@ -53,13 +57,19 @@ class EntrepreneurAuthController extends Controller
|
|||||||
|
|
||||||
public function verifyPhoneNumber(Request $request)
|
public function verifyPhoneNumber(Request $request)
|
||||||
{
|
{
|
||||||
|
$request->merge([
|
||||||
|
'phone_number' => normalizeTurkmenPhoneNumber($request->phone_number),
|
||||||
|
]);
|
||||||
|
|
||||||
$validator = Validator::make($request->all(), ['phone_number' => 'required|integer|between:61000000,65999999', 'code' => 'required|string']);
|
$validator = Validator::make($request->all(), ['phone_number' => 'required|integer|between:61000000,65999999', 'code' => 'required|string']);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
|
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
$verification = Verification::where('username', $request->phone_number)->where('code', $request->code)->first();
|
$verification = isOtpBypass($request->phone_number, $request->code)
|
||||||
|
? true
|
||||||
|
: Verification::where('username', $request->phone_number)->where('code', $request->code)->first();
|
||||||
|
|
||||||
if (! $verification) {
|
if (! $verification) {
|
||||||
return response()->rest([], 400, 'Wrong credentials');
|
return response()->rest([], 400, 'Wrong credentials');
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\FlashSale;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\V1\FlashSale\Resources\FlashSaleResource;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\CMS\Marketing\FlashSale;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class FlashSaleController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Active flash sales.
|
||||||
|
*/
|
||||||
|
public function index(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->rest(
|
||||||
|
FlashSaleResource::collection(
|
||||||
|
FlashSale::query()
|
||||||
|
->active()
|
||||||
|
->with(['products' => function ($query) {
|
||||||
|
$query
|
||||||
|
->with(['brand', 'media', 'reviews'])
|
||||||
|
->where('products.is_visible', true)
|
||||||
|
->where('products.parent_id', null)
|
||||||
|
->where('products.stock', '>', 0);
|
||||||
|
}])
|
||||||
|
->latest('starts_at')
|
||||||
|
->get()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\FlashSale\Resources;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\V1\Product\Resources\ProductIndexResource;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class FlashSaleResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->whenHas('id'),
|
||||||
|
'title' => $this->whenHas('title'),
|
||||||
|
'starts_at' => $this->starts_at?->toISOString(),
|
||||||
|
'ends_at' => $this->ends_at?->toISOString(),
|
||||||
|
'products' => ProductIndexResource::collection($this->whenLoaded('products')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\Story\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class StoryResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->whenHas('id'),
|
||||||
|
'title' => $this->whenHas('title'),
|
||||||
|
'photo' => $this->photo(),
|
||||||
|
'thumbnail' => $this->photo('thumb720x1280'),
|
||||||
|
'expires_at' => $this->expires_at?->toISOString(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Http/Controllers/Api/V1/Story/StoryController.php
Normal file
26
app/Http/Controllers/Api/V1/Story/StoryController.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\Story;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\V1\Story\Resources\StoryResource;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\CMS\Media\Story;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class StoryController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* (Media) Active stories.
|
||||||
|
*/
|
||||||
|
public function index(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->rest(
|
||||||
|
StoryResource::collection(
|
||||||
|
Story::with('media')
|
||||||
|
->active()
|
||||||
|
->ordered()
|
||||||
|
->get()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Api\V1\Auth;
|
namespace App\Http\Requests\Api\V1\Auth;
|
||||||
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class AuthLoginRequest extends FormRequest
|
class AuthLoginRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->merge([
|
||||||
|
'phone_number' => normalizeTurkmenPhoneNumber($this->phone_number),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, ValidationRule|array|string>
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,15 +3,24 @@
|
|||||||
namespace App\Http\Requests\Api\V1\Auth;
|
namespace App\Http\Requests\Api\V1\Auth;
|
||||||
|
|
||||||
use App\Rules\VerificationRule;
|
use App\Rules\VerificationRule;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class AuthVerifyRequest extends FormRequest
|
class AuthVerifyRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->merge([
|
||||||
|
'phone_number' => normalizeTurkmenPhoneNumber($this->phone_number),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, ValidationRule|array|string>
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
|||||||
67
app/Models/CMS/Marketing/FlashSale.php
Normal file
67
app/Models/CMS/Marketing/FlashSale.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\CMS\Marketing;
|
||||||
|
|
||||||
|
use App\Models\Ecommerce\Product\Product\Product;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Spatie\Translatable\HasTranslations;
|
||||||
|
|
||||||
|
class FlashSale extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasTranslations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'id',
|
||||||
|
'title',
|
||||||
|
'starts_at',
|
||||||
|
'ends_at',
|
||||||
|
'is_visible',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'starts_at' => 'datetime',
|
||||||
|
'ends_at' => 'datetime',
|
||||||
|
'is_visible' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translatable attributes.
|
||||||
|
*
|
||||||
|
* @var array<string>
|
||||||
|
*/
|
||||||
|
public $translatable = ['title'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Related products.
|
||||||
|
*/
|
||||||
|
public function products(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Product::class)
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active flash sales are visible and inside their timer window.
|
||||||
|
*/
|
||||||
|
public function scopeActive(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where('is_visible', true)
|
||||||
|
->where('starts_at', '<=', now())
|
||||||
|
->where('ends_at', '>', now());
|
||||||
|
}
|
||||||
|
}
|
||||||
116
app/Models/CMS/Media/Story.php
Normal file
116
app/Models/CMS/Media/Story.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\CMS\Media;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Spatie\EloquentSortable\SortableTrait;
|
||||||
|
use Spatie\Image\Manipulations;
|
||||||
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
use Spatie\Translatable\HasTranslations;
|
||||||
|
|
||||||
|
class Story extends Model implements HasMedia
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasTranslations;
|
||||||
|
use InteractsWithMedia;
|
||||||
|
use SortableTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'id',
|
||||||
|
'title',
|
||||||
|
'expires_at',
|
||||||
|
'sort_order',
|
||||||
|
'is_visible',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'expires_at' => 'datetime',
|
||||||
|
'is_visible' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translatable attributes.
|
||||||
|
*
|
||||||
|
* @var array<string>
|
||||||
|
*/
|
||||||
|
public $translatable = ['title'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sortable attributes.
|
||||||
|
*
|
||||||
|
* @var array<string, mixed>
|
||||||
|
*/
|
||||||
|
public $sortable = [
|
||||||
|
'order_column_name' => 'sort_order',
|
||||||
|
'sort_when_creating' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "booted" method of the model.
|
||||||
|
*/
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::saving(function (Story $story) {
|
||||||
|
if (blank($story->expires_at)) {
|
||||||
|
$story->expires_at = now()->addDay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media collections.
|
||||||
|
*/
|
||||||
|
public function registerMediaCollections(): void
|
||||||
|
{
|
||||||
|
$this->addMediaCollection('photo')
|
||||||
|
->singleFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media conversions.
|
||||||
|
*/
|
||||||
|
public function registerMediaConversions(?Media $media = null): void
|
||||||
|
{
|
||||||
|
$this->addMediaConversion('thumb200x200')
|
||||||
|
->fit(Manipulations::FIT_CONTAIN, 200, 200);
|
||||||
|
|
||||||
|
$this->addMediaConversion('thumb720x1280')
|
||||||
|
->fit(Manipulations::FIT_CONTAIN, 720, 1280);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active stories are visible and not expired.
|
||||||
|
*/
|
||||||
|
public function scopeActive(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where('is_visible', true)
|
||||||
|
->where('expires_at', '>', now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Story photo URL.
|
||||||
|
*/
|
||||||
|
public function photo(?string $conversion = null): string
|
||||||
|
{
|
||||||
|
if ($conversion) {
|
||||||
|
return $this->getFirstMediaUrl('photo', $conversion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getFirstMediaUrl('photo');
|
||||||
|
}
|
||||||
|
}
|
||||||
175
app/Nova/Resources/CMS/Marketing/FlashSale.php
Normal file
175
app/Nova/Resources/CMS/Marketing/FlashSale.php
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Nova\Resources\CMS\Marketing;
|
||||||
|
|
||||||
|
use App\Models\CMS\Marketing\FlashSale as FlashSaleModel;
|
||||||
|
use App\Nova\Forms\NovaForm;
|
||||||
|
use App\Nova\Resource;
|
||||||
|
use App\Nova\Resources\Ecommerce\Product\Product\Product;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Laravel\Nova\Fields\DateTime;
|
||||||
|
use Laravel\Nova\Fields\ID;
|
||||||
|
use Laravel\Nova\Fields\Text;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
use Laravel\Nova\Panel;
|
||||||
|
use Outl1ne\MultiselectField\Multiselect;
|
||||||
|
use Trin4ik\NovaSwitcher\NovaSwitcher;
|
||||||
|
|
||||||
|
class FlashSale extends Resource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The model the resource corresponds to.
|
||||||
|
*
|
||||||
|
* @var class-string<FlashSaleModel>
|
||||||
|
*/
|
||||||
|
public static $model = FlashSaleModel::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The single value that should be used to represent the resource when being displayed.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $title = 'id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The columns that should be searched.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $search = [
|
||||||
|
'title',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationships that should be eager loaded on index queries.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $with = ['products'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return __('Flash sales');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return __('Flash sale');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the fields displayed by the resource.
|
||||||
|
*/
|
||||||
|
public function fields(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new Panel(__('Basic information'), [
|
||||||
|
ID::make()->sortable(),
|
||||||
|
|
||||||
|
Text::make(__('Title'), 'title')
|
||||||
|
->rules('required')
|
||||||
|
->translatable(),
|
||||||
|
|
||||||
|
DateTime::make(__('Starts at'), 'starts_at')
|
||||||
|
->rules('required', 'date')
|
||||||
|
->default(now()),
|
||||||
|
|
||||||
|
DateTime::make(__('Ends at'), 'ends_at')
|
||||||
|
->rules('required', 'date', 'after:starts_at')
|
||||||
|
->default(now()->addDay()),
|
||||||
|
|
||||||
|
Text::make(__('Products count'), fn () => $this->products->count())
|
||||||
|
->exceptOnForms(),
|
||||||
|
|
||||||
|
Text::make(__('Products'), fn () => $this->products->pluck('name')->implode(', '))
|
||||||
|
->onlyOnDetail(),
|
||||||
|
|
||||||
|
Multiselect::make(__('Products'), 'products')
|
||||||
|
->asyncResource(Product::class)
|
||||||
|
->placeholder(__('Search by id, name, sku, barcode...'))
|
||||||
|
->rules('required')
|
||||||
|
->resolveUsing(fn () => $this->products->pluck('id')->toArray())
|
||||||
|
->fillUsing(NovaForm::fillEmpty())
|
||||||
|
->onlyOnForms(),
|
||||||
|
|
||||||
|
NovaSwitcher::make(__('Visible'), 'is_visible')
|
||||||
|
->default(true)
|
||||||
|
->sortable(),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to be called after the resource is created.
|
||||||
|
*/
|
||||||
|
public static function afterCreate(NovaRequest $request, Model $model): void
|
||||||
|
{
|
||||||
|
static::syncProducts($request, $model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to be called after the resource is updated.
|
||||||
|
*/
|
||||||
|
public static function afterUpdate(NovaRequest $request, Model $model): void
|
||||||
|
{
|
||||||
|
static::syncProducts($request, $model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync selected products.
|
||||||
|
*/
|
||||||
|
protected static function syncProducts(NovaRequest $request, Model $model): void
|
||||||
|
{
|
||||||
|
$products = $request->input('products', []);
|
||||||
|
|
||||||
|
if (is_string($products)) {
|
||||||
|
$products = str_contains($products, ',') ? explode(',', $products) : [$products];
|
||||||
|
}
|
||||||
|
|
||||||
|
$products = collect($products)
|
||||||
|
->filter()
|
||||||
|
->map(fn ($product) => intval($product))
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$model->products()->sync($products);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cards available for the request.
|
||||||
|
*/
|
||||||
|
public function cards(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the filters available for the resource.
|
||||||
|
*/
|
||||||
|
public function filters(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the lenses available for the resource.
|
||||||
|
*/
|
||||||
|
public function lenses(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actions available for the resource.
|
||||||
|
*/
|
||||||
|
public function actions(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
134
app/Nova/Resources/CMS/Media/Story.php
Normal file
134
app/Nova/Resources/CMS/Media/Story.php
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Nova\Resources\CMS\Media;
|
||||||
|
|
||||||
|
use App\Models\CMS\Media\Story as StoryModel;
|
||||||
|
use App\Nova\Filters\VisableFilter;
|
||||||
|
use App\Nova\Resource;
|
||||||
|
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||||
|
use Laravel\Nova\Fields\DateTime;
|
||||||
|
use Laravel\Nova\Fields\ID;
|
||||||
|
use Laravel\Nova\Fields\Text;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
use Laravel\Nova\Panel;
|
||||||
|
use Outl1ne\NovaSortable\Traits\HasSortableRows;
|
||||||
|
use Trin4ik\NovaSwitcher\NovaSwitcher;
|
||||||
|
|
||||||
|
class Story extends Resource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sortable behavior.
|
||||||
|
*/
|
||||||
|
use HasSortableRows;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model the resource corresponds to.
|
||||||
|
*
|
||||||
|
* @var class-string<StoryModel>
|
||||||
|
*/
|
||||||
|
public static $model = StoryModel::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The single value that should be used to represent the resource when being displayed.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $title = 'id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The columns that should be searched.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $search = [
|
||||||
|
'title',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationships that should be eager loaded on index queries.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $with = ['media'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return __('Stories');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return __('Story');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the fields displayed by the resource.
|
||||||
|
*/
|
||||||
|
public function fields(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new Panel(__('Basic information'), [
|
||||||
|
ID::make()->sortable(),
|
||||||
|
|
||||||
|
Images::make(__('Photo'), 'photo')
|
||||||
|
->conversionOnIndexView('thumb200x200')
|
||||||
|
->rules('required')
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Text::make(__('Title'), 'title')
|
||||||
|
->rules('required')
|
||||||
|
->translatable(),
|
||||||
|
|
||||||
|
DateTime::make(__('Expires at'), 'expires_at')
|
||||||
|
->default(now()->addDay())
|
||||||
|
->nullable()
|
||||||
|
->rules('nullable', 'date')
|
||||||
|
->help(__('Default is 24 hours from creation. Set a specific date and hour to control when the story disappears from the mobile app.')),
|
||||||
|
|
||||||
|
NovaSwitcher::make(__('Visible'), 'is_visible')
|
||||||
|
->default(true)
|
||||||
|
->sortable(),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cards available for the request.
|
||||||
|
*/
|
||||||
|
public function cards(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the filters available for the resource.
|
||||||
|
*/
|
||||||
|
public function filters(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new VisableFilter(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the lenses available for the resource.
|
||||||
|
*/
|
||||||
|
public function lenses(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actions available for the resource.
|
||||||
|
*/
|
||||||
|
public function actions(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
109
app/Policies/CMS/Marketing/FlashSalePolicy.php
Normal file
109
app/Policies/CMS/Marketing/FlashSalePolicy.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies\CMS\Marketing;
|
||||||
|
|
||||||
|
use App\Models\CMS\Marketing\FlashSale;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
use Illuminate\Auth\Access\Response;
|
||||||
|
|
||||||
|
class FlashSalePolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform pre-authorization checks.
|
||||||
|
*/
|
||||||
|
public function before(User $user, string $ability): ?Response
|
||||||
|
{
|
||||||
|
if ($user->isMe()) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*/
|
||||||
|
public function viewAny(User $user): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*/
|
||||||
|
public function view(User $user, FlashSale $flashSale): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create models.
|
||||||
|
*/
|
||||||
|
public function create(User $user): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*/
|
||||||
|
public function update(User $user, FlashSale $flashSale): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*/
|
||||||
|
public function delete(User $user, FlashSale $flashSale): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can restore the model.
|
||||||
|
*/
|
||||||
|
public function restore(User $user, FlashSale $flashSale): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can permanently delete the model.
|
||||||
|
*/
|
||||||
|
public function forceDelete(User $user, FlashSale $flashSale): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
}
|
||||||
109
app/Policies/CMS/Media/StoryPolicy.php
Normal file
109
app/Policies/CMS/Media/StoryPolicy.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies\CMS\Media;
|
||||||
|
|
||||||
|
use App\Models\CMS\Media\Story;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
use Illuminate\Auth\Access\Response;
|
||||||
|
|
||||||
|
class StoryPolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform pre-authorization checks.
|
||||||
|
*/
|
||||||
|
public function before(User $user, string $ability): ?Response
|
||||||
|
{
|
||||||
|
if ($user->isMe()) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*/
|
||||||
|
public function viewAny(User $user): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*/
|
||||||
|
public function view(User $user, Story $story): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create models.
|
||||||
|
*/
|
||||||
|
public function create(User $user): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*/
|
||||||
|
public function update(User $user, Story $story): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*/
|
||||||
|
public function delete(User $user, Story $story): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can restore the model.
|
||||||
|
*/
|
||||||
|
public function restore(User $user, Story $story): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can permanently delete the model.
|
||||||
|
*/
|
||||||
|
public function forceDelete(User $user, Story $story): Response
|
||||||
|
{
|
||||||
|
if ($user->hasRole(['admin'])) {
|
||||||
|
return $this->allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->deny();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,13 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Models\CMS\Forms\ContactUS;
|
use App\Models\CMS\Forms\ContactUS;
|
||||||
|
use App\Models\CMS\Marketing\FlashSale;
|
||||||
use App\Models\CMS\Marketing\Newsletter;
|
use App\Models\CMS\Marketing\Newsletter;
|
||||||
use App\Models\CMS\Marketing\NewsletterUser;
|
use App\Models\CMS\Marketing\NewsletterUser;
|
||||||
use App\Models\CMS\Media\Banner;
|
use App\Models\CMS\Media\Banner;
|
||||||
use App\Models\CMS\Media\Carousel;
|
use App\Models\CMS\Media\Carousel;
|
||||||
use App\Models\CMS\Media\Gallery;
|
use App\Models\CMS\Media\Gallery;
|
||||||
|
use App\Models\CMS\Media\Story;
|
||||||
use App\Models\Ecommerce\Channel\Channel;
|
use App\Models\Ecommerce\Channel\Channel;
|
||||||
use App\Models\Ecommerce\Payouts\Payout;
|
use App\Models\Ecommerce\Payouts\Payout;
|
||||||
use App\Models\Ecommerce\Product\Brand\Brand;
|
use App\Models\Ecommerce\Product\Brand\Brand;
|
||||||
@@ -29,11 +31,13 @@ use App\Models\System\Settings\Payments\PaymentType;
|
|||||||
use App\Models\System\VersionManagement\AppVersion;
|
use App\Models\System\VersionManagement\AppVersion;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Policies\CMS\Forms\ContactUSPolicy;
|
use App\Policies\CMS\Forms\ContactUSPolicy;
|
||||||
|
use App\Policies\CMS\Marketing\FlashSalePolicy;
|
||||||
use App\Policies\CMS\Marketing\NewsletterPolicy;
|
use App\Policies\CMS\Marketing\NewsletterPolicy;
|
||||||
use App\Policies\CMS\Marketing\NewsletterUserPolicy;
|
use App\Policies\CMS\Marketing\NewsletterUserPolicy;
|
||||||
use App\Policies\CMS\Media\BannerPolicy;
|
use App\Policies\CMS\Media\BannerPolicy;
|
||||||
use App\Policies\CMS\Media\CarouselPolicy;
|
use App\Policies\CMS\Media\CarouselPolicy;
|
||||||
use App\Policies\CMS\Media\GalleryPolicy;
|
use App\Policies\CMS\Media\GalleryPolicy;
|
||||||
|
use App\Policies\CMS\Media\StoryPolicy;
|
||||||
use App\Policies\Ecommerce\Channel\ChannelPolicy;
|
use App\Policies\Ecommerce\Channel\ChannelPolicy;
|
||||||
use App\Policies\Ecommerce\Payout\PayoutPolicy;
|
use App\Policies\Ecommerce\Payout\PayoutPolicy;
|
||||||
use App\Policies\Ecommerce\Product\Brand\BrandPolicy;
|
use App\Policies\Ecommerce\Product\Brand\BrandPolicy;
|
||||||
@@ -106,12 +110,16 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
ContactUS::class => ContactUSPolicy::class,
|
ContactUS::class => ContactUSPolicy::class,
|
||||||
|
|
||||||
// Marketing...
|
// Marketing...
|
||||||
|
FlashSale::class => FlashSalePolicy::class,
|
||||||
Newsletter::class => NewsletterPolicy::class,
|
Newsletter::class => NewsletterPolicy::class,
|
||||||
NewsletterUser::class => NewsletterUserPolicy::class,
|
NewsletterUser::class => NewsletterUserPolicy::class,
|
||||||
|
|
||||||
// Banners...
|
// Banners...
|
||||||
Banner::class => BannerPolicy::class,
|
Banner::class => BannerPolicy::class,
|
||||||
|
|
||||||
|
// Stories...
|
||||||
|
Story::class => StoryPolicy::class,
|
||||||
|
|
||||||
// Permissions (settings)...
|
// Permissions (settings)...
|
||||||
Role::class => RolePolicy::class,
|
Role::class => RolePolicy::class,
|
||||||
Permission::class => PermissionPolicy::class,
|
Permission::class => PermissionPolicy::class,
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ namespace App\Providers;
|
|||||||
use App\Modules\GlobalOrder\Nova\Resources\GlobalOrderResource;
|
use App\Modules\GlobalOrder\Nova\Resources\GlobalOrderResource;
|
||||||
use App\Nova\Dashboards\Main;
|
use App\Nova\Dashboards\Main;
|
||||||
use App\Nova\Resources\CMS\Forms\ContactUS;
|
use App\Nova\Resources\CMS\Forms\ContactUS;
|
||||||
|
use App\Nova\Resources\CMS\Marketing\FlashSale;
|
||||||
use App\Nova\Resources\CMS\Marketing\Newsletter;
|
use App\Nova\Resources\CMS\Marketing\Newsletter;
|
||||||
use App\Nova\Resources\CMS\Marketing\NewsletterUser;
|
use App\Nova\Resources\CMS\Marketing\NewsletterUser;
|
||||||
use App\Nova\Resources\CMS\Media\Banner;
|
use App\Nova\Resources\CMS\Media\Banner;
|
||||||
use App\Nova\Resources\CMS\Media\Carousel;
|
use App\Nova\Resources\CMS\Media\Carousel;
|
||||||
use App\Nova\Resources\CMS\Media\Gallery;
|
use App\Nova\Resources\CMS\Media\Gallery;
|
||||||
|
use App\Nova\Resources\CMS\Media\Story;
|
||||||
use App\Nova\Resources\Ecommerce\Channel\Channel;
|
use App\Nova\Resources\Ecommerce\Channel\Channel;
|
||||||
use App\Nova\Resources\Ecommerce\Payout\PayoutResource;
|
use App\Nova\Resources\Ecommerce\Payout\PayoutResource;
|
||||||
use App\Nova\Resources\Ecommerce\Product\Attribute\Attribute;
|
use App\Nova\Resources\Ecommerce\Product\Attribute\Attribute;
|
||||||
@@ -180,6 +182,7 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
|
|||||||
MenuItem::resource(Banner::class),
|
MenuItem::resource(Banner::class),
|
||||||
MenuItem::resource(Carousel::class),
|
MenuItem::resource(Carousel::class),
|
||||||
MenuItem::resource(Gallery::class),
|
MenuItem::resource(Gallery::class),
|
||||||
|
MenuItem::resource(Story::class),
|
||||||
])->collapsedByDefault(),
|
])->collapsedByDefault(),
|
||||||
|
|
||||||
MenuGroup::make(__('Legal'), [
|
MenuGroup::make(__('Legal'), [
|
||||||
@@ -188,6 +191,7 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
|
|||||||
])->icon('newspaper')->collapsedByDefault(),
|
])->icon('newspaper')->collapsedByDefault(),
|
||||||
|
|
||||||
MenuSection::make(__('Marketing'), [
|
MenuSection::make(__('Marketing'), [
|
||||||
|
MenuItem::resource(FlashSale::class),
|
||||||
MenuItem::resource(NewsletterUser::class),
|
MenuItem::resource(NewsletterUser::class),
|
||||||
MenuItem::resource(Newsletter::class),
|
MenuItem::resource(Newsletter::class),
|
||||||
MenuItem::resource(Coupon::class),
|
MenuItem::resource(Coupon::class),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Rules;
|
|||||||
use App\Models\Auth\Verification;
|
use App\Models\Auth\Verification;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
|
||||||
|
|
||||||
class VerificationRule implements ValidationRule
|
class VerificationRule implements ValidationRule
|
||||||
{
|
{
|
||||||
@@ -22,7 +21,7 @@ class VerificationRule implements ValidationRule
|
|||||||
/**
|
/**
|
||||||
* Run the validation rule.
|
* Run the validation rule.
|
||||||
*
|
*
|
||||||
* @param Closure(string): PotentiallyTranslatedString $fail
|
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||||
*/
|
*/
|
||||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
{
|
{
|
||||||
@@ -30,6 +29,10 @@ class VerificationRule implements ValidationRule
|
|||||||
$fail(__('No phone provided'));
|
$fail(__('No phone provided'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOtpBypass($this->phone, $value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Verification::where('username', $this->phone)
|
Verification::where('username', $this->phone)
|
||||||
->where('code', $value)
|
->where('code', $value)
|
||||||
->existsOr(fn () => $fail(__('Write a correct data please')));
|
->existsOr(fn () => $fail(__('Write a correct data please')));
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('stories', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->jsonb('title')->index();
|
||||||
|
$table->timestamp('expires_at')->index();
|
||||||
|
$table->integer('sort_order')->nullable();
|
||||||
|
$table->boolean('is_visible')->default(true)->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('stories');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('flash_sales', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->jsonb('title')->index();
|
||||||
|
$table->timestamp('starts_at')->index();
|
||||||
|
$table->timestamp('ends_at')->index();
|
||||||
|
$table->boolean('is_visible')->default(true)->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('flash_sale_product', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('flash_sale_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['flash_sale_id', 'product_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('flash_sale_product');
|
||||||
|
Schema::dropIfExists('flash_sales');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -39,6 +39,10 @@ Route::middleware('auth:sanctum')
|
|||||||
// Media...
|
// Media...
|
||||||
Route::get('media/banners', [BannerController::class, 'index']);
|
Route::get('media/banners', [BannerController::class, 'index']);
|
||||||
Route::get('media/carousels', [CarouselController::class, 'index']);
|
Route::get('media/carousels', [CarouselController::class, 'index']);
|
||||||
|
Route::get('media/stories', [StoryController::class, 'index']);
|
||||||
|
|
||||||
|
// Flash sales...
|
||||||
|
Route::get('flash-sales', [FlashSaleController::class, 'index']);
|
||||||
|
|
||||||
// Forms...
|
// Forms...
|
||||||
Route::post('forms/newsletter-subscription', [NewsletterSubscriptionController::class, 'store']);
|
Route::post('forms/newsletter-subscription', [NewsletterSubscriptionController::class, 'store']);
|
||||||
|
|||||||
21
tests/Unit/OtpBypassTest.php
Normal file
21
tests/Unit/OtpBypassTest.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Rules\VerificationRule;
|
||||||
|
|
||||||
|
it('normalizes the bypass phone number from international format', function () {
|
||||||
|
expect(normalizeTurkmenPhoneNumber('+99365222548'))->toBe('65222548');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows the approved otp bypass code for the approved phone number', function () {
|
||||||
|
$failed = false;
|
||||||
|
|
||||||
|
(new VerificationRule('+99365222548'))->validate('code', 12212, function () use (&$failed) {
|
||||||
|
$failed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($failed)->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow the otp bypass code for other phone numbers', function () {
|
||||||
|
expect(isOtpBypass('+99365222549', 12212))->toBeFalse();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user