Browse Source

WooooHooooo, Hello again my beloved LARAVEL

pull/2/head
Mohammad Akbari 4 years ago
parent
commit
9ff6e84f83
Signed by: akbarjimi GPG Key ID: 55726AEFECE5E683
  1. 6
      .env.example
  2. 136
      app/Console/Commands/CostCommand.php
  3. 268
      app/Http/Controllers/AuthController.php
  4. 195
      app/Http/Controllers/BusinessController.php
  5. 69
      app/Http/Controllers/CreditController.php
  6. 203
      app/Http/Controllers/FileController.php
  7. 80
      app/Http/Controllers/InvoiceController.php
  8. 159
      app/Http/Controllers/ProjectController.php
  9. 47
      app/Http/Controllers/SprintController.php
  10. 37
      app/Http/Controllers/StatusController.php
  11. 40
      app/Http/Controllers/SystemController.php
  12. 37
      app/Http/Controllers/TagController.php
  13. 89
      app/Http/Controllers/TaskFileController.php
  14. 69
      app/Http/Controllers/UserController.php
  15. 71
      app/Http/Controllers/WorkflowController.php
  16. 34
      app/Http/Resources/BusinessResource.php
  17. 37
      app/Http/Resources/FileResource.php
  18. 34
      app/Http/Resources/FingerprintResource.php
  19. 38
      app/Http/Resources/ProjectResource.php
  20. 32
      app/Http/Resources/TransactionResource.php
  21. 34
      app/Http/Resources/UserResource.php
  22. 10
      app/Models/Activity.php
  23. 486
      app/Models/Business.php
  24. 24
      app/Models/Cost.php
  25. 75
      app/Models/File.php
  26. 30
      app/Models/Fingerprint.php
  27. 204
      app/Models/Project.php
  28. 29
      app/Models/SoftDeletes.php
  29. 73
      app/Models/Sprint.php
  30. 55
      app/Models/Status.php
  31. 69
      app/Models/System.php
  32. 58
      app/Models/Tag.php
  33. 25
      app/Models/Task.php
  34. 164
      app/Models/Transaction.php
  35. 207
      app/Models/User.php
  36. 88
      app/Models/Workflow.php
  37. 21
      app/Providers/AuthServiceProvider.php
  38. 40
      app/Rules/MaxBound.php
  39. 18
      app/Scopes/BusinessScope.php
  40. 15
      app/Utilities/Avatar/DefaultConversionFileNamer.php
  41. 41
      app/Utilities/Avatar/DefaultPathGenerator.php
  42. 43
      app/Utilities/BusinessInfoRequestMixin.php
  43. 83
      app/Utilities/Payload.php
  44. 51
      app/Utilities/RequestMixin.php
  45. 46
      app/Utilities/Zarinpal/Drivers/DriverInterface.php
  46. 197
      app/Utilities/Zarinpal/Drivers/RestDriver.php
  47. 24
      app/Utilities/Zarinpal/Laravel/Facade/Zarinpal.php
  48. 46
      app/Utilities/Zarinpal/Laravel/ZarinpalServiceProvider.php
  49. 130
      app/Utilities/Zarinpal/Zarinpal.php
  50. 15
      composer.json
  51. 4094
      composer.lock
  52. 232
      config.default/app.php
  53. 0
      config.default/auth.php
  54. 0
      config.default/broadcasting.php
  55. 0
      config.default/cache.php
  56. 34
      config.default/cors.php
  57. 0
      config.default/database.php
  58. 72
      config.default/filesystems.php
  59. 0
      config.default/hashing.php
  60. 104
      config.default/logging.php
  61. 0
      config.default/mail.php
  62. 0
      config.default/queue.php
  63. 33
      config.default/services.php
  64. 0
      config.default/session.php
  65. 0
      config.default/view.php
  66. 73
      config/amqp.php
  67. 221
      config/app.php
  68. 43
      config/cors.php
  69. 21
      config/filesystems.php
  70. 172
      config/geoip.php
  71. 98
      config/logging.php
  72. 179
      config/media-library.php
  73. 38
      config/query-builder.php
  74. 40
      config/services.php
  75. 19
      database/factories/BusinessFactory.php
  76. 19
      database/factories/CostFactory.php
  77. 35
      database/factories/FileFactory.php
  78. 38
      database/factories/FingerprintFactory.php
  79. 23
      database/factories/ProjectFactory.php
  80. 16
      database/factories/SprintflowFactory.php
  81. 16
      database/factories/SystemFactory.php
  82. 15
      database/factories/TagFactory.php
  83. 22
      database/factories/TaskFactory.php
  84. 21
      database/factories/TransactionFactory.php
  85. 55
      database/factories/UserFactory.php
  86. 15
      database/factories/WorkflowFactory.php
  87. 15
      database/factories/WorkstatusFactory.php
  88. 0
      database/migrations/.gitkeep
  89. 36
      database/migrations/2019_08_19_000000_create_failed_jobs_table.php
  90. 10
      database/migrations/2020_08_18_085016_create_users_table.php
  91. 38
      database/migrations/2020_08_18_085017_fingerprints.php
  92. 49
      database/migrations/2020_08_18_085018_create_businesses_table.php
  93. 58
      database/migrations/2020_08_18_085046_create_projects_table.php
  94. 13
      database/migrations/2020_08_18_085054_create_workflows_table.php
  95. 39
      database/migrations/2020_08_18_085102_create_statuses_table.php
  96. 41
      database/migrations/2020_08_18_085114_create_tags_table.php
  97. 34
      database/migrations/2020_08_28_095802_create_systems_table.php
  98. 38
      database/migrations/2020_08_28_101545_create_sprint_table.php
  99. 37
      database/migrations/2020_10_31_182018_create_transactions_table.php
  100. 41
      database/migrations/2021_09_03_085114_create_costs_table.php

6
.env.example

@ -1,8 +1,11 @@
APP_NAME=Laravel
CONTAINER_NAME=
APP_NAME=Liwo
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
APP_TIMEZONE="Asia/Tehran"
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_LEVEL=debug LOG_LEVEL=debug
@ -47,3 +50,4 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

136
app/Console/Commands/CostCommand.php

@ -0,0 +1,136 @@
<?php
namespace App\Console\Commands;
use DB;
use App\Business;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
class CostCommand extends Command
{
public const USER_FEE = 400;
public const FILE_FEE = [
200 => 0,
400 => 1000,
800 => 2000,
];
protected $signature = 'cost:work';
protected $description = 'Run the cost worker';
public function __construct()
{
parent::__construct();
}
public function handle()
{
// infinte loop
while (true) {
// to baghali ha
$recorded_month = jdate($business->calculated_at)->format("Y-m-01");
$calculated_at = Cache::get('calculated_at');
if ($calculated_at === null) {
$business = Business::orderBy('calculated_at')->first()->load('users', 'cost');
$calculated_at = $business->calculated_at;
$until_now = jdate()->getMonth() > jdate($business->calculated_at)->getMonth()
? jdate($business->calculated_at)->toCarbon()->setTime("00", "00", "00")
: \Carbon\Carbon::now();
Cache::put('calculated_at', $until_now, 60);
}
// if calculated_at less than an hour stop
if (\Carbon\Carbon::now()->diffInMinutes($until_now) <= 60) {
$this->info('nothing to cost');
continue;
}
try {
DB::beginTransaction();
// business order by last_calculated_at take first
if (!isset($business)) {
$business = Business::orderBy('calculated_at')->first()->load('users', 'cost');
}
$user_fee = enum('business.fee.user');
// get business employee
$users_cost = $business->cost
->where('type', '=', 'users')
->where('fee', '=', $user_fee)
->where('month', '=', $recorded_month)
->where('amount', '=', $business->users->count())
->first();
if ($users_cost === null) {
$business->cost()->create([
'type' => 'users',
'month' => $recorded_month,
'amount' => $business->users->count(),
'fee' => $user_fee,
'duration' => $duration = $until_now->diffInSeconds($calculated_at), // from the created_at time of the newset fifth user
'additional' => $business->users->pluck('id')->toArray(),
]);
} else {
$users_cost->update([
'duration' => $duration = $until_now->diffInMinutes($calculated_at), // last calc - (current month - now else last calc - end of the past month),
'additional' => $business->users->pluck('id')->toArray(),
]);
}
$costs = $user_fee * $duration;
// do the math in php
if (intdiv($business->files_volume, 200) === 0) {
$pads = 0;
} else {
$pads = intdiv($business->files_volume, 200) - 1;
}
$file_fee = enum('business.fee.file');
$files = $business->cost
->where('type', '=', 'files')
->where('fee', '=', $file_fee)
->where('month', '=', $recorded_month)
->where('amount', '=', $business->files_volume)
->first();
if ($files === null) {
$business->cost()->create([
'type' => 'files',
'month' => $recorded_month,
'amount' => $pads,
'fee' => $file_fee,
'duration' => $duration = $until_now->diffInMinutes($calculated_at), // how to determine the file?,
]);
} else {
$files->update([
'duration' => $duration = $until_now->diffInMinutes($calculated_at), // last calc - (current month - now else last calc - end of the past month),,
]);
}
$costs += $file_fee * $duration;
// increment and decrement of wallet in php
// deduct costs from your business wallet
// make sure save the calculated_at
$business->update([
'wallet' => $business->wallet - $costs,
'calculated_at' => \Carbon\Carbon::now(),
]);
DB::commit();
} catch (Throwable $thr) {
DB::rollback();
throw $thr;
continue;
}
}
}
}

268
app/Http/Controllers/AuthController.php

@ -0,0 +1,268 @@
<?php
namespace App\Http\Controllers;
use App\User;
use App\Business;
use App\Fingerprint;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Http\JsonResponse;
use App\Http\Resources\UserResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
use Laravel\Lumen\Routing\Controller;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Session\TokenMismatchException;
use Symfony\Component\HttpFoundation\Response;
class AuthController extends Controller
{
public function redirectToGoogle()
{
return Socialite::driver('google')->stateless()->redirect();
}
public function handleGoogleCallback(Request $request)
{
try {
$user = Socialite::driver('google')->stateless()->user();
$find_user = User::where('email', $user->email)->first();
if ($find_user) {
$find_user->update([
'active' => true
]);
Auth::setUser($find_user);
} else {
$user = User::create($user->user + [
'password' => Hash::make('google-login-user'),
'username' => $user->email,
'active' => true
]);
Auth::setUser($user);
}
$finger_print = $this->createFingerPrint();
return redirect('http://localhost:3000/login?token='.$finger_print->token);
} catch (Exception $e) {
dd($e->getMessage());
}
}
public function login(Request $request)
{
// todo: Logging in from a new device will result in sending a notification
$this->validate($request, [
'email' => 'required|email|exists:users,email',
'password' => 'required|string|min:6'
]);
$user = User::where('email', $request->email)->first();
if ($user && Hash::check($request->password, $user->password)) {
Auth::viaRequest('api', fn() => $user);
return [
'auth' => $this->createFingerPrint(),
'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid))
];
}
return new JsonResponse([
'message' => trans('auth.failed'),
'status' => Response::HTTP_NOT_FOUND,
], Response::HTTP_NOT_FOUND);
}
public function register(Request $request)
{
$this->validate($request, [
'name' => 'required|string|max:225|min:2',
'username' => ['required', Rule::unique('users', 'username')],
'email' => ['required', 'email', Rule::unique('users', 'email')],
'password' => 'required|string|min:8'
]);
$request->merge(['password' => Hash::make($request->password)]);
$code_data = ['verification_code' => $this->sendVerificationCode()];
$method_data = ['method' => 'registerMain'];
Cache::put($request->email, $request->all() + $code_data + $method_data, 3600); // remain one hour
return \response()->json([
'message' => 'Code send for user and user must be verified.'],
Response::HTTP_OK);
}
public function registerMain($user_info)
{
$user = User::create($user_info);
Auth::setUser($user);
return $this->createFingerPrint();
}
public function sendVerificationCode($contact_way = null)
{
$verification_code = 1234; // rand(10001, 99999)
//send code for user with contact way
return $verification_code;
}
public function verification(Request $request)
{
if (!Cache::has($request->email)) {
return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST);
}
$user_info = Cache::get($request->email);
$this->validate($request, [
'email' => 'required|email',
'verification_code' => 'required|string|min:4|max:4|in:'.$user_info['verification_code']
]);
Cache::forget($request->email);
return isset($user_info['method']) ?
call_user_func('self::'.$user_info['method'], $user_info) :
\response()->json(['message' => 'Code verified successfully.'], Response::HTTP_OK,);
}
public function forgetPassword(Request $request)
{
$this->validate($request, [
'email' => 'required|email|exists:users,email'
]);
$code_data = ['verification_code' => $this->sendVerificationCode()];
Cache::put($request->email, $request->all() + $code_data, 3600); // remain one hour
return \response()->json([
'message' => 'Code send for user and user must be verified.'],
Response::HTTP_OK);
}
public function updatePassword(Request $request)
{
if (!Cache::has($request->email)) {
return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST);
}
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|string|min:8|confirmed',
'verification_code' => 'required|string|min:4|max:4|in:'.Cache::get($request->email)['verification_code']
]);
$user = User::where('email', $request->email)->first();
$user->update([
'password' => Hash::make($request->password)
]);
Auth::setUser($user);
return $this->createFingerPrint();
}
/**
* @param Request $request
* @return mixed
* @throws TokenMismatchException
*/
public function logout(Request $request)
{
$token = $request->getCurrentToken();
if (blank($token)) {
return new JsonResponse([
'message' => 'Not authorized request.',
'status' => Response::HTTP_UNAUTHORIZED
]);
}
/** @var Fingerprint $token */
$token = Auth::user()->fingerprints()->firstWhere([
'token' => $token,
]);
if ($token) {
return $token->delete();
}
throw new TokenMismatchException('Invalid token!');
}
/**
* @param string $token
* @throws TokenMismatchException
*/
public function revoke(string $token)
{
/** @var Fingerprint $token */
$token = Auth::user()->fingerprints()->firstWhere([
'token' => $token,
]);
if ($token) {
return $token->delete();
}
throw new TokenMismatchException();
}
public function auth()
{
return new UserResource(Auth::user());
}
public function authWithInfo()
{
return [
'auth' => new UserResource(Auth::user()),
'businesses' => Auth::user()->businesses->keyBy('id') ->map(fn($b, $bid) => Business::info($bid))
];
}
public function delete(Request $request)
{
Auth::user()->fingerprints()->delete();
unset(Auth::user()->token);
Auth::user()->delete();
return 'success';
}
public function createFingerPrint()
{
$attributes = [
'agent' => \request()->getAgent(),
'ip' => \request()->getClientIp(),
'os' => \request()->getOS(),
'latitude' => \request()->getLocation()->getAttribute('lat'),
'longitude' => \request()->getLocation()->getAttribute('lon'),
];
$values = [
'token' => Str::random(60)
];
return Auth::user()->fingerprints()->firstOrCreate($attributes, $attributes + $values);
}
}

195
app/Http/Controllers/BusinessController.php

@ -0,0 +1,195 @@
<?php
namespace App\Http\Controllers;
use App\User;
use App\Business;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class BusinessController extends Controller
{
public function index()
{
return auth()->user()->businesses
->keyBy('id')
->map(fn($b, $bid) => Business::info($bid));
}
public function store(Request $request)
{
// $users = [];
// foreach ($request->users ?? [] as $key => $value) {
// $users[$value] = [];
// }
// $owner = [
// Auth::id() => [
// 'level' => enum('levels.owner.id'),
// ]
// ];
//
// $users = $users + $owner;
//
// $request->merge(['users' => $users]);
$business = Business::create($request->all());
$business->users()->sync([Auth::id() => [
'level' => enum('levels.owner.id'),
]
], false);
return Business::info($business->id);
}
public function show(string $business)
{
permit('businessAccess');
return Business::info($business);
}
public function update(Request $request, string $business)
{
// permit('businessEdit');
$business = Business::findOrFail($business);
$business->fill($request->all())->save();
return Business::info($business->id);
}
public function setAvatar(Request $request, string $business)
{
$business = Business::findOrFail($business);
if ($request->hasFile('avatar')) {
$business->saveAsAvatar($request->file('avatar'));
}
return Business::info($business->id);
}
public function unSetAvatar(Request $request, string $business)
{
$business = Business::findOrFail($business);
$business->deleteAvatar();
return Business::info($business->id);
}
public function info(string $business)
{
return request('_business_info');
}
public function restore(string $business)
{
$business = Business::onlyTrashed()->findOrFail($business);
$business->restore();
return response(['message' => 'business successfully restored.']);
}
public function storeOrUpdateUser($business, Request $request)
{
permit('businessUsers');
$validatedData = $this->validate($request, [
'level' => 'required|numeric|between:0,4',
'user_id' => 'required|numeric|not_in:'.auth()->id(),
]);
DB::transaction(function () use ($validatedData, $request, $business) {
$this->addUser($business, $request->user_id, $validatedData);
if (can('businessAccess', ['user_id'=> $request->user_id])) {
//update
$this->relatedUpdateChanges($request->user_id, $request->level);
}
}, 3);
return Business::info($business, true);
}
public function relatedUpdateChanges($user_id, $level)
{
if ($level == enum('levels.owner.id')) {
// user up level to owner
$this->removeProjectDirectRelation($user_id);
}
if ($level != enum('levels.owner.id') &&
$level > request('_business_info')['info']['users'][$user_id]['level']) {
// user at least up level to $request->level
$this->updateProjectAccessLevel($level, $user_id);
}
}
public function addUser($business, $user, $validatedData)
{
$businessModel = Business::findOrFail($business);
$businessModel->users()->sync([$user => $validatedData], false);
}
public function removeProjectDirectRelation($user)
{
$userModel = User::findOrFail($user);
return $userModel->projects()->sync([], true);
}
public function updateProjectAccessLevel($level, $user)
{
$ids = [];
foreach (request('_business_info')['projects'] as $project_id => $item) {
foreach ($item['members'] as $idx => $member) {
if ($member['id'] == $user && $member['level'] != enum('levels.inactive.id') && $member['level'] < $level) {
$ids[$project_id] = ['level' => $level];
break;
}
}
}
$userModel = User::findOrFail($user);
return $userModel->projects()->sync($ids, false);
}
public function deleteUser($business, $user)
{
permit('businessAccess');
$this->checkDeleteUserPolicy($user);
$businessModel = Business::findOrFail($business);
DB::transaction(function () use ($user, $businessModel) {
$this->detachUser($businessModel, $user);
$this->removeProjectDirectRelation($user);
}, 3);
return Business::info($business, true);
}
public function haveAnotherOwner($user)
{
foreach (request('_business_info')['info']['users'] as $id => $item) {
if ($item['level'] == enum('levels.owner.id') && $id != $user) {
return true;
}
}
return false;
}
public function detachUser($business, $user)
{
return $business->users()->sync(
$business->users->except($user)->pluck('id')->toArray()
);
}
public function checkDeleteUserPolicy($user)
{
if (!can('isBusinessOwner') && auth()->id() != $user ) {
// Non owner user remove another owner
abort(405);
}
if (can('isBusinessOwner') && auth()->id() == $user && !$this->haveAnotherOwner($user)) {
// Owner remove self but business haven't another owner
abort(405);
}
}
}

69
app/Http/Controllers/CreditController.php

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use App\Transaction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;
use App\Http\Resources\TransactionResource;
class CreditController extends Controller
{
public function payments(Request $request, int $business)
{
\permit('isBusinessOwner');
$query = Transaction::where('business_id', $business);
$builder = QueryBuilder::for($query)
->allowedSorts([
'amount',
'succeeded',
'created_at',
])
->allowedFilters([
AllowedFilter::exact('user_id'),
AllowedFilter::exact('succeeded'),
]);
return TransactionResource::collection(
$builder->paginate($request->per_page)
);
}
public function pay(Request $request, int $business)
{
\permit('isBusinessOwner');
return Transaction::create([
'user_id'=> Auth::id(),
'business_id'=> $business,
'amount'=> $request->amount,
]);
}
public function redirection($transaction)
{
$transaction = Transaction::findOrFail($transaction);
if ($transaction->isWentToPaymentGateway()) {
throw new \Exception("Siktir baba ye bar ghablan rafti.");
}
return $transaction->prepare()->redirect();
}
public function callback(Request $request)
{
$transaction = Transaction::findByAuthority($request->get('Authority'))->verify();
if (!$transaction->hasBeenAppliedToWallet() && $transaction->succeeded) {
$transaction->business->increment("wallet", $transaction->amount);
$transaction->amountWasAppliedToWallet();
return true;
}
throw new \Exception("تراکنش تایید نشد");
}
}

203
app/Http/Controllers/FileController.php

@ -0,0 +1,203 @@
<?php
namespace App\Http\Controllers;
use App\File;
use App\Project;
use App\Business;
use App\Rules\maxBound;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use App\HiLib\Resources\FileResource;
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;
use Illuminate\Support\Facades\Storage;
class FileController extends Controller
{
public function index(int $business, Request $request)
{
permit('businessAccess');
$this->indexValidation($request);
$per_page = $request->limit > 100 ? 10 : $request->limit;
return $this->indexFiltering($business)->paginate($per_page);
}
public function indexValidation($request)
{
$bound = 10;
$this->validate($request, [
'filter.project_id' => [new maxBound($bound)] ,
'filter.user_id' => [new maxBound($bound)] ,
]);
}
public function indexFiltering($business)
{
$query = File::where('business_id', $business);
$fileQ = QueryBuilder::for($query)
->allowedFilters([
AllowedFilter::exact('user_id'),
AllowedFilter::exact('project_id'),
AllowedFilter::exact('extension'),
]);
if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) {
$requested_projects = isset(\request('filter')['project_id']) ?
array_unique(explode(',',\request('filter')['project_id'] ?? null )) :
null;
$requested_projects = collect($requested_projects)->keyBy(null)->toArray();
$project_ids = $this->myStateProjects($requested_projects);
$fileQ->where(function ($q) use ($project_ids) {
$q->whereIn('project_id', $project_ids['non_guest_ids'])
->orWhere(function ($q) use ($project_ids) {
$q->whereIn('project_id', $project_ids['guest_ids'])
->where('user_id', auth()->id());
});
});
}
if (request()->filled('group')) {
$fileQ->selectRaw("files.group, count(files.id) as file_count, sum(files.size) as file_size")->groupBy('group');
}
return $fileQ;
}
public function myStateProjects($requested_projects)
{
$non_guest_ids = [];
$guest_ids = [];
$is_empty = empty($requested_projects);
foreach (\request('_business_info')['info']['projects'] as $p_id => $p) {
$level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level'];
if (( $is_empty || isset($requested_projects[$p_id]))
&& $level > enum('levels.guest.id')) {
array_push($non_guest_ids, $p_id);
}
if (( $is_empty || isset($requested_projects[$p_id]))
&& $level == enum('levels.guest.id')) {
array_push($guest_ids, $p_id);
}
}
return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids];
}
public function store(Request $request, int $business, int $project)
{
// different size and different validation
// validate
// validate the wallet is not so much in debt
// create record in the db
// put file in s3
// return file resource
$business = Business::findOrFail($business);
$project = Project::findOrFail($project);
// $this->validate($request, ['file' => 'required|file',]);
$file = $request->file('file');
$file_extension = $file->getClientOriginalExtension();
$file_name = Str::random(40).'.'.$file_extension;
$file->storeAs(
$business->id.\DIRECTORY_SEPARATOR.$project->id,
$file_name,
's3'
);
$file_record = File::create([
'user_id' => Auth::id(),
'business_id' => $business->id,
'project_id' => $project->id,
'disk' => 's3', // default disk
'original_name' => $file->getClientOriginalName(),
'extension' => $file_extension,
'name' => $file_name,
'mime' => $file->getClientMimeType(),
'group' => $this->groupDetection($file),
'size' => $file->getSize(),
'description' => $request->description
]);
return new FileResource($file_record);
}
public function groupDetection(UploadedFile $file)
{
// Media files like mp4, mp3, wma and png or jpeg
[$type, $subtype] = Str::of($file->getMimeType())->explode("/",2)->pad(2, null);
if (in_array($type, ['audio', 'video', 'image'])) {
return $type;
}
// Covert string to \Illuminate\Support\Stringable object
$subtype = Str::of($subtype);
// PDF files
if ($subtype->contains(["pdf"])) {
return "pdf";
}
// Compressed files like zip, cab, rar, etc.
if ($subtype->contains(['compressed']) || in_array($file->getClientOriginalExtension(), ['zip', 'rar','7z','cab'])) {
return "compressed";
}
// Office files like xls, xlsx, doc, docx, etc.
if ($subtype->contains(['vnd.ms', 'vnd.sealed', 'officedocument', 'opendocument'])) {
return "office";
}
// Non of the above files
return "other";
}
public function download(int $business, int $project, int $file)
{
// requested file belongs to this project and this business
// check permisson
// create perma link or temp link
// return the file resource or stream it
return File::findOrFail($file)->getTemporaryLink();
}
public function rename(Request $request, int $business, int $project, int $file)
{
// requested file belongs to this project and this business
// check permisson
// update original name
// return the file resource
// sanitize the name for slashs and back slashes
$this->validate($request, [
'name' => 'required|string'
]);
$file = File::findOrFail($file);
$file->update(['original_name' => $request->name.".".$file->extension]);
return new FileResource($file);
}
public function delete(Request $request, int $business, int $project, int $file)
{
// requested file belongs to this project and this business
// check permisson
// check it's relations
// delete the file form File table
// delete file from s3
$file = File::findOrFail($file);
Storage::disk('s3')->delete($file->getPath());
return $file->delete();
}
}

80
app/Http/Controllers/InvoiceController.php

@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers;
use App\Cost;
use App\Business;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;
class InvoiceController extends Controller
{
public function index(Request $request, int $business)
{
$business = Business::findOrFail($business);
$builder = Cost::select('month')
->selectRaw("concat_ws('-',business_id,month) as factor_id")
->selectRaw("MIN(created_at) as begin")
->selectRaw("MAX(updated_at) as end")
->selectRaw("sum(cost) as cost")
->selectRaw("sum(tax) as tax")
->where('business_id','=',$business->id)
->groupBy('month','factor_id');
$costs = QueryBuilder::for($builder)
->allowedSorts([
'factor_id',
'begin',
'end',
'cost',
])
->allowedFilters([
AllowedFilter::exact('month'),
AllowedFilter::exact('type'),
]);
return $costs->paginate($request->per_page);
}
public function indexFiltering($business)
{
$query = File::where('business_id', $business);
$fileQ = QueryBuilder::for($query)
->allowedFilters([
AllowedFilter::exact('user_id'),
AllowedFilter::exact('project_id'),
AllowedFilter::exact('extension'),
]);
if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) {
$requested_projects = isset(\request('filter')['project_id']) ?
array_unique(explode(',', \request('filter')['project_id'] ?? null)) :
null;
$requested_projects = collect($requested_projects)->keyBy(null)->toArray();
$project_ids = $this->myStateProjects($requested_projects);
$fileQ->where(function ($q) use ($project_ids) {
$q->whereIn('project_id', $project_ids['non_guest_ids'])
->orWhere(function ($q) use ($project_ids) {
$q->whereIn('project_id', $project_ids['guest_ids'])
->where('user_id', auth()->id());
});
});
}
if (request()->filled('group')) {
$fileQ->selectRaw("files.group, count(files.id) as file_count, sum(files.size) as file_size")->groupBy('group');
}
return $fileQ;
}
public function show(Request $request, int $business, string $date)
{
return Cost::where('business_id', '=', $business)
->where("month","=",$date)
->get();
}
}

159
app/Http/Controllers/ProjectController.php

@ -0,0 +1,159 @@
<?php
namespace App\Http\Controllers;
use App\Project;
use App\Business;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ProjectController extends Controller
{
public function index(Request $request, int $business)
{
// permit('businessAccess');
return Project::where('business_id', $business)->get();
}
public function store(Request $request, string $business)
{
permit('businessProjects');
Project::create($request->merge(['business_id' => $business])->all());
return Business::info($request->route('business'), true);
}
public function update(Request $request,string $project)
{
permit('projectEdit', ['project_id' => $project]);
$project = Project::findOrFail($project);
$project->update($request->except('business_id'));
return Business::info($request->route('business'), true);
}
public function delete(Request $request, string $project)
{
permit('businessProjects');
$project = Project::findOrFail($project);
$project->delete();
return Business::info($request->route('business'));
}
public function restore(Request $request, string $project)
{
$project = Project::onlyTrashed()->findOrFail($project);
$project->restore();
return response(['message' => 'project successfully restored.']);
}
public function storeOrUpdateUser($business, $project, Request $request)
{
permit('projectUsers', ['project_id' => $project]);
$validatedData = $this->validate($request, [
'level' => 'required|numeric|between:1,3',
'user_id' => 'required|numeric|not_in:'.auth()->id(),
]);
$this->checkAddUserPolicy($request->user_id, $request->level);
$projectModel = Project::findOrFail($project);
DB::transaction(function () use ($business, $validatedData, $request, $projectModel) {
$projectModel->members()->sync([$request->user_id => $validatedData], false);
if (!can('businessAccess', ['user_id' => $request->user_id])) {
// Register user to business with zero level
//User not exist in the business before
$this->addUserWithZeroLevel($request->user_id, $business);
}
}, 3);
return Business::info($projectModel->business_id, true);
}
public function checkAddUserPolicy($user, $level)
{
if (can('businessAccess', ['user_id' => $user])
&& $level < request('_business_info')['info']['users'][$user]['level']) {// before in business
abort(405);
}
}
public function addUserWithZeroLevel($user_id, $business)
{
$businessModel = Business::findOrFail($business);
return $businessModel->users()->sync([$user_id => [
'level' => 0,
'user_id' => $user_id
]], false);
}
public function deleteUser($business, $project, $user)
{
permit('projectAccess', ['project_id' => $project]);
$this->checkDeleteUserPolicy($user, $project);
$projectModel = Project::findOrFail($project);
DB::transaction(function () use ($project, $business, $user, $projectModel) {
$this->detachMember($projectModel, $user);
if (!can('isActiveUser', ['user_id' => $user]) && !$this->haveOneProject($user, $project)) {
// User level in business is zero
// And haven't another project then remove it form business
$businessModel = Business::findOrFail($business);
$this->detachUser($businessModel, $user);
}
}, 3);
return Business::info($projectModel->business_id, true);
}
public function detachMember($project, $user)
{
return $project->members()->detach($user) ? true : abort(404);
}
public function detachUser($business, $user)
{
return $business->users()->detach($user) ? true : abort(404);
}
public function haveOneProject($user, $project)
{
foreach (request('_business_info')['info']['projects'] as $id => $item) {
if ($item['members'][$user]['level'] > enum('levels.inactive.id') && $id != $project) {
return true;
}
}
return false;
}
public function checkDeleteUserPolicy($user, $project)
{
if (!can('isProjectOwner', ['project_id' => $project]) && (auth()->id() != $user) ) {
abort(405);
}
}
public function setAvatar(Request $request, string $project)
{
$project = Project::findOrFail($project);
if ($request->hasFile('avatar')) {
$project->saveAsAvatar($request->file('avatar'));
}
return $project;
}
public function unSetAvatar(Request $request, string $project)
{
$project = Project::findOrFail($project);
$project->deleteAvatar();
return $project;
}
}

47
app/Http/Controllers/SprintController.php

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers;
use App\Business;
use App\Sprint;
use Illuminate\Http\Request;
class SprintController extends Controller
{
public function store($business, $project, Request $request)
{
permit('projectSprints', ['project_id' => $project]);
Sprint::create($request->merge(
['business_id' => $business, 'project_id' => $project]
)->except('_business_info'));
return Business::info($business, true);
}
public function update($business, $project, $sprint, Request $request)
{
permit('projectSprints', ['project_id' => $project]);
$sprint = Sprint::findOrFail($sprint);
$sprint->update($request->except('_business_info'));
return Business::info($business, true);
}
public function delete($business, $project, $sprint)
{
permit('projectSprints', ['project_id' => $project]);
$sprint = Sprint::findOrFail($sprint);
$sprint->delete();
return Business::info($business, true);
}
}

37
app/Http/Controllers/StatusController.php

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Business;
use App\Status;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\Rule;
class StatusController extends Controller
{
public function store($business, $workflow, Request $request)
{
permit('businessStatuses');
Status::create($request->merge(['business_id' => $business, 'workflow_id' => $workflow])->except('_business_info'));
return Business::info($business, true);
}
public function update($business, $workflow, $status, Request $request)
{
permit('businessStatuses');
$status = Status::findOrFail($status);
$status->update($request->except('_business_info'));
return Business::info($business, true);
}
public function delete($business, $workflow, $status)
{
permit('businessStatuses');
$status = Status::findOrFail($status);
$status->delete();
return Business::info($business, true);
}
}

40
app/Http/Controllers/SystemController.php

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers;
use App\Business;
use App\System;
use Illuminate\Http\Request;
class SystemController extends Controller
{
public function store($business, $project, Request $request)
{
permit('projectSystems', ['project_id' => $project]);
System::create([
'business_id' => $business,
'project_id' => $project,
'name' => $request->name
]);
return Business::info($business, true);
}
public function update($business, $project, $system, Request $request)
{
permit('projectSystems', ['project_id' => $project]);
$system = System::findOrFail($system);
$system->update($request->except('_business_info'));
return Business::info($business, true);
}
public function delete($business, $project, $system)
{
permit('projectSystems', ['project_id' => $project]);
$system = System::findOrFail($system);
$system->delete();
return Business::info($business, true);
}
}

37
app/Http/Controllers/TagController.php

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Business;
use App\Tag;
use App\Workflow;
use Illuminate\Http\Request;
class TagController extends Controller
{
public function store($business, Request $request)
{
permit('businessTags');
Tag::create($request->merge(['business_id' => $business])->except('_business_info'));
return Business::info($business, true);
}
public function update($business, $tag, Request $request)
{
permit('businessTags');
$tag = Tag::findOrFail($tag);
$tag->update($request->except('_business_info'));
return Business::info($business, true);
}
public function delete($business, $tag)
{
permit('businessTags');
$tag = Tag::findOrFail($tag);
$tag->delete();
return Business::info($business, true);
}
}

89
app/Http/Controllers/TaskFileController.php

@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers;
use Auth;
use App\File;
use App\Task;
use App\Project;
use App\Business;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\FileResource;
use mysql_xdevapi\Exception;
use Symfony\Component\HttpFoundation\Response;
class TaskFileController extends Controller
{
public function checkBelonging(int $business, int $project, int $task)
{
$business = Business::findOrFail($business);
$project = Project::findOrFail($project);
$task = Task::find($task);
if (
$business->id !== $project->business_id
|| $project->id !== $task['project_id']
// || $task['user_id']!== Auth::id()
) {
\abort(Response::HTTP_UNAUTHORIZED);
}
return [$business, $project, $task];
}
public function index(int $business, int $project, int $task)
{
// check permissions
// owner project
// admin project
// colleague project
// guest or de active
// return files as file resource
[$business, $project, $task] = $this->checkBelonging($business, $project, $task);
return FileResource::collection($task->files);
}
public function sync(Request $request,int $business, int $project, int $task)
{
// different size and different validation
// validate
// validate the wallet is not so much in debt
// create record in the db
// put file in s3
// return file resource
[$business, $project, $task] = $this->checkBelonging($business,$project,$task);
$this->validate($request, [
'files' => 'required|array',
'files.*' => 'int',
]);
$files = File::find($request->files)->each(function (File $file) {
if ($file->user_id !== Auth::id()) {
abort(Response::HTTP_UNAUTHORIZED);
}
});
// sync
return FileResource::collection($files);
}
public function download(int $business, int $project, int $task, int $file)
{
// requested file belongs to this project and this business
// check permisson
// create perma link or temp link
// return the file resource or stream it
[$business, $project, $task] = $this->checkBelonging($business, $project, $task);
$file = File::find($file);
if ($file->user_id !== Auth::id()) {
abort(Response::HTTP_UNAUTHORIZED);
}
return $file->getTemporaryLink();
}
}

69
app/Http/Controllers/UserController.php

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
class UserController extends Controller
{
public function search(Request $request)
{
$limit = 20;
$userQ = User::query();
if ($request->filled('search')){
$userQ = $userQ->where(function($query) use ($request) {
$query->where('email', 'like', '%'.$request->search.'%')
->orWhere('username', 'like', '%'.$request->search.'%');
});
}
return $userQ->where('id', '!=', auth()->id())
->whereNotIn('id', request('_business_info')['info']['users']->keys())
->select('id', 'name', 'email', 'username')->take($limit)->get();
}
public function index(Request $request)
{
$userQ = QueryBuilder::for(User::class)
->allowedFilters([
AllowedFilter::exact('id'),
]);
return $userQ->select('id', 'name', 'email', 'username')->get();
}
public function show($user)
{
return User::select('id', 'name', 'email', 'username')->findOrFail($user);
}
public function update(Request $request, string $user)
{
$user = User::findOrFail($user);
$user->update($request->all());
return $user;
}
public function setAvatar(Request $request, string $user)
{
$user = User::findOrFail($user);
if ($request->hasFile('avatar')) {
$user->saveAsAvatar($request->file('avatar'));
}
return $user;
}
public function unSetAvatar(Request $request, string $user)
{
$user = User::findOrFail($user);
$user->deleteAvatar();
return $user;
}
}

71
app/Http/Controllers/WorkflowController.php

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use App\Business;
use App\Status;
use App\Workflow;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class WorkflowController extends Controller
{
public function store($business, Request $request)
{
permit('businessWorkFlows');
Workflow::create($request->merge(['business_id' => $business])->except('_business_info'));
// $statuses = collect($validatedData['statuses'])->map(function($status) use ($workflow, $business) {
// $status['business_id'] = $business;
// $status['workflow_id'] = $workflow->id;
// return $status;
// });
// $statuses = $workflow->statuses()->createMany($statuses->toArray());
return Business::info($business, true);
}
public function update($business, $workflow, Request $request)
{
permit('businessWorkFlows');
$workflowModel = Workflow::findOrFail($workflow);
$workflowModel->update($request->except('_business_info'));
return Business::info($business, true);
}
public function syncStatus($business, $workflowModel)
{
$old_statuses_name = array_keys(collect(\request('_business_info')['workflows'][$workflowModel->id]['statuses'])->keyBy('name')->toArray());
$new_statuses_name = array_keys(collect(\request('statuses'))->keyBy('name')->toArray());
$removed_statuses_name = array_diff(array_merge($old_statuses_name, $new_statuses_name), $new_statuses_name);
foreach ($removed_statuses_name as $status_name) {
//delete all statuses that removed name's from request->statuses
$workflowModel->statuses()->where('name', $status_name)->first()->delete();
}
foreach (request('statuses') as $status) {
//sync another statuses
$workflowModel->statuses()
->updateOrCreate(
['name' => $status['name'], 'business_id' => $business, 'workflow_id' => $workflowModel->id],
['state' => $status['state'], 'order' => $status['order']]
);
}
return $workflowModel;
}
public function delete($business, $workflow)
{
permit('businessWorkFlows');
$workflow = Workflow::findOrFail($workflow);
foreach ($workflow->statuses as $status) {
//delete all statuses related to this workflow
$status->delete();
}
$workflow->delete();
return Business::info($business, true);
}
}

34
app/Http/Resources/BusinessResource.php

@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BusinessResource extends JsonResource
{
public function toArray($request)
{
$resource = [
'_service' => 'user',
'_resource' => 'businesses',
];
foreach ($this->getAttributes() as $attribute => $value) {
switch ($attribute) {
case 'name':
case 'slug':
case 'wallet':
case 'files_volume':
case 'cache':
case 'calculated_at':
case 'created_at':
case 'updated_at':
case 'deleted_at':
$resource[$attribute] = $value;
break;
}
}
return $resource;
}
}

37
app/Http/Resources/FileResource.php

@ -0,0 +1,37 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class FileResource extends JsonResource
{
public function toArray($request)
{
$resource = [
'_service' => 'user',
'_resource' => 'files',
];
foreach ($this->getAttributes() as $attribute => $value) {
switch ($attribute) {
case 'user_id':
case 'business_id':
case 'project_id':
case 'disk':
case 'original_name':
case 'name':
case 'extension':
case 'mime':
case 'size':
case 'description':
$resource[$attribute] = $value;
break;
}
}
return $resource;
}
}

34
app/Http/Resources/FingerprintResource.php

@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class FingerprintResource extends JsonResource
{
public function toArray($request)
{
$resource = [
'_service' => 'user',
'_resource' => 'fingerprints',
];
foreach ($this->getAttributes() as $attribute => $value) {
switch ($attribute) {
case 'id':
case 'agent':
case 'ip':
case 'os':
case 'latitude':
case 'longitude':
case 'token':
$resource[$attribute] = $value;
break;
}
}
return $resource;
}
}

38
app/Http/Resources/ProjectResource.php

@ -0,0 +1,38 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProjectResource extends JsonResource
{
public function toArray($request)
{
$resource = [
'_service' => 'user',
'_resource' => 'project',
];
foreach ($this->getAttributes() as $attribute => $value) {
switch ($attribute) {
case 'name':
case 'business_id':
case 'slug':
case 'private':
case 'budget':
case 'start':
case 'finish':
case 'created_at':
case 'updated_at':
case 'archived_at':
case 'deleted_at':
$resource[$attribute] = $value;
break;
}
}
return $resource;
}
}

32
app/Http/Resources/TransactionResource.php

@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class TransactionResource extends JsonResource
{
public function toArray($request)
{
$resource = [
'_service' => 'user',
'_resource' => 'transactions',
];
foreach ($this->getAttributes() as $attribute => $value) {
switch ($attribute) {
case 'user_id':
case 'business_id':
case 'amount':
case 'succeeded':
case 'options':
case 'created_at':
case 'updated_at':
$resource[$attribute] = $value;
break;
}
}
return $resource;
}
}

34
app/Http/Resources/UserResource.php

@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray($request)
{
$resource = [
'_service' => 'user',
'_resource' => 'user',
];
foreach ($this->getAttributes() as $attribute => $value) {
switch ($attribute) {
case 'id':
case 'name':
case 'mobile':
case 'email':
case 'created_at':
case 'updated_at':
$resource[$attribute] = $value;
break;
}
}
$resource['includes']['fingerprints'] = $this->whenLoaded('fingerprints', function () {
return FingerprintResource::collection($this->fingerprints);
});
return $resource;
}
}

10
app/Models/Activity.php

@ -0,0 +1,10 @@
<?php
namespace App;
use App\HiLib\Models\RemoteModel;
class Activity extends RemoteModel
{
}

486
app/Models/Business.php

@ -0,0 +1,486 @@
<?php
namespace App;
use App\File;
use App\SoftDeletes;
use App\HiLib\Models\Model;
use Illuminate\Validation\Rule;
use Illuminate\Http\UploadedFile;
use Spatie\MediaLibrary\HasMedia;
use Illuminate\Support\Facades\Cache;
use App\HiLib\Models\ReportableRelation;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class Business extends Model implements HasMedia
{
use SoftDeletes,
InteractsWithMedia;
const CONVERSION_NAME = 'avatar';
const COLLECTION_NAME = 'avatars';
public static $permissions = ['level'];
protected $table = 'businesses';
protected $fillable = ['name', 'slug', 'wallet','files_volume','cache', 'color', 'description', 'has_avatar', 'calculated_at', 'users'];
protected $fillable_relations = [
'users'
];
protected $reportable = [
'name', 'slug', 'wallet', 'files_volume', 'cache', 'calculated_at', // fields
['users' => 'business_user'] // relations [ name => pivot ]
];
public $detach_relation = false;
protected $casts = [
'has_avatar' => 'boolean',
];
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->id,
'workflow_id' => null,
'project_id' => null,
'sprint_id' => null,
'status_id' => null,
'system_id' => null,
'user_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function rules()
{
return [
'name' => 'bail|required|string|min:2|max:255',
'color' => 'nullable|string|min:2|max:255',
'slug' => ['bail', 'required', 'string', 'min:2', 'max:255', 'regex:/^[a-zA-Z]+[a-zA-Z\d-]*(.*[a-zA-Z\d])+$/',
Rule::unique($this->table, 'slug')->ignore($this->id)],
'description' => 'nullable|string|min:2|max:1000',
];
}
public function cost()
{
return $this->hasMany(Cost::class, 'business_id','id');
}
public function owners()
{
return $this->users()->wherePivot('level', '=', enum('levels.owner.id'));
}
public function members()
{
return $this->users()->wherePivot('owner', '!=', enum('levels.owner.id'));
}
public function users()
{
return $this->belongsToMany(
User::class, 'business_user', 'business_id', 'user_id',
'id', 'id', __FUNCTION__
)
->using(ReportableRelation::class)
->withPivot(self::$permissions);
}
public function tags()
{
return $this->hasMany(Tag::class, 'business_id', 'id');
}
public function projects()
{
return $this->hasMany(Project::class, 'business_id', 'id');
}
public function systems()
{
return $this->hasMany(System::class, 'business_id', 'id');
}
public function workflows()
{
return $this->hasMany(Workflow::class, 'business_id', 'id');
}
public function sprints()
{
return $this->hasMany(Sprint::class, 'business_id', 'id');
}
public function statuses()
{
return $this->hasMany(Status::class, 'business_id', 'id');
}
public function files()
{
return $this->hasMany(File::class, 'user_id', 'id');
}
public function transactions()
{
return $this->hasMany(Transaction::class, 'business_id', 'id');
}
public function updateRelations()
{
// users relations
if (!empty($this->filled_relations['users']) || $this->detach_relation) {
$this->dirties['users'] = $this->users()->sync($this->filled_relations['users'], $this->detach_relation);
}
}
public static function info($businessId, $fresh = false)
{
$info = [];
$fresh = true;
if ($fresh){
Cache::forget('business_info'.$businessId);
}
if (Cache::has('business_info'.$businessId)) {
return Cache::get('business_info'.$businessId);
} else {
$business = self::findOrFail($businessId);
$tags = $business->tags()->select('id', 'label', 'color')->get()->keyBy('id');
$info['tags'] = $tags->pluck('id');
$workflows = $business->workflows()->select ('id', 'business_id', 'name','desc')->get()->keyBy('id')
->load(['statuses'=> fn($q) => $q->select('id', 'business_id', 'workflow_id', 'name', 'state', 'order')])
->map(fn($q) => [
'id' => $q->id,
'business_id' => $q->business_id,
'name' => $q->name,
'desc' => $q->desc,
'statuses' => $q['statuses']->keyBy('id'),
]);
$info['workflows'] = $workflows->map(fn($q) => ['statuses' => $q['statuses']->pluck('id')]);
$users = $business->users()->select('id', 'name', 'email', 'mobile', 'username')->with('media')->get()
->keyBy('id')
->map(fn($u) => [
'id' => $u->id,
'name' => $u->name,
'email' => $u->email,
'mobile' => $u->mobile,
'username' => $u->get,
'avatar' => $u->has_avatar,
'level' => $u->pivot['level'],
]);
$info['users'] = $users->map(fn($u) => [
'level' => $u['level']
]);
$projects = $business->projects()->get()->keyBy('id')->load([
'members' => fn($q) => $q->select('id', 'level'),
'systems' => fn($q) => $q->select('id', 'project_id', 'name'),
'sprints' => fn($q) => $q->select('id', 'project_id', 'name', 'description', 'started_at', 'ended_at', 'active'),
'media',
]);
$info['projects'] = $projects->map(function($q) use($users){
return [
'avatar' => $q->has_avatar,
'systems' => $q->systems->pluck('id'),
'sprints' => $q->sprints->pluck('id'),
'members' => $users->keyBy('id')->map(function($u, $uid) use ($q){
$project_user = $q->members->firstWhere('id', $uid);
return $project_user? ['level' => $project_user->pivot->level, 'is_direct' => true]:
['level' => $q->private && $u['level'] != enum('levels.owner.id') ? enum('levels.inactive.id') : $u['level'],
'is_direct'=> $project_user ? true : false];
})
];
});
$business_info = array_merge(
$business->only('id', 'name', 'slug', 'color','wallet', 'files_volume'),
['avatar' => $business->has_avatar],
compact(
'info',
'tags',
'workflows',
'users',
'projects'
)
);
Cache::put('business_info'.$businessId , $business_info, config('app.cache_ttl'));
return $business_info;
}
}
public static function stats()
{
return [
'users' => [
10 => [
'statuses' => [
10 => 20,
30 => 40
],
'workflows' => [
10 => 20,
30 => 40
],
'tags' => [
10 => 20,
30 => 40
],
'project' => [
10 => 20,
30 => 40
],
'sprints' => [
10 => 20,
30 => 40
],
'works' => [
],
'__subsystems' => [
10 => 20,
30 => 40
],
]
],
'workflows' => [
50 => [
'statuses' => [
20 => 50
],
]
],
'statuses' => [
10 => [
'users' => [
],
'projects' => [
]
]
],
'sprints' => [
10 => [
'statuses' => [
10 => [
10 => 1
]
]
]
]
];
}
public function reportActivity()
{
}
public static function nuxtInfo($businessId)
{
if (empty($businessId)){
return null;
}
Cache::forget('business_nuxt_info' . $businessId);
return Cache::rememberForever('business_nuxt_info' . $businessId, function () use ($businessId) {
$business = self::with([
'projects.members' => fn($q) => $q->select('id', 'name'),
'projects',
'tags',
'workflows.statuses',
'workflows',
'statuses',
'users',
])->findOrFail($businessId);
$globals = [];
$business->users->each(function ($u) use (&$globals) {
$globals[$u->id] = $u->pivot->owner == true ? ['owner' => true] : $u->pivot->only(self::$permissions);
});
$projects = [];
$business->projects->each(function ($p) use (&$projects, &$globals) {
});
$business->setRelation('projects', collect($projects));
$business->setRelation('users', collect($globals));
return $business;
});
}
public static function infoOld($businessId)
{
if (empty($businessId))
return null;
Cache::forget('business_info' . $businessId);
return Cache::rememberForever('business_info' . $businessId, function () use ($businessId) {
$ob = [];
$business = self::with([
'projects.members' => fn($q) => $q->select('id', 'name'),
'projects' => fn($q) => $q->select('id', 'business_id', 'private'),
'tags' => fn($q) => $q->select('id', 'business_id', 'label'),
'sprints' => fn($q) => $q->select('id','business_id','name', 'active'),
'workflows.statuses' => fn($q) => $q->select('id', 'name'),
'workflows' => fn($q) => $q->select('id', 'business_id', 'name'),
'statuses' => fn($q) => $q->select('id', 'business_id', 'name', 'state'),
'users' => fn($q) => $q->select('id', 'name'),
])->findOrFail($businessId);
// permissions in business
$permissions = [];
$business->users->each(function ($user) use (&$permissions) {
$permissions[$user->id] = $user->pivot->only(['owner']);
$permissions[$user->id]['global_PA'] = $permissions[$user->id]['owner'] == true ?
enum('roles.manager.id') : $user->pivot->PA;
$permissions[$user->id]['global_FA'] = $permissions[$user->id]['owner'] == true ?
enum('roles.manager.id') : $user->pivot->FA;
$permissions[$user->id]['PA'] = [];
$permissions[$user->id]['FA'] = [];
});
//projects
$projects = [];
$business->projects->each(function ($p) use (&$permissions, $business, &$projects) {
$business->users->each(function ($user) use (&$permissions, $p) {
$PA = null;
$FA = null;
if ($permissions[$user->id]['owner'] == true) {
$PA = enum('roles.manager.id');
$FA = enum('roles.manager.id');
} else if (!empty($project_user = $p->getPermissions($user->id))) {
$PA = $project_user->pivot->PA;
$FA = $project_user->pivot->FA;
} else if (empty($p->getPermissions($user->id))) {
$PA = $p->private == false ? $permissions[$user->id]['global_PA'] : enum('roles.hidden.id') ;
$FA = $p->private == false ? $permissions[$user->id]['global_FA'] : enum('roles.hidden.id');
}
if ($PA !== null && $FA !== null) {
$permissions[$user->id]['PA'][$p->id] = $PA;
$permissions[$user->id]['FA'][$p->id] = $FA;
}
});
$projects[$p->id] ='';
});
//workflow
$workflows = [];
$business->workflows->each(function ($w) use (&$workflows) {
$workflows[$w->id] = $w->statuses->pluck('id');
});
$ob['tags'] = $business->tags->pluck('id');
$ob['statuses'] = $business->statuses;
$ob['sprints'] = $business->sprints;
$ob['workflows'] = $workflows;
$ob['users'] = $permissions;
$ob['projects'] = $projects;
return collect($ob);
});
}
public function registerMediaCollections(): void
{
$this->addMediaCollection(static::COLLECTION_NAME)
->acceptsMimeTypes([
'image/jpeg',
'image/png',
'image/tiff',
'image/gif',
])
->useDisk('public')
->singleFile();
}
public function registerMediaConversions(Media $media = null): void
{
$this->addMediaConversion(static::CONVERSION_NAME)
->width(200)
->height(200)
->queued()
->nonOptimized()
->performOnCollections(static::COLLECTION_NAME);
}
public function saveAsAvatar(UploadedFile $avatar): void
{
$this->addMedia($avatar)->toMediaCollection(static::COLLECTION_NAME);
$this->update([
'has_avatar' => true,
]);
@unlink($this->getFirstMedia(static::COLLECTION_NAME)->getPath());
}
public function deleteAvatar(): void
{
$path = $this->getFirstMedia(static::COLLECTION_NAME)->getPath();
$this->getFirstMedia(static::COLLECTION_NAME)->delete();
$this->update([
'has_avatar' => false,
]);
@unlink($path);
}
public function getAvatarUrl(): ?string
{
if ($url = $this->getFirstMediaUrl(static::COLLECTION_NAME, static::CONVERSION_NAME)) {
return $url;
}
return null;
}
}

24
app/Models/Cost.php

@ -0,0 +1,24 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Cost extends Model
{
public $perPage = 12;
protected $fillable = [
'business_id', 'type', 'month', 'amount', 'fee', 'duration','cost','tax', 'additional'
];
public $casts = [
'additional' => 'array',
'tax' => 'float',
];
public function business()
{
return $this->belongsTo(Business::class, 'business_id');
}
}

75
app/Models/File.php

@ -0,0 +1,75 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use Illuminate\Support\Facades\Storage;
class File extends Model
{
protected $table = 'files';
protected $fillable = [
'user_id', 'business_id', 'project_id', 'disk', 'original_name', 'name',
'extension', 'mime', 'group','size', 'description'
];
protected $casts = [];
public function rules()
{
return [
'user_id' => 'required',
'business_id' => 'required',
'project_id' => 'required',
'original_name' => 'required',
'name' => 'required',
'extension' => 'required',
'size' => 'required',
'description' => 'nullable',
];
}
public function user()
{
return $this->belongsTo(User::class, 'user_id','id','id',__FUNCTION__);
}
public function business()
{
return $this->belongsTo(Business::class, 'business_id', 'id', 'id', __FUNCTION__);
}
public function project()
{
return $this->belongsTo(Project::class, 'project_id', 'id', 'id', __FUNCTION__);
}
public function updateRelations()
{
}
public function reportActivity()
{
}
public function getPath()
{
return $this->business->id . \DIRECTORY_SEPARATOR . $this->project->id . DIRECTORY_SEPARATOR . $this->name;
}
public function getTemporaryLink()
{
return Storage::disk('s3')->temporaryUrl(
$this->getPath(),
\Carbon\Carbon::now()->addMinutes(15),
[
'Content-Type' => $this->mime,
'Content-Disposition' => $this->original_name,
]
);
}
}

30
app/Models/Fingerprint.php

@ -0,0 +1,30 @@
<?php
namespace App;
use App\HiLib\Models\Model;
class Fingerprint extends Model
{
protected $fillable = ['user_id', 'agent', 'ip', 'os', 'latitude', 'longitude', 'token',];
protected $table = 'fingerprints';
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id', __FUNCTION__);
}
public function rules()
{
return [
'user_id' => 'required|integer|exists:users,id',
'agent' => 'required|string',
'ip' => 'required|ip',
'os' => 'required|string',
'latitude' => 'required',
'longitude' => 'required',
'token' => 'required|string|min:60',
];
}
}

204
app/Models/Project.php

@ -0,0 +1,204 @@
<?php
namespace App;
use App\File;
use App\SoftDeletes;
use App\HiLib\Models\Model;
use Illuminate\Validation\Rule;
use Illuminate\Http\UploadedFile;
use Spatie\MediaLibrary\HasMedia;
use App\HiLib\Models\ReportableRelation;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class Project extends Model implements HasMedia
{
use SoftDeletes,
InteractsWithMedia;
const CONVERSION_NAME = 'avatar';
const COLLECTION_NAME = 'avatars';
public static $permissions = ['level'];
protected $table = 'projects';
protected $fillable = [
'business_id', 'name', 'slug', 'private', 'budget', 'color', 'active', 'description', 'has_avatar', 'start', 'finish','members'
];
protected $reportable = [
'business_id', 'name', 'slug', ['members' => 'project_user']
];
protected $fillable_relations = ['members'];
public $detach_relation = false;
public function rules()
{
return [
'name' => 'required|string|min:2|max:225',
'slug' => ['required', 'string', 'min:2', 'max:225',
Rule::unique($this->table, 'slug')
->where('business_id', request('business_id'))
->whereNull('deleted_at')
->ignore($this->id)],
'order' => 'nullable|numeric|min:0',
'private' => 'nullable|boolean',
'color' => 'nullable|string|min:2|max:255',
'active' => 'nullable|boolean',
'description' => 'nullable|string|min:2|max:1000',
// 'members' => empty($this->id) ? '' : 'required|array'
];
}
protected $casts = [
'private' => 'boolean',
'start' => 'date',
'finish' => 'date',
'has_avatar' => 'boolean',
];
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => $this->id,
'sprint_id' => null,
'workflow_id' => null,
'status_id' => null,
'system_id' => null,
'actor_id' => auth()->id(),
'user_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function members()
{
$permissions = self::$permissions;
return $this->belongsToMany(
User::class, 'project_user', 'project_id', 'user_id',
'id', 'id', __FUNCTION__
)->using(ReportableRelation::class)
->withPivot($permissions);
}
public function tasks()
{
return $this->hasMany(Task::class, 'project_id', 'id');
}
public function business()
{
return $this->belongsTo(Business::class, 'business_id', 'id');
}
public function systems()
{
return $this->hasMany(System::class, 'project_id', 'id');
}
public function sprints()
{
return $this->hasMany(Sprint::class, 'project_id', 'id');
}
public function files()
{
return $this->hasMany(File::class, 'user_id', 'id');
}
public function updateRelations()
{
// members
// if (!empty($this->filled_relations['members']) || $this->detach_relation) {
// $this->dirties['members'] = $this->members()->sync($this->filled_relations['members'], $this->detach_relation);
// }
}
public function reportActivity()
{
// foreach ($this->dirties as $name => $value) {
// return \post('task', 'task/v1/log', [
// 'user_id' => Auth::id(),
// 'business_id' => $this->business_id,
// 'loggable_id' => $this->id,
// 'loggable_type' => $this->getTypeId(),
// 'action' => $this->getAction(), // id of the action
// 'data' => [$name => [
// 'original' => $value['original'],
// 'diff' => $value['diff'],
// ]],
// ]);
// }
}
public function getPermissions($user_id)
{
return $this->members->where('id',$user_id)->first();
}
public function registerMediaCollections(): void
{
$this->addMediaCollection(static::COLLECTION_NAME)
->acceptsMimeTypes([
'image/jpeg',
'image/png',
'image/tiff',
'image/gif',
])
->useDisk('public')
->singleFile();
}
public function registerMediaConversions(Media $media = null): void
{
$this->addMediaConversion(static::CONVERSION_NAME)
->width(200)
->height(200)
->queued()
->nonOptimized()
->performOnCollections(static::COLLECTION_NAME);
}
public function saveAsAvatar(UploadedFile $avatar): void
{
$this->addMedia($avatar)->toMediaCollection(static::COLLECTION_NAME);
$this->update([
'has_avatar' => true,
]);
@unlink($this->getFirstMedia(static::COLLECTION_NAME)->getPath());
}
public function deleteAvatar(): void
{
$path = $this->getFirstMedia(static::COLLECTION_NAME)->getPath();
$this->getFirstMedia(static::COLLECTION_NAME)->delete();
$this->update([
'has_avatar' => false,
]);
@unlink($path);
}
public function getAvatarUrl(): ?string
{
if ($url = $this->getFirstMediaUrl(static::COLLECTION_NAME, static::CONVERSION_NAME)) {
return $url;
}
return null;
}
}

29
app/Models/SoftDeletes.php

@ -0,0 +1,29 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\SoftDeletes as SoftDeletesOriginal;
trait SoftDeletes
{
use SoftDeletesOriginal;
protected function performDeleteOnModel()
{
if ($this->forceDeleting) {
$this->exists = false;
return $this->setKeysForSaveQuery($this->newModelQuery())->forceDelete();
}
$time = $this->freshTimestamp();
$this->{$this->getDeletedAtColumn()} = $time;
if ($this->timestamps && !is_null($this->getUpdatedAtColumn())) {
$this->{$this->getUpdatedAtColumn()} = $time;
}
return $this->save();
}
}

73
app/Models/Sprint.php

@ -0,0 +1,73 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use Illuminate\Validation\Rule;
use Illuminate\Database\Eloquent\SoftDeletes;
class Sprint extends Model
{
protected $table = 'sprints';
protected $fillable = ['business_id', 'project_id', 'name', 'description', 'started_at', 'ended_at', 'active'];
protected $reportable = [
'name', 'started_at', 'ended_at', // fields
];
public function rules()
{
return [
'name' => 'bail|required|string|min:2|max:225',
'started_at' => 'bail|required|date|date_format:Y-m-d',
'ended_at' => 'bail|required|date|date_format:Y-m-d|after:started_at',
'active' => 'nullable|boolean',
'description' => 'nullable|string|min:2|max:1000',
];
}
protected $casts = [
'active' => 'boolean'
];
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => $this->project_id,
'sprint_id' => $this->id,
'workflow_id' => null,
'status_id' => null,
'system_id' => null,
'user_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function business()
{
return $this->belongsTo(Business::class, 'business_id', 'id');
}
public function projects()
{
return $this->belongsTo(Project::class, 'project_id', 'id');
}
public function tasks()
{
return $this->hasMany(Task::class, 'sprint_id', 'id');
}
}

55
app/Models/Status.php

@ -0,0 +1,55 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use Illuminate\Validation\Rule;
class Status extends Model
{
protected $table = 'statuses';
protected $fillable = [
'business_id', 'workflow_id', 'name', 'state', 'order'
];
protected $reportable = [
'name', 'state', 'order'
];
public function rules()
{
return [
'name' =>'required|string|min:3|max:255',
'state' => 'nullable|between:0,3',
'order' => 'nullable|numeric|min:0'
];
}
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => null,
'sprint_id' => null,
'workflow_id' => $this->workflow_id,
'status_id' => $this->id,
'system_id' => null,
'user_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function workflow()
{
return $this->belongsTo(Workflow::class,'workflow_id','id');
}
}

69
app/Models/System.php

@ -0,0 +1,69 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Validation\Rule;
class System extends Model
{
protected $table = 'systems';
protected $fillable = ['business_id', 'project_id', 'name'];
protected $reportable = [
'name'
];
public function rules()
{
return [
'name' => 'required|string|min:2|max:225',
];
}
protected $casts = [
'private' => 'boolean'
];
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => $this->project_id,
'sprint_id' => null,
'workflow_id' => null,
'status_id' => null,
'system_id' => $this->id,
'user_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function business()
{
return $this->belongsTo(Business::class, 'business_id', 'id');
}
public function project()
{
return $this->belongsTo(Project::class, 'project_id', 'id');
}
public function tasks()
{
return $this->hasMany(Task::class, 'sub_project_id', 'id');
}
}

58
app/Models/Tag.php

@ -0,0 +1,58 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use App\Scopes\BusinessScope;
class Tag extends Model
{
protected $fillable = ['business_id', 'label', 'color'];
protected $reportable = [
'label', 'color'
];
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => null,
'sprint_id' => null,
'workflow_id' => null,
'status_id' => null,
'system_id' => null,
'user_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function rules()
{
return [
'label' => 'required|string|min:3|max:225',
'color' => 'nullable|string|min:2|max:255',
];
}
public function business()
{
return $this->belongsTo(Business::class,'business_id','id',__FUNCTION__);
}
public function task()
{
return $this->belongsToMany(
Task::class,'tag_task','tag_id','task_id',
'id','id',__FUNCTION__
);
}
}

25
app/Models/Task.php

@ -0,0 +1,25 @@
<?php
namespace App;
use App\HiLib\Models\RemoteModel;
class Task extends RemoteModel
{
public string $host;
public ?string $path;
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->host = 'hi-task-app';
$this->path = 'task/v1/businesses/'.request('_business_info')['id'].'/tasks/';
}
public function files()
{
return $this->hasMany(File::class, 'attached_to_id', 'id')
->where('attached_to_table', enum('tables.tasks.id'));
}
}

164
app/Models/Transaction.php

@ -0,0 +1,164 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use App\Utilities\Zarinpal\Laravel\Facade\Zarinpal;
use Illuminate\Support\Arr;
class Transaction extends Model
{
protected $table = 'transactions';
protected $fillable = [
'user_id', 'business_id', 'amount', 'succeeded', 'options'
];
protected $casts = [
'options' => 'array',
'succeeded' => 'boolean',
];
protected $fillable_relations = [
'user', 'business'
];
protected $reportable = [
'user_id', 'business_id', 'amount', 'succeeded', 'options', // fields
];
public $perPage = 12;
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => null,
'sprint_id' => null,
'system_id' => null,
'user_id' => $this->user_id,
'workflow_id' => null,
'status_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function rules(): array
{
return [
'user_id' => 'required|',
'business_id' => 'required',
'amount' => 'required|integer|min:1',
];
}
public function updateRelations()
{
// user relations
if (!empty($this->filled_relations['user'])) {
$this->dirties['user'] = $this->user_id;
}
// business relations
if (!empty($this->filled_relations['business'])) {
$this->dirties['business'] = $this->business_id;
}
}
public function reportActivity()
{
}
public function user()
{
return $this->belongsTo(User::class, 'user_id','id',__FUNCTION__);
}
public function business()
{
return $this->belongsTo(Business::class, 'business_id','id',__FUNCTION__);
}
/**
* Receive the authority key from the payment gateway
*/
public function prepare(): Transaction
{
$results = Zarinpal::request(
config('services.zarinpal.callback-url'),
$this->amount,
config('services.zarinpal.description')
);
$this->options = $results;
$this->save();
return $this;
}
/**
* Redirect to the payment gateway
*/
public function redirect()
{
return Zarinpal::redirect();
}
public function verify(): Transaction
{
$results = Zarinpal::verify($this->amount, $this->options['Authority']);
if ($results['Status'] == 'verified_before') {
throw new \Exception("تراکنش قبلا تایید شده است.");
}
if ($results['Status'] == 'success') {
$this->succeeded = true;
} else {
$this->succeeded = false;
}
$options = array_merge($this->options, $results);
$this->options = $options;
$this->save();
return $this;
}
/**
* Find a transaction via the authoriry key that psp provides us
*
* @throw ModelNotFound
*/
public static function findByAuthority(string $authority): Transaction
{
return static::where('options->Authority','=',$authority)->firstOrFail();
}
public function isWentToPaymentGateway(): bool
{
return !empty($this->options);
}
public function hasBeenAppliedToWallet(): bool
{
return Arr::get($this->options,"applied", false);
}
public function amountWasAppliedToWallet()
{
$options = $this->options;
$options['applied'] = true;
$this->options = $options;
$this->save();
}
}

207
app/Models/User.php

@ -1,43 +1,184 @@
<?php <?php
namespace App\Models;
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\File;
use App\SoftDeletes;
use App\HiLib\Models\Model;
use Illuminate\Validation\Rule;
use Illuminate\Http\UploadedFile;
use Spatie\MediaLibrary\HasMedia;
use Illuminate\Auth\Authenticatable;
use Laravel\Lumen\Auth\Authorizable;
use App\HiLib\Models\ReportableRelation;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
class User extends Authenticatable
class User extends Model implements AuthenticatableContract, AuthorizableContract, HasMedia
{ {
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
use SoftDeletes,
Authorizable,
Authenticatable,
InteractsWithMedia;
const CONVERSION_NAME = 'avatar';
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
const COLLECTION_NAME = 'avatars';
public $casts = [
'has_avatar' => 'boolean',
]; ];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
protected $fillable = ['name', 'email','mobile', 'username','password','active','has_avatar'];
protected $fillable_relations = ['projects'];
protected $reportable = [
'name', 'username', 'mobile', 'email', // fields
['projects' => 'project_user']
]; ];
public $detach_relation = false;
public function updateRelations()
{
// projects relations
if (!empty($this->filled_relations['projects']) || $this->detach_relation) {
$this->dirties['projects'] = $this->projects()->sync($this->filled_relations['projects'], $this->detach_relation);
}
}
/** =============================== Validations ======================== */
public function rules()
{
return [
'name' => 'required|string|max:225|min:2',
'username' => ['required', Rule::unique('users', 'username')->ignore($this->id)],
'email' => ['required', 'email', Rule::unique('users', 'email')->ignore($this->id)],
'password' => ['required','string','min:8']
];
}
/** =============================== End Validations ==================== */
/** =============================== Relations ========================== */
public function fingerprints()
{
return $this->hasMany(Fingerprint::class, 'user_id', 'id');
}
public function businesses()
{
return $this->belongsToMany(
Business::class,
'business_user',
'user_id',
'business_id',
'id',
'id',
__FUNCTION__
);
}
public function tasks()
{
return $this->hasMany(Task::class, 'user_id', 'id');
}
public function projects()
{
return $this->belongsToMany(
Project::class,
'project_user',
'user_id',
'project_id',
'id',
'id',
__FUNCTION__
)->using(ReportableRelation::class);
}
public function files()
{
return $this->hasMany(File::class, 'user_id','id');
}
/** =============================== End Relations ====================== */
public function getValueOf(?string $key)
{
$values = [
'business_id' => request('_business_info')['id'] ?? null,
'user_id' => $this->id,
'workflow_id' => null,
'project_id' => null,
'sprint_id' => null,
'system_id' => null,
'status_id' => null,
'task_id' => null,
'subject_id' => $this->id,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function registerMediaCollections(): void
{
$this->addMediaCollection(static::COLLECTION_NAME)
->acceptsMimeTypes([
'image/jpeg',
'image/png',
'image/tiff',
'image/gif',
])
->useDisk('public')
->singleFile();
}
public function registerMediaConversions(Media $media = null): void
{
$this->addMediaConversion(static::CONVERSION_NAME)
->width(200)
->height(200)
->queued()
->nonOptimized()
->performOnCollections(static::COLLECTION_NAME);
}
public function saveAsAvatar(UploadedFile $avatar): void
{
$this->addMedia($avatar)->toMediaCollection(static::COLLECTION_NAME);
$this->update([
'has_avatar' => true,
]);
@unlink($this->getFirstMedia(static::COLLECTION_NAME)->getPath());
}
public function deleteAvatar(): void
{
$path = $this->getFirstMedia(static::COLLECTION_NAME)->getPath();
$this->getFirstMedia(static::COLLECTION_NAME)->delete();
$this->update([
'has_avatar' => false,
]);
@unlink($path);
}
public function getAvatarUrl(): ?string
{
if ($url = $this->getFirstMediaUrl(static::COLLECTION_NAME, static::CONVERSION_NAME)) {
return $url;
}
return null;
}
} }

88
app/Models/Workflow.php

@ -0,0 +1,88 @@
<?php
namespace App;
use App\HiLib\Models\Model;
use App\Scopes\BusinessScope;
class Workflow extends Model
{
protected $fillable = ['business_id', 'name', 'desc', 'statuses'];
protected $reportable = [
'name'
];
protected $fillable_relations = ['statuses'];
public function rules()
{
return [
'name' => 'required|string|min:3|max:225',
'desc' => 'nullable|string|min:3|max:225',
'statuses' => 'required|array|min:2',
'statuses.*' => 'required|array|min:2',
'statuses.*.id' => 'nullable|numeric',
'statuses.*.name' => 'required|string|min:3',
'statuses.*.state' => 'required|numeric|between:0,3',
'statuses.*.order' => 'nullable|numeric',
];
}
public function getValueOf(?string $key)
{
$values = [
'business_id' => $this->business_id,
'project_id' => null,
'sprint_id' => null,
'workflow_id' => $this->id,
'status_id' => null,
'system_id' => null,
'user_id' => null,
];
if ($key && isset($values, $key)) {
return $values[$key];
}
return $values;
}
public function updateRelations()
{
$old_statuses_name = isset(\request('_business_info')['workflows'][$this->id]['statuses']) ?
array_keys(collect(\request('_business_info')['workflows'][$this->id]['statuses'])->toArray()) :
[];
$new_statuses_name = array_keys(collect($this->filled_relations['statuses'])->keyBy('id')->toArray());
$removed_statuses_name = array_diff(array_merge($old_statuses_name, $new_statuses_name), $new_statuses_name);
foreach ($removed_statuses_name as $status_name) {
//delete all statuses that removed name's from request->statuses
$this->statuses()->where('id', $status_name)->first()->delete();
}
foreach (request('statuses') as $status) {
//sync another statuses
$this->statuses()
->updateOrCreate(
['id' => $status['id'] ?? null, 'business_id' => $this->business_id, 'workflow_id' => $this->id],
['name' => $status['name'], 'state' => $status['state'], 'order' => $status['order']]
);
}
}
public function business()
{
return $this->belongsTo(Business::class, 'business_id');
}
public function statuses()
{
return $this->hasMany(Status::class, 'workflow_id', 'id');
}
public function tasks()
{
return $this->hasMany(Task::class, 'workflow_id', 'id');
}
}

21
app/Providers/AuthServiceProvider.php

@ -2,8 +2,10 @@
namespace App\Providers; namespace App\Providers;
use App\Fingerprint;
use App\Utilities\RequestMixin;
use App\Utilities\BusinessInfoRequestMixin;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider class AuthServiceProvider extends ServiceProvider
{ {
@ -25,6 +27,21 @@ class AuthServiceProvider extends ServiceProvider
{ {
$this->registerPolicies(); $this->registerPolicies();
//
$this->app['request']->mixin(new RequestMixin);
$this->app['request']->mixin(new BusinessInfoRequestMixin);
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->bearerToken() === null) {
return null;
}
$fingerprint = Fingerprint::where([
'token' => $request->bearerToken(),
'agent' => $request->getAgent(),
'os' => $request->getOS(),
])->first();
return $fingerprint->user->setAttribute('token', $fingerprint->token);
});
} }
} }

40
app/Rules/MaxBound.php

@ -0,0 +1,40 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class MaxBound implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return sizeof(explode(',', trim($value, ','))) <= $this->bound;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The :attribute is out of bound.';
}
}

18
app/Scopes/BusinessScope.php

@ -0,0 +1,18 @@
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class BusinessScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('business_id', '=', request('business_id'));
}
}

15
app/Utilities/Avatar/DefaultConversionFileNamer.php

@ -0,0 +1,15 @@
<?php
namespace App\Utilities\Avatar;
use Spatie\MediaLibrary\Conversions\Conversion;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\Conversions\ConversionFileNamer;
class DefaultConversionFileNamer extends ConversionFileNamer
{
public function getFileName(Conversion $conversion, Media $media): string
{
return $media->model->getKey();
}
}

41
app/Utilities/Avatar/DefaultPathGenerator.php

@ -0,0 +1,41 @@
<?php
namespace App\Utilities\Avatar;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;
class DefaultPathGenerator implements PathGenerator
{
/*
* Get a unique base path for the given media.
*/
protected function getBasePath(Media $media): string
{
return $media->model->getTable()."/";
}
/*
* Get the path for the given media, relative to the root storage path.
*/
public function getPath(Media $media): string
{
return $this->getBasePath($media);
}
/*
* Get the path for conversions of the given media, relative to the root storage path.
*/
public function getPathForConversions(Media $media): string
{
return $this->getBasePath($media);
}
/*
* Get the path for responsive images of the given media, relative to the root storage path.
*/
public function getPathForResponsiveImages(Media $media): string
{
return $this->getBasePath($media).'/responsive-images/';
}
}

43
app/Utilities/BusinessInfoRequestMixin.php

@ -0,0 +1,43 @@
<?php
namespace App\Utilities;
use App\User;
use Illuminate\Support\Arr;
use Jenssegers\Agent\Agent;
/**
* @mixin Request
* Class RequestMixin
*/
class BusinessInfoRequestMixin
{
public function getBusinessInfo()
{
return function () {
return Arr::get($this, '_business_info');
};
}
public function getFromBusinessInfo()
{
return function(string $key) {
return Arr::get($this, "_business_info.$key");
};
}
public function getBusinessUsers()
{
return function() {
return $this->getFromBusinessInfo('info.users');
};
}
public function isBusinessOwner()
{
return function(User $user) {
return Arr::get($this, "_business_info.info.users.{$user->id}.level") != enum('levels.owner.id');
};
}
}

83
app/Utilities/Payload.php

@ -0,0 +1,83 @@
<?php
namespace App\Utilities;
use ArrayAccess;
use App\Models\Notifiable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Support\Stringable;
class Payload implements ArrayAccess
{
public array $payload;
public function __construct(array $payload)
{
$this->payload = $payload;
}
public function getActor(): ?Notifiable
{
return new Notifiable(Arr::get($this->payload, "info.users." . $this['auth']));
}
public function getSubject(): ?Notifiable
{
return new Notifiable(
Arr::get($this->payload, "info.users." . $this['data']['original']['user_id'])
);
}
public function getOwners(): Collection
{
return new Collection(
Arr::where($this->payload['info']['users'], fn ($user) => $user['level'] === 4)
);
}
public function getTableName(): ?Stringable
{
return Str::of(Arr::get($this->payload, "data.table_name"));
}
public function getActId()
{
return Arr::get($this->payload, "data.crud_id");
}
public function getTitle()
{
return Arr::get($this->payload, "info.name");
}
public function isPivot(): bool
{
return $this->getTableName()->contains("_");
}
public function offsetExists($key)
{
return isset($this->payload[$key]);
}
public function offsetGet($key)
{
return $this->payload[$key];
}
public function offsetSet($key, $value)
{
if (is_null($key)) {
$this->payload[] = $value;
} else {
$this->payload[$key] = $value;
}
}
public function offsetUnset($key)
{
unset($this->payload[$key]);
}
}

51
app/Utilities/RequestMixin.php

@ -0,0 +1,51 @@
<?php
namespace App\Utilities;
use Closure;
use Jenssegers\Agent\Agent;
use Illuminate\Support\Facades\Auth;
/**
* @mixin Request
* Class RequestMixin
*/
class RequestMixin
{
/**
* Return the OS
*
* @return Closure
*/
public function getOS()
{
return fn() => $this->hasHeader('USER_AGENT') ? (new Agent())->platform() : null;
}
/**
* Return the browser
*
* @return Closure
*/
public function getAgent()
{
return fn() => $this->hasHeader('USER_AGENT') ? (new Agent())->browser() : null;
}
/**
* Get the user location's based on her/his IP address
*
* @return Closure
*/
public function getLocation()
{
return fn() => geoip()->getLocation($this->getClientIp());
}
public function getCurrentToken()
{
// todo: how to implement ip lookup for current token
return fn() => Auth::user()->token;
}
}

46
app/Utilities/Zarinpal/Drivers/DriverInterface.php

@ -0,0 +1,46 @@
<?php
namespace App\Utilities\Zarinpal\Drivers;
interface DriverInterface
{
/**
* @param $inputs
*
* @return array|redirect
*/
public function request($inputs);
/**
* @param $inputs
*
* @return array|redirect
*/
public function requestWithExtra($inputs);
/**
* @param $inputs
*
* @return array
*/
public function verify($inputs);
/**
* @param $inputs
*
* @return array
*/
public function verifyWithExtra($inputs);
/**
* @param $inputs
*
* @return array
*/
public function setAddress($inputs);
/**
* activate sandbox mod for dev environment.
*/
public function enableSandbox();
}

197
app/Utilities/Zarinpal/Drivers/RestDriver.php

@ -0,0 +1,197 @@
<?php
namespace App\Utilities\Zarinpal\Drivers;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class RestDriver implements DriverInterface
{
protected $baseUrl = 'https://www.zarinpal.com/pg/rest/WebGate/';
/**
* request driver.
*
* @param $inputs
*
* @return array
*/
public function request($inputs)
{
$result = $this->restCall('PaymentRequest.json', $inputs);
if ($result['Status'] == 100) {
return ['Authority' => $result['Authority']];
} else {
return ['error' => $result['Status']];
}
}
/**
* requestWithExtra driver.
*
* @param $inputs
*
* @return array
*/
public function requestWithExtra($inputs)
{
$result = $this->restCall('PaymentRequestWithExtra.json', $inputs);
if ($result['Status'] == 100) {
return ['Authority' => $result['Authority']];
} else {
return [
'Status' => 'error',
'error' => !empty($result['Status']) ? $result['Status'] : null,
'errorInfo' => !empty($result['errors']) ? $result['errors'] : null,
];
}
}
/**
* verify driver.
*
* @param $inputs
*
* @return array
*/
public function verify($inputs)
{
$result = $this->restCall('PaymentVerification.json', $inputs);
if ($result['Status'] == 100) {
return [
'Status' => 'success',
'RefID' => $result['RefID'],
];
} elseif ($result['Status'] == 101) {
return [
'Status' => 'verified_before',
'RefID' => $result['RefID'],
];
} else {
return [
'Status' => 'error',
'error' => !empty($result['Status']) ? $result['Status'] : null,
'errorInfo' => !empty($result['errors']) ? $result['errors'] : null,
];
}
}
/**
* verifyWithExtra driver.
*
* @param $inputs
*
* @return array
*/
public function verifyWithExtra($inputs)
{
$result = $this->restCall('PaymentVerificationWithExtra.json', $inputs);
if ($result['Status'] == 100) {
return [
'Status' => 'success',
'RefID' => $result['RefID'],
'ExtraDetail' => $result['ExtraDetail'],
];
} elseif ($result['Status'] == 101) {
return [
'Status' => 'verified_before',
'RefID' => $result['RefID'],
'ExtraDetail' => $result['ExtraDetail'],
];
} else {
return [
'Status' => 'error',
'error' => !empty($result['Status']) ? $result['Status'] : null,
'errorInfo' => !empty($result['errors']) ? $result['errors'] : null,
];
}
}
/**
* unverifiedTransactions driver.
*
* @param $inputs
*
* @return array
*/
public function unverifiedTransactions($inputs)
{
$result = $this->restCall('UnverifiedTransactions.json', $inputs);
if ($result['Status'] == 100) {
return ['Status' => 'success', 'Authorities' => $result['Authorities']];
} else {
return [
'Status' => 'error',
'error' => !empty($result['Status']) ? $result['Status'] : null,
'errorInfo' => !empty($result['errors']) ? $result['errors'] : null,
];
}
}
/**
* refreshAuthority driver.
*
* @param $inputs
*
* @return array
*/
public function refreshAuthority($inputs)
{
$result = $this->restCall('RefreshAuthority.json', $inputs);
if ($result['Status'] == 100) {
return ['Status' => 'success', 'refreshed' => true];
} else {
return ['Status' => 'error', 'error' => $result['Status']];
}
}
/**
* request rest and return the response.
*
* @param $uri
* @param $data
*
* @return mixed
*/
private function restCall($uri, $data)
{
try {
$client = new Client(['base_uri' => $this->baseUrl]);
$response = $client->request('POST', $uri, ['json' => $data]);
$rawBody = $response->getBody()->getContents();
$body = json_decode($rawBody, true);
} catch (RequestException $e) {
$response = $e->getResponse();
$rawBody = is_null($response) ? '{"Status":-98,"message":"http connection error"}' : $response->getBody()->getContents();
$body = json_decode($rawBody, true);
}
if (!isset($result['Status'])) {
$result['Status'] = -99;
}
return $body;
}
/**
* @param mixed $baseUrl
*
* @return void
*/
public function setAddress($baseUrl)
{
$this->baseUrl = $baseUrl;
}
public function enableSandbox()
{
$this->setAddress('https://sandbox.zarinpal.com/pg/rest/WebGate/');
}
}

24
app/Utilities/Zarinpal/Laravel/Facade/Zarinpal.php

@ -0,0 +1,24 @@
<?php
namespace App\Utilities\Zarinpal\Laravel\Facade;
use Illuminate\Support\Facades\Facade;
/**
* @method static request($callbackURL, $Amount, $Description, $Email = null, $Mobile = null,$additionalData = null)
* @method static verify($status, $amount, $authority)
* @method static redirect()
* @method static getDriver()
*/
class Zarinpal extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'Zarinpal';
}
}

46
app/Utilities/Zarinpal/Laravel/ZarinpalServiceProvider.php

@ -0,0 +1,46 @@
<?php
namespace App\Utilities\Zarinpal\Laravel;
use App\Utilities\Zarinpal\Zarinpal;
use Illuminate\Support\ServiceProvider;
use App\Utilities\Zarinpal\Drivers\RestDriver;
use App\Utilities\Zarinpal\Drivers\DriverInterface;
class ZarinpalServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(DriverInterface::class, function () {
return new RestDriver();
});
$this->app->singleton('Zarinpal', function () {
$merchantID = config('services.zarinpal.merchantID', config('Zarinpal.merchantID', 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'));
$zarinpal = new Zarinpal($merchantID, $this->app->make(DriverInterface::class));
if (config('services.zarinpal.sandbox', false)) {
$zarinpal->enableSandbox();
}
if (config('services.zarinpal.zarinGate', false)) {
$zarinpal->isZarinGate();
}
return $zarinpal;
});
}
/**
* Publish the plugin configuration.
*/
public function boot()
{
//
}
}

130
app/Utilities/Zarinpal/Zarinpal.php

@ -0,0 +1,130 @@
<?php
namespace App\Utilities\Zarinpal;
use App\Utilities\Zarinpal\Drivers\RestDriver;
use App\Utilities\Zarinpal\Drivers\DriverInterface;
class Zarinpal
{
private $redirectUrl = 'https://www.zarinpal.com/pg/StartPay/%u';
private $merchantID;
private $driver;
private $Authority;
public function __construct($merchantID, DriverInterface $driver = null)
{
if (is_null($driver)) {
$driver = new RestDriver();
}
$this->merchantID = $merchantID;
$this->driver = $driver;
}
/**
* send request for money to zarinpal
* and redirect if there was no error.
*
* @param string $callbackURL
* @param string $Amount
* @param string $Description
* @param string $Email
* @param string $Mobile
* @param null $additionalData
*
* @return array|@redirect
*/
public function request($callbackURL, $Amount, $Description, $Email = null, $Mobile = null, $additionalData = null)
{
$inputs = [
'MerchantID' => $this->merchantID,
'CallbackURL' => $callbackURL,
'Amount' => $Amount,
'Description' => $Description,
];
if (!is_null($Email)) {
$inputs['Email'] = $Email;
}
if (!is_null($Mobile)) {
$inputs['Mobile'] = $Mobile;
}
if (!is_null($additionalData)) {
$inputs['AdditionalData'] = $additionalData;
$results = $this->driver->requestWithExtra($inputs);
} else {
$results = $this->driver->request($inputs);
}
if (empty($results['Authority'])) {
$results['Authority'] = null;
}
$this->Authority = $results['Authority'];
return $results;
}
/**
* verify that the bill is paid or not
* by checking authority, amount and status.
*
* @param $amount
* @param $authority
*
* @return array
*/
public function verify($amount, $authority)
{
// backward compatibility
if (count(func_get_args()) == 3) {
$amount = func_get_arg(1);
$authority = func_get_arg(2);
}
$inputs = [
'MerchantID' => $this->merchantID,
'Authority' => $authority,
'Amount' => $amount,
];
return $this->driver->verifyWithExtra($inputs);
}
public function redirect()
{
header('Location: ' . sprintf($this->redirectUrl, $this->Authority));
die;
}
/**
* @return string
*/
public function redirectUrl()
{
return sprintf($this->redirectUrl, $this->Authority);
}
/**
* @return DriverInterface
*/
public function getDriver()
{
return $this->driver;
}
/**
* active sandbox mod for test env.
*/
public function enableSandbox()
{
$this->redirectUrl = 'https://sandbox.zarinpal.com/pg/StartPay/%u';
$this->getDriver()->enableSandbox();
}
/**
* active zarinGate mode.
*/
public function isZarinGate()
{
$this->redirectUrl = $this->redirectUrl . '/ZarinGate';
}
}

15
composer.json

@ -9,6 +9,21 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.3|^8.0", "php": "^7.3|^8.0",
"ext-gd": "*",
"ext-json": "*",
"anik/amqp": "^1.3",
"illuminate/notifications": "^8.0",
"jenssegers/agent": "^2.6",
"laravel/legacy-factories": "^1",
"laravel/lumen-framework": "^8.0",
"laravel/socialite": "^5.1",
"league/flysystem-aws-s3-v3": "~1.0",
"league/flysystem-cached-adapter": "~1.0",
"morilog/jalali": "3.*",
"spatie/image": "^1.0",
"spatie/laravel-medialibrary": "^8.0",
"spatie/laravel-query-builder": "^3.3",
"torann/geoip": "^3.0",
"fideloper/proxy": "^4.4", "fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1", "guzzlehttp/guzzle": "^7.0.1",

4094
composer.lock
File diff suppressed because it is too large
View File

232
config.default/app.php

@ -0,0 +1,232 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL', null),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
// 'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
],
];

0
config/auth.php → config.default/auth.php

0
config/broadcasting.php → config.default/broadcasting.php

0
config/cache.php → config.default/cache.php

34
config.default/cors.php

@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

0
config/database.php → config.default/database.php

72
config.default/filesystems.php

@ -0,0 +1,72 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
*/
'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

0
config/hashing.php → config.default/hashing.php

104
config.default/logging.php

@ -0,0 +1,104 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

0
config/mail.php → config.default/mail.php

0
config/queue.php → config.default/queue.php

33
config.default/services.php

@ -0,0 +1,33 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
];

0
config/session.php → config.default/session.php

0
config/view.php → config.default/view.php

73
config/amqp.php

@ -0,0 +1,73 @@
<?php
use PhpAmqpLib\Message\AMQPMessage;
return [
/* Default connection */
'default' => env('AMQP_CONNECTION', 'rabbitmq'),
/*Available connections*/
'connections' => [
'rabbitmq' => [
'connection' => [
'host' => env('AMQP_HOST', 'base-rabbitmq'),
'port' => env('AMQP_PORT', 5672),
'username' => env('AMQP_USERNAME', 'root'),
'password' => env('AMQP_PASSWORD', 'root'),
'vhost' => env('AMQP_VHOST', '/'),
'connect_options' => [],
'ssl_options' => [],
'ssl_protocol' => env('AMQP_SSL_PROTOCOL', 'ssl'),
],
'channel_id' => null,
'message' => [
'content_type' => 'text/plain',
'delivery_mode' => env('AMQP_MESSAGE_DELIVERY_MODE', AMQPMessage::DELIVERY_MODE_PERSISTENT),
'content_encoding' => 'UTF-8',
],
'exchange' => [
'name' => env('AMQP_EXCHANGE_NAME', 'activity_exchange'),
'declare' => env('AMQP_EXCHANGE_DECLARE', false),
'type' => env('AMQP_EXCHANGE_TYPE', 'headers'),
'passive' => env('AMQP_EXCHANGE_PASSIVE', false),
'durable' => env('AMQP_EXCHANGE_DURABLE', true),
'auto_delete' => env('AMQP_EXCHANGE_AUTO_DEL', false),
'internal' => env('AMQP_EXCHANGE_INTERNAL', false),
'nowait' => env('AMQP_EXCHANGE_NOWAIT', false),
'properties' => [],
],
'queue' => [
'declare' => env('AMQP_QUEUE_DECLARE', false),
'passive' => env('AMQP_QUEUE_PASSIVE', false),
'durable' => env('AMQP_QUEUE_DURABLE', true),
'exclusive' => env('AMQP_QUEUE_EXCLUSIVE', false),
'auto_delete' => env('AMQP_QUEUE_AUTO_DEL', false),
'nowait' => env('AMQP_QUEUE_NOWAIT', false),
'd_properties' => [], // queue_declare properties/arguments
'b_properties' => [], // queue_bind properties/arguments
],
'consumer' => [
'tag' => env('AMQP_CONSUMER_TAG', ''),
'no_local' => env('AMQP_CONSUMER_NO_LOCAL', false),
'no_ack' => env('AMQP_CONSUMER_NO_ACK', false),
'exclusive' => env('AMQP_CONSUMER_EXCLUSIVE', false),
'nowait' => env('AMQP_CONSUMER_NOWAIT', false),
'ticket' => null,
'properties' => [],
],
'qos' => [
'enabled' => env('AMQP_QOS_ENABLED', false),
'qos_prefetch_size' => env('AMQP_QOS_PREF_SIZE', 0),
'qos_prefetch_count' => env('AMQP_QOS_PREF_COUNT', 1),
'qos_a_global' => env('AMQP_QOS_GLOBAL', false),
],
],
],
];

221
config/app.php

@ -1,232 +1,19 @@
<?php <?php
return [ return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'), 'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false), 'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL', null),
'timezone' => 'Asia/Tehran',
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'locale' => 'fa',
'fallback_locale' => 'en', 'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
// 'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
],
'faker_locale' => 'fa_IR',
'cache_ttl' => 60
]; ];

43
config/cors.php

@ -1,34 +1,59 @@
<?php <?php
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
| Laravel CORS Options
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
| The allowed_methods and allowed_headers options are case-insensitive.
|
| You don't need to provide both allowed_origins and allowed_origins_patterns.
| If one of the strings passed matches, it is considered a valid origin.
| |
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
| If ['*'] is provided to allowed_methods, allowed_origins or allowed_headers
| all methods / origins / headers are allowed.
| |
*/ */
'paths' => ['api/*', 'sanctum/csrf-cookie'],
/*
* You can enable CORS for 1 or multiple paths.
* Example: ['api/*']
*/
'paths' => ['/*'],
/*
* Matches the request method. `['*']` allows all methods.
*/
'allowed_methods' => ['*'], 'allowed_methods' => ['*'],
/*
* Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`
*/
'allowed_origins' => ['*'], 'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
/*
* Patterns that can be used with `preg_match` to match the origin.
*/
'allowedOriginsPatterns' => ['Content-Type', 'X-Requested-With'],
/*
* Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers.
*/
'allowed_headers' => ['*'], 'allowed_headers' => ['*'],
/*
* Sets the Access-Control-Expose-Headers response header with these headers.
*/
'exposed_headers' => [], 'exposed_headers' => [],
/*
* Sets the Access-Control-Max-Age response header when > 0.
*/
'max_age' => 0, 'max_age' => 0,
/*
* Sets the Access-Control-Allow-Credentials header.
*/
'supports_credentials' => false, 'supports_credentials' => false,
]; ];

21
config/filesystems.php

@ -15,6 +15,19 @@ return [
'default' => env('FILESYSTEM_DRIVER', 'local'), 'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Default Cloud Filesystem Disk
|--------------------------------------------------------------------------
|
| Many applications store files both locally and in the cloud. For this
| reason, you may specify a default "cloud" driver here. This driver
| will be bound as the Cloud disk implementation in the container.
|
*/
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Filesystem Disks | Filesystem Disks
@ -38,7 +51,7 @@ return [
'public' => [ 'public' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app/public'), 'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'url' => env('APP_URL') . '/storage',
'visibility' => 'public', 'visibility' => 'public',
], ],
@ -46,8 +59,8 @@ return [
'driver' => 's3', 'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'), 'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'), 'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'region' => env('AWS_DEFAULT_REGION','us'),
'bucket' => env('AWS_BUCKET','real-test-2'),
'url' => env('AWS_URL'), 'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'), 'endpoint' => env('AWS_ENDPOINT'),
], ],
@ -66,7 +79,7 @@ return [
*/ */
'links' => [ 'links' => [
public_path('storage') => storage_path('app/public'),
base_path('public/storage') => storage_path('app/public'),
], ],
]; ];

172
config/geoip.php

@ -0,0 +1,172 @@
<?php
use Torann\GeoIP\Services\IPApi;
use Torann\GeoIP\Services\IPData;
use Torann\GeoIP\Services\IPFinder;
use Torann\GeoIP\Services\IPGeoLocation;
use Torann\GeoIP\Services\MaxMindDatabase;
use Torann\GeoIP\Services\MaxMindWebService;
return [
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
|
| Here you may configure the log settings for when a location is not found
| for the IP provided.
|
*/
'log_failures' => false,
/*
|--------------------------------------------------------------------------
| Include Currency in Results
|--------------------------------------------------------------------------
|
| When enabled the system will do it's best in deciding the user's currency
| by matching their ISO code to a preset list of currencies.
|
*/
'include_currency' => false,
/*
|--------------------------------------------------------------------------
| Default Service
|--------------------------------------------------------------------------
|
| Here you may specify the default storage driver that should be used
| by the framework.
|
| Supported: "maxmind_database", "maxmind_api", "ipapi"
|
*/
'service' => 'ipapi',
/*
|--------------------------------------------------------------------------
| Storage Specific Configuration
|--------------------------------------------------------------------------
|
| Here you may configure as many storage drivers as you wish.
|
*/
'services' => [
'maxmind_database' => [
'class' => MaxMindDatabase::class,
'database_path' => storage_path('app/geoip.mmdb'),
'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')),
'locales' => ['en'],
],
'maxmind_api' => [
'class' => MaxMindWebService::class,
'user_id' => env('MAXMIND_USER_ID'),
'license_key' => env('MAXMIND_LICENSE_KEY'),
'locales' => ['en'],
],
'ipapi' => [
'class' => IPApi::class,
'secure' => true,
'key' => env('IPAPI_KEY'),
'continent_path' => storage_path('app/continents.json'),
'lang' => 'en',
],
'ipgeolocation' => [
'class' => IPGeoLocation::class,
'secure' => true,
'key' => env('IPGEOLOCATION_KEY'),
'continent_path' => storage_path('app/continents.json'),
'lang' => 'en',
],
'ipdata' => [
'class' => IPData::class,
'key' => env('IPDATA_API_KEY'),
'secure' => true,
],
'ipfinder' => [
'class' => IPFinder::class,
'key' => env('IPFINDER_API_KEY'),
'secure' => true,
'locales' => ['en'],
],
],
/*
|--------------------------------------------------------------------------
| Default Cache Driver
|--------------------------------------------------------------------------
|
| Here you may specify the type of caching that should be used
| by the package.
|
| Options:
|
| all - All location are cached
| some - Cache only the requesting user
| none - Disable cached
|
*/
'cache' => 'none',
/*
|--------------------------------------------------------------------------
| Cache Tags
|--------------------------------------------------------------------------
|
| Cache tags are not supported when using the file or database cache
| drivers in Laravel. This is done so that only locations can be cleared.
|
*/
// 'cache_tags' => [''],
/*
|--------------------------------------------------------------------------
| Cache Expiration
|--------------------------------------------------------------------------
|
| Define how long cached location are valid.
|
*/
'cache_expires' => 30,
/*
|--------------------------------------------------------------------------
| Default Location
|--------------------------------------------------------------------------
|
| Return when a location is not found.
|
*/
'default_location' => [
'ip' => '127.0.0.0',
'iso_code' => 'IRN',
'country' => 'Islamic Republic of Iran',
'city' => 'Tehran',
'state' => 'teh',
'state_name' => 'Connecticut',
'postal_code' => '513',
'lat' => 35.6892,
'lon' => 51.3890,
'timezone' => 'Asia/Tehran',
'continent' => 'Asia',
'default' => true,
'currency' => 'IRR',
],
];

98
config/logging.php

@ -1,104 +1,44 @@
<?php <?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Handler\AmqpHandler;
use PhpAmqpLib\Channel\AMQPChannel;
use App\HiLib\Logger\CreateCustomLogger;
use PhpAmqpLib\Connection\AMQPStreamConnection;
return [ return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'), 'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [ 'channels' => [
'hi' => [
'driver' => 'custom',
'via' => CreateCustomLogger::class,
'handler' => AmqpHandler::class,
'with' => [
'exchange' => new AMQPChannel(
new AMQPStreamConnection("base-rabbitmq", 5672, 'root', 'root')
),
'exchangeName' => 'log_exchange'
],
],
'stack' => [ 'stack' => [
'driver' => 'stack', 'driver' => 'stack',
'channels' => ['single'],
'channels' => ['hi', 'daily',],
'ignore_exceptions' => false, 'ignore_exceptions' => false,
], ],
'single' => [ 'single' => [
'driver' => 'single', 'driver' => 'single',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'level' => 'debug',
], ],
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'level' => 'debug',
'days' => 14, 'days' => 14,
], ],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
], ],
]; ];

179
config/media-library.php

@ -0,0 +1,179 @@
<?php
return [
/*
* The disk on which to store added files and derived images by default. Choose
* one or more of the disks you've configured in config/filesystems.php.
*/
'disk_name' => env('MEDIA_DISK', 'public'),
/*
* The maximum file size of an item in bytes.
* Adding a larger file will result in an exception.
*/
'max_file_size' => 1024 * 1024 * 10,
/*
* This queue will be used to generate derived and responsive images.
* Leave empty to use the default queue.
*/
'queue_name' => '',
/*
* By default all conversions will be performed on a queue.
*/
'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true),
/*
* The fully qualified class name of the media model.
*/
'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
'remote' => [
/*
* Any extra headers that should be included when uploading media to
* a remote disk. Even though supported headers may vary between
* different drivers, a sensible default has been provided.
*
* Supported by S3: CacheControl, Expires, StorageClass,
* ServerSideEncryption, Metadata, ACL, ContentEncoding
*/
'extra_headers' => [
'CacheControl' => 'max-age=604800',
],
],
'responsive_images' => [
/*
* This class is responsible for calculating the target widths of the responsive
* images. By default we optimize for filesize and create variations that each are 20%
* smaller than the previous one. More info in the documentation.
*
* https://docs.spatie.be/laravel-medialibrary/v8/advanced-usage/generating-responsive-images
*/
'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class,
/*
* By default rendering media to a responsive image will add some javascript and a tiny placeholder.
* This ensures that the browser can already determine the correct layout.
*/
'use_tiny_placeholders' => true,
/*
* This class will generate the tiny placeholder used for progressive image loading. By default
* the media library will use a tiny blurred jpg image.
*/
'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class,
],
/*
* When converting Media instances to response the media library will add
* a `loading` attribute to the `img` tag. Here you can set the default
* value of that attribute.
*
* Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction.
*
* More info: https://css-tricks.com/native-lazy-loading/
*/
'default_loading_attribute_value' => null,
/*
* This is the class that is responsible for naming conversion files. By default,
* it will use the filename of the original and concatenate the conversion name to it.
*/
'conversion_file_namer' => App\Utilities\Avatar\DefaultConversionFileNamer::class,
/*
* The class that contains the strategy for determining a media file's path.
*/
'path_generator' => App\Utilities\Avatar\DefaultPathGenerator::class,
/*
* When urls to files get generated, this class will be called. Use the default
* if your files are stored locally above the site root or on s3.
*/
'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
/*
* Whether to activate versioning when urls to files get generated.
* When activated, this attaches a ?v=xx query string to the URL.
*/
'version_urls' => false,
/*
* The media library will try to optimize all converted images by removing
* metadata and applying a little bit of compression. These are
* the optimizers that will be used by default.
*/
'image_optimizers' => [
Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
'--strip-all', // this strips out all text information such as comments and EXIF data
'--all-progressive', // this will make sure the resulting image is a progressive one
],
Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
'--force', // required parameter for this package
],
Spatie\ImageOptimizer\Optimizers\Optipng::class => [
'-i0', // this will result in a non-interlaced, progressive scanned image
'-o2', // this set the optimization level to two (multiple IDAT compression trials)
'-quiet', // required parameter for this package
],
Spatie\ImageOptimizer\Optimizers\Svgo::class => [
'--disable=cleanupIDs', // disabling because it is known to cause troubles
],
Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
'-b', // required parameter for this package
'-O3', // this produces the slowest but best results
],
],
/*
* These generators will be used to create an image of media files.
*/
'image_generators' => [
Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class,
],
/*
* The engine that should perform the image conversions.
* Should be either `gd` or `imagick`.
*/
'image_driver' => env('IMAGE_DRIVER', 'gd'),
/*
* FFMPEG & FFProbe binaries paths, only used if you try to generate video
* thumbnails and have installed the php-ffmpeg/php-ffmpeg composer
* dependency.
*/
'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
/*
* The path where to store temporary files while performing image conversions.
* If set to null, storage_path('media-library/temp') will be used.
*/
'temporary_directory_path' => null,
/*
* Here you can override the class names of the jobs used by this package. Make sure
* your custom jobs extend the ones provided by the package.
*/
'jobs' => [
'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class,
'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class,
],
/*
* When using the addMediaFromUrl method you may want to replace the default downloader.
* This is particularly useful when the url of the image is behind a firewall and
* need to add additional flags, possibly using curl.
*/
'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class,
];

38
config/query-builder.php

@ -0,0 +1,38 @@
<?php
/**
* @see https://github.com/spatie/laravel-query-builder
*/
return [
/*
* By default the package will use the `include`, `filter`, `sort`
* and `fields` query parameters as described in the readme.
*
* You can customize these query string parameters here.
*/
'parameters' => [
'include' => 'include',
'filter' => 'filter',
'sort' => 'sort',
'fields' => 'fields',
'append' => 'append',
],
/*
* Related model counts are included using the relationship name suffixed with this string.
* For example: GET /users?include=postsCount
*/
'count_suffix' => 'Count',
/*
* By default the package will throw an `InvalidFilterQuery` exception when a filter in the
* URL is not allowed in the `allowedFilters()` method.
*/
'disable_invalid_filter_query_exception' => false,
];

40
config/services.php

@ -1,33 +1,21 @@
<?php <?php
return [ return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'task' => 'hi-task-app',
'zarinpal' => [
'merchant-id' => '68d337b0-4b77-11ea-8409-000c295eb8fc',
'type' => 'zarin-gate', // Types: [zarin-gate || normal]
'callback-url' => 'http://127.0.0.1:8000/user/v1/callback',
'server' => 'germany', // Servers: [germany || iran || test]
'email' => 'admin@base.com',
'mobile' => '09123456789',
'description' => env('APP_NAME', 'APP_NAME'),
'sandbox' => true,
], ],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
'google' => [
'client_id' => '1002439248397-oa6hnh25n6qri3q4kst62gvb1k9ki65l.apps.googleusercontent.com',
'client_secret' => 'tKbiyh5hOjYIcj-W1y3N8X5R',
'redirect' => 'http://localhost:8000/user/v1/auth/google/callback',
], ],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
]; ];

19
database/factories/BusinessFactory.php

@ -0,0 +1,19 @@
<?php
/** @var Factory $factory */
use App\Business;
use Illuminate\Support\Str;
use Faker\Generator as Faker;
use Illuminate\Database\Eloquent\Factory;
$factory->define(Business::class, function (Faker $faker) {
return [
'name' => $name = $faker->unique()->company,
'slug' => Str::slug($name) . $faker->numberBetween(1, 100),
'wallet' => random_int(111111, 999999),
'slug' => Str::slug($name) . $faker->numberBetween(1, 100),
'color' => $faker->colorName,
'calculated_at' => \Carbon\Carbon::now()->subMinutes(random_int(1, 1000)),
];
});

19
database/factories/CostFactory.php

@ -0,0 +1,19 @@
<?php
/** @var Factory $factory */
use App\Cost;
use Carbon\Carbon;
use Faker\Generator as Faker;
$factory->define(Cost::class, function (Faker $faker) {
return [
'business_id' => random_int(1,5000),
'type' => $type = $faker->boolean() ? 'users' : 'files',
'month' => jdate(Carbon::now()->subDays(random_int(0,90)))->format("Y-m-01"),
'amount' => random_int(1,1000),
'fee' => $type === 'users' ? enum("business.fee.user") : enum("business.fee.file"),
'duration' => random_int(1,60),
'created_at' => Carbon::now()->subMinutes(random_int(1, 1000)),
];
});

35
database/factories/FileFactory.php

@ -0,0 +1,35 @@
<?php
/** @var Factory $factory */
use App\File;
use Faker\Generator as Faker;
$factory->define(File::class, function (Faker $faker) {
$mimes = ['application/pdf', 'video/mp4', 'image/png', 'image/jpeg', 'audio/x-wav'];
$extensions = ['pdf', 'mp4', 'png', 'jpg', 'wav'];
$groups = [
'pdf' => 'pdf',
'mp4' => 'video',
'png' => 'image',
'jpg' => 'image',
'wav' => 'audio',
];
$sizes = [1, 5, 128, 256, 1024, 2048];
$rand_type = $faker->numberBetween(0, 4);
return [
'user_id' => $faker->numberBetween(1, 200),
'business_id' => $faker->numberBetween(1, 200),
'project_id' => $faker->numberBetween(1, 200),
'attached_to_id' => $faker->numberBetween(1, 100),
'attached_to_table' => enum('tables.tasks.id'),
'disk' => 's3',
'original_name' => $faker->words(1, true),
'name' => $faker->words(1, true),
'extension' => $extension = $extensions[$rand_type],
'mime' => $mimes[$rand_type],
'group' => $groups[$extension],
'size' => $sizes[$faker->numberBetween(0, 5)],
'description' => $faker->text,
];
});

38
database/factories/FingerprintFactory.php

@ -0,0 +1,38 @@
<?php
/** @var Factory $factory */
use App\Fingerprint;
use Faker\Factory as Faker;
use Illuminate\Database\Eloquent\Factory;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
$faker = Faker::create('fa_IR');
$factory->define(Fingerprint::class, function () use ($faker) {
$os = [
$faker->windowsPlatformToken,
$faker->linuxPlatformToken,
$faker->macPlatformToken
];
$browsers = [
$faker->firefox,
$faker->chrome,
$faker->opera,
$faker->safari,
];
$detector = new Jenssegers\Agent\Agent();
return [
'user_id' => $faker->numberBetween(1, 1000),
'agent' => $detector->browser(Arr::random($browsers)),
'ip' => $faker->ipv4,
'os' => $detector->platform(Arr::random($browsers)),
'latitude' => $faker->latitude,
'longitude' => $faker->longitude,
'token' => Str::random(60),
];
});

23
database/factories/ProjectFactory.php

@ -0,0 +1,23 @@
<?php
/** @var Factory $factory */
use App\Project;
use Faker\Generator as Faker;
use Illuminate\Database\Eloquent\Factory;
use Illuminate\Support\Str;
$factory->define(Project::class, function (Faker $faker) {
return [
'business_id' => null,
'name' => $name = $faker->words(3, true),
'slug' => Str::slug($name),
'private' => false,
'budget' => 0,
'start' => null,
'finish' => null,
'color' => $faker->colorName,
'active' => rand(0, 1),
'description' => $faker->paragraph,
];
});

16
database/factories/SprintflowFactory.php

@ -0,0 +1,16 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Sprint;
use Faker\Generator as Faker;
$factory->define(Sprint::class, function (Faker $faker) {
return [
'business_id' => null,
'name' => $faker->randomElement(['scrum', 'printing',
'agile', 'develop', 'design', 'writing', 'seo', 'sale']),
'active' => rand(0, 1),
'description' => $faker->paragraph,
];
});

16
database/factories/SystemFactory.php

@ -0,0 +1,16 @@
<?php
/** @var Factory $factory */
use App\System;
use Faker\Generator as Faker;
use Illuminate\Database\Eloquent\Factory;
use Illuminate\Support\Str;
$factory->define(System::class, function (Faker $faker) {
return [
'business_id' => null,
'project_id' => null,
'name' => $name = $faker->words(3, true),
];
});

15
database/factories/TagFactory.php

@ -0,0 +1,15 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Tag;
use Faker\Factory;
$faker = Factory::create('fa_IR');
$factory->define(Tag::class, function () use ($faker) {
return [
'label' => $faker->colorName,
'color' => $faker->colorName,
'business_id' => null,
];
});

22
database/factories/TaskFactory.php

@ -0,0 +1,22 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Task;
use Carbon\Carbon;
use Faker\Generator as Faker;
$factory->define(Task::class, function (Faker $faker) {
return [
'business_id' => null,
'creator_id' => null,
'project_id' => null,
'user_id' => null,
'workflow_id' => null,
'name' => $faker->sentences(3, true),
'time' => null,
'cost' => 0,
'completed' => false,
'due_date' => Carbon::now()->toDateTimeString(),
];
});

21
database/factories/TransactionFactory.php

@ -0,0 +1,21 @@
<?php
/** @var Factory $factory */
use Carbon\Carbon;
use App\Transaction;
use Faker\Generator as Faker;
use Illuminate\Support\Facades\DB;
$factory->define(Transaction::class, function (Faker $faker) {
$business_user = DB::selectOne('select business_id, user_id from business_user where level = 4 order by rand() limit 1');
return [
'user_id' => $business_user->user_id,
'business_id' => $business_user->business_id,
'amount' => random_int(1000, 9999),
'succeeded' => $faker->boolean(),
'options' => json_encode([]),
'created_at' => Carbon::now()->subMinutes(random_int(1, 1000)),
];
});

55
database/factories/UserFactory.php

@ -1,47 +1,16 @@
<?php <?php
namespace Database\Factories;
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\User;
use Faker\Generator as Faker;
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function unverified()
{
return $this->state(function (array $attributes) {
return [
'email_verified_at' => null,
];
});
}
}
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'mobile' => $faker->unique()->phoneNumber,
'username' => $faker->unique()->userName,
'password' => '$2y$10$l8jgLtb7RyDd7wbvxYPsuu7gjo/bLBkBYQhXnkpdmm.SVF3CT00UW',
];
});

15
database/factories/WorkflowFactory.php

@ -0,0 +1,15 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Workflow;
use Faker\Generator as Faker;
$factory->define(Workflow::class, function (Faker $faker) {
return [
'business_id' => null,
'name' => $faker->randomElement(['scrum', 'printing',
'agile', 'develop', 'design', 'writing', 'seo', 'sale']),
'desc' => $faker->sentences(1, true),
];
});

15
database/factories/WorkstatusFactory.php

@ -0,0 +1,15 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Status;
use Faker\Generator as Faker;
$factory->define(Status::class, function (Faker $faker) {
return [
'name' => $faker->randomElement(['idea', 'todo',
'doing', 'done', 'pending', 'hold', 'logestik', 'sew']),
'state' => rand(0, 3),
'order' => rand(0, 10),
];
});

0
database/migrations/.gitkeep

36
database/migrations/2019_08_19_000000_create_failed_jobs_table.php

@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFailedJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('failed_jobs');
}
}

10
database/migrations/2014_10_12_000000_create_users_table.php → database/migrations/2020_08_18_085016_create_users_table.php

@ -17,10 +17,14 @@ class CreateUsersTable extends Migration
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('email')->unique(); $table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('mobile')->unique()->nullable();
$table->string('username')->unique();
$table->string('password'); $table->string('password');
$table->rememberToken();
$table->timestamps();
$table->boolean('active')->default(false);
$table->boolean('has_avatar')->default(false);
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
$table->timestamp('deleted_at')->nullable();
}); });
} }

38
database/migrations/2020_08_18_085017_fingerprints.php

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Fingerprints extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('fingerprints', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('agent');
$table->ipAddress('ip');
$table->string('os');
$table->decimal('latitude', 10, 2);
$table->decimal('longitude', 11, 2);
$table->char('token', 60)->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('fingerprints');
}
}

49
database/migrations/2020_08_18_085018_create_businesses_table.php

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBusinessesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('businesses', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->integer('wallet')->default(0);
$table->unsignedInteger('files_volume')->default(0);
$table->string('color')->nullable();
$table->string('description')->nullable();
$table->json('cache')->nullable();
$table->boolean('has_avatar')->default(false);
$table->timestamp('calculated_at')->nullable();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
$table->timestamp('deleted_at')->nullable();
});
Schema::create('business_user', function (Blueprint $table) {
$table->unsignedBigInteger('business_id');
$table->unsignedBigInteger('user_id');
$table->tinyInteger('level')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('businesses');
Schema::dropIfExists('businesses_user');
}
}

58
database/migrations/2020_08_18_085046_create_projects_table.php

@ -0,0 +1,58 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('business_id');
$table->string('name');
$table->string('slug');
$table->boolean('private')->default(false);
$table->unsignedBigInteger('budget')->default(0);
$table->string('color')->nullable();
$table->boolean('active')->nullable();
$table->text('description')->nullable();
$table->boolean('has_avatar')->default(false);
$table->timestamp('start')->nullable();
$table->timestamp('finish')->nullable();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
$table->timestamp('deleted_at')->nullable();
});
// Only those users that added directly to the project are stored here.
// Users who are members of the business have access to the projects of that business
// without being stored here.
Schema::create('project_user', function (Blueprint $table) {
$table->unsignedBigInteger('project_id');
$table->unsignedBigInteger('user_id');
$table->tinyInteger('level')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('projects');
Schema::dropIfExists('project_user');
}
}

13
database/migrations/2014_10_12_100000_create_password_resets_table.php → database/migrations/2020_08_18_085054_create_workflows_table.php

@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
class CreateWorkflowsTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,10 +13,13 @@ class CreatePasswordResetsTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
Schema::create('workflows', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('business_id');
$table->string('name');
$table->string('desc')->nullable();
$table->timestamp('created_at')->nullable(); $table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
}); });
} }
@ -27,6 +30,6 @@ class CreatePasswordResetsTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('password_resets');
Schema::dropIfExists('workflows');
} }
} }

39
database/migrations/2020_08_18_085102_create_statuses_table.php

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStatusesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('statuses', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('business_id');
$table->unsignedBigInteger('workflow_id');
$table->string('name');
$table->string('state')->default(enum('status.states.inactive.id'));
$table->unsignedSmallInteger('order')->default(0);
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('statuses');
// Schema::dropIfExists('status_workflow');
}
}

41
database/migrations/2020_08_18_085114_create_tags_table.php

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTagsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('label');
$table->string('color')->nullable();
$table->unsignedBigInteger('business_id');
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
});
Schema::create('tag_task', function (Blueprint $table) {
$table->unsignedBigInteger('tag_id');
$table->unsignedBigInteger('task_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tags');
Schema::dropIfExists('tag_task');
}
}

34
database/migrations/2020_08_28_095802_create_systems_table.php

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSystemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('systems', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('business_id');
$table->unsignedBigInteger('project_id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('systems');
}
}

38
database/migrations/2020_08_28_101545_create_sprint_table.php

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSprintTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sprints', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('business_id');
$table->unsignedBigInteger('project_id');
$table->string('name');
$table->text('description')->nullable();
$table->date('started_at');
$table->date('ended_at');
$table->boolean('active')->default(true);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sprints');
}
}

37
database/migrations/2020_10_31_182018_create_transactions_table.php

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTransactionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('transactions', function (Blueprint $table) {
$table->bigInteger('id', true, true);
$table->bigInteger('user_id', false, true);
$table->bigInteger('business_id', false, true);
$table->integer('amount', false, true);
$table->boolean('succeeded')->default(false);
$table->json('options')->nullable();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('transactions');
}
}

41
database/migrations/2021_09_03_085114_create_costs_table.php

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('costs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('business_id');
$table->string('type');
$table->date('month');
$table->unsignedInteger('amount');
$table->unsignedInteger('fee')->default(0);
$table->unsignedInteger('duration');
$table->unsignedInteger('cost')->storedAs('fee*duration');
$table->unsignedFloat('tax', 8, 2)->storedAs('(cost/100)*9');
$table->json('additional')->nullable();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('costs');
}
}

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

Loading…
Cancel
Save