Compare commits

..

44 Commits

Author SHA1 Message Date
Ricky Rx d9cc561dc5 fix: video_update > upsert, is selected anomaly 2 months ago
yaaa90520@gmail.com 04d3a50889 fix: rule unique for apk upload 1 year ago
yaaa90520@gmail.com 3f0ceeb4e5 fix: TVLog > TvLog 1 year ago
yaaa90520@gmail.com cfea605fc1 Merge branch 'v2.0.0' 1 year ago
ricky rx e2ab453d66 feat: api for new tv request > approve existing tv 1 year ago
ricky rx 2ed57016bc chore: adjust for filter TV 1 year ago
ricky rx 3ff470bc55 refactor: new_tv_req > activated_at change to approved_at 1 year ago
ricky rx 3322055ac5 chore: adjustment related to tv > instaleed at & last connected at 1 year ago
ricky rx 163d5a0486 chore: write tvlogs for upsert action 1 year ago
ricky rx 56384c1fa9 feat: tv with import export excel 1 year ago
ricky rx b82fb64dd3 feat: add col version_name to app_update 1 year ago
ricky rx 22f99b29fa refactor: add prefix apk_ tv>version_code & tv_version_name 1 year ago
ricky rx 52b7e91405 feat: TV code with autogenerate code 1 year ago
ricky rx eee7c263ab chore: TV > add col company name, address, street address 1 year ago
ricky rx d50cde1189 fix: forget db commit 2 years ago
ricky rx 9ae7e9f7b3 feat: add version_code & name on tv 2 years ago
ricky rx 363f11136e chore: remove indokargo resync data feature 2 years ago
ricky rx b6e3edecde refactor: change mobile-token to mobile-auth-token 2 years ago
ricky rx 2c1a415979 feat: sts reschedule 2 years ago
ricky rx 158106c758 chore: change code for ik http transaction & timeout 2 years ago
ricky rx d947df81d3 fix: add info last_connected_at & installed_at 2 years ago
ricky rx 7118fea237 feat: api for tv > tv page 2 years ago
ricky rx d1c4f39591 fix: validation for bool & custom message 2 years ago
ricky rx e2e6c2b6f8 feat: video_update with file_size information 2 years ago
ricky rx cbbe612658 refactor: TV Controller change update condition to check update 2 years ago
ricky rx 5e46b20720 adjust: api for background service 2 years ago
ricky rx 4b56d762f6 feat: api for mobile > tv > new tv request 2 years ago
ricky rx fb281607d1 feat: api for frontend> tv > new tv request 2 years ago
ricky rx 546df1abf6 feat: api for frontend 2 years ago
ricky rx 2a7a0b32f3 feat: model for v2.0.0 2 years ago
ricky rx 4bde317a23 feat: migration for v2.0.0 2 years ago
ricky rx aee6d9440c doc: add simple readme 2 years ago
ricky rx d4a8d29afb fix: migration > timestamp > timestamptz 2 years ago
ricky rx 249190e007 chore: throw storage to false when failed to save data 2 years ago
ricky rx b287b3587a chore: change seeder 2 years ago
ricky rx 2b11a40fb6 Merge branch 'mobile' 2 years ago
ricky rx 8e1da6c4b1 feat: api for check updates mobile 2 years ago
ricky rx 5567f61f3d refactor: api routes for superadmin 2 years ago
ricky rx 8fc4188bae fix: video update anomaly 2 years ago
ricky rx 3124adb8ae chore: change session obj value 2 years ago
ricky rx a9f4318f38 feat: user management page 2 years ago
ricky rx 461b4e815b feat: api for apk upload 2 years ago
ricky rx 5d902d6117 feat: migration change for apk updates 2 years ago
ricky rx a64a017f00 Merge branch 'upload-video' 2 years ago

@ -3,10 +3,16 @@ APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
MOBILE_AUTH_TOKEN=Gaobd5OKPdGARLGTD03vSFStrADAxmQ9
# for CORS # for CORS
SANCTUM_STATEFUL_DOMAINS=[http://localhost:3000] SANCTUM_STATEFUL_DOMAINS=[http://localhost:3000]
# for Indokargo
IK_BE_DOMAIN=
IK_SESSION_ID=
IK_OAUTH_ID=
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug

@ -1,66 +1,29 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p> ### Note: if you forgot how to open readme from vs code:
- Ctrl K + V (Side bar readme)
<p align="center"> - Crtl Shift V (Full size read me)
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a> # Laravel Tivi
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a> ## Instalation
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a> - open /etc/php/8.2/fpm/php.ini
</p> - upload_max_filesize = 100M
- post_max_size = 100M
## About Laravel - Pull Project on server
- Setting .env from .env.example
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: - note: fill SANCTUM_LARAVEL_DOMAINS with frontend url
- example: SANCTUM_STATEFUL_DOMAINS=[http://tv.bintangtechnology.com,https://tv.bintangtechnology.com,]
- [Simple, fast routing engine](https://laravel.com/docs/routing). - run command
- [Powerful dependency injection container](https://laravel.com/docs/container). - sudo chmod -R 777 storage -> laravel storage log permission
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. - php artisan storage:link -> if u use public storage in laravel, not s3
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). - composer update {or} /usr/bin/php8.2 /usr/local/bin/composer update
- Database agnostic [schema migrations](https://laravel.com/docs/migrations). - php artisan migrate --seed {or} php8.2 artisan migrate --seed
- [Robust background job processing](https://laravel.com/docs/queues). - php artisan key:generate {or} php artisan key:generate
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
- note:
Laravel is accessible, powerful, and provides tools required for large, robust applications. - usually, cors problem cause by chmod on /storage or upload_max_filesize / post_max_size (only happen when u debug with firefox)
## Learning Laravel ## Update
- open project loaction (ex: /var/www/api_tivi)
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. - run command
- git pull --all
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. - composer install
- php artisan migrate (if it has database changes)
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

@ -0,0 +1,86 @@
<?php
namespace App\Console\Commands;
use App\Helper\Sts\Indokargo;
use App\Models\StsLog;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Http\Request;
class StsReschedule extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sts:reschedule';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Resschedule failed sts to sts data';
/**
* Execute the console command.
*/
public function handle(): void {
$sleep = 30; // 30 second
while(true) {
$this->info('---------------------------');
$this->info('reschedule sts:' . Carbon::now()->toDateTimeString());
$this->info('---------------------------');
// get reschedu;e sts logs
$success = 0;
$failed = 0;
$rescheduleStsLogs = StsLog::where(['is_retry' => true])->get();
foreach($rescheduleStsLogs as $rescheduleStsLog) {
try {
$partner = $rescheduleStsLog->partner;
$module = $rescheduleStsLog->module;
$serviceName = $rescheduleStsLog->service_name;
$request = new Request((array) $rescheduleStsLog->request_data ?? []);
$localId = $rescheduleStsLog->local_id;
$partnerId = $rescheduleStsLog->partner_id;
$lastSeq = $rescheduleStsLog->seq;
if($partner == StsLog::PARTNER_INDOKARGO) {
if($module == StsLog::MODULE_TV) {
if($serviceName == StsLog::SERVICE_CREATE_TV_ADDRESS) {
Indokargo::createTVAddress($request, $localId, $lastSeq);
} else if($serviceName == StsLog::SERVICE_UPDATE_TV_ADDRESS) {
Indokargo::updateTVAddress($request, $localId, $partnerId, $lastSeq);
} else if($serviceName == StsLog::SERVICE_CHANGE_STATUS_TV_ADDRESS) {
Indokargo::changeStatusAddress($request, $localId, $partnerId, $lastSeq);
} else {
throw new \Exception("Service name '$partner' > '$module' > '$serviceName' not found service");
}
} else {
throw new \Exception("Module name '$partner' > '$module' not found service");
}
} else {
throw new \Exception("Partner name '$partner' not found service");
}
$success++;
} catch(\Throwable $th) {
$failed++;
$this->info("stsLog id: ". $rescheduleStsLog->id . " => " . $th->getMessage());
}
}
// save all retry request is retry false
$rescheduleStsLogIds = $rescheduleStsLogs->pluck('id')->toArray();
StsLog::whereIn('id', $rescheduleStsLogIds)->update(['is_retry' => false]);
$this->info('---------------------------');
$this->info("result: $success success, $failed failed" );
$this->info("Sleep in $sleep seconds" );
$this->info('---------------------------');
sleep($sleep);
}
}
}

@ -2,6 +2,8 @@
namespace App\Helper; namespace App\Helper;
use Illuminate\Support\Facades\DB;
class Common { class Common {
public static function convertRequestConfig(?array $requestConfig): array { public static function convertRequestConfig(?array $requestConfig): array {
$config = []; $config = [];
@ -26,4 +28,9 @@ class Common {
public static function trueOrFalse(mixed $value): bool { public static function trueOrFalse(mixed $value): bool {
return ($value === "true" || $value === "1" || $value === 1 || $value === true) ? true : false; return ($value === "true" || $value === "1" || $value === 1 || $value === true) ? true : false;
} }
public static function setTimezone($timezone) {
date_default_timezone_set($timezone);
DB::statement("SET TIME ZONE '$timezone'");
}
} }

@ -25,5 +25,13 @@
]; ];
} }
public static function getSearchValidation() { return 'nullable|string'; } public static function getSearchValidation() { return 'nullable|string'; }
public static function compileDirtyEloquentToArrMessage($model) {
$dirties = $model->getDirty();
foreach($dirties as $key => $dirty) {
$dirties[$key] = $model->getOriginal($key) . ' => ' . $dirty;
}
return $dirties;
}
} }
?> ?>

@ -1,6 +1,8 @@
<?php <?php
namespace App\Helper; namespace App\Helper;
use AWS\CRT\HTTP\Request;
class FileHelper { class FileHelper {
static $allowedVideoExtensions = ['mp4', 'mkv']; static $allowedVideoExtensions = ['mp4', 'mkv'];
static $allowedApkExtensions = ['apk']; static $allowedApkExtensions = ['apk'];
@ -15,7 +17,7 @@ class FileHelper {
static function convertToStrLaraValidation(array $allowedFileExtensions, $type = 'string') { static function convertToStrLaraValidation(array $allowedFileExtensions, $type = 'string') {
$validations = [ $validations = [
'mimes:' . implode(',', $allowedFileExtensions), 'mimes:' . implode(',', self::_convertFileExtensionToMimes($allowedFileExtensions)),
'extensions:' . implode(',', $allowedFileExtensions) 'extensions:' . implode(',', $allowedFileExtensions)
]; ];
@ -23,5 +25,14 @@ class FileHelper {
else if($type == 'array') return $validations; else if($type == 'array') return $validations;
else throw new \Exception('Type not valid'); else throw new \Exception('Type not valid');
} }
private static function _convertFileExtensionToMimes($allowedFileExtensions) {
$mimes = [];
foreach($allowedFileExtensions as $allowedFileExtension) {
if($allowedFileExtension == 'apk') $mimes[] = 'zip'; // mimes of apk == zip
else $mimes[] = $allowedFileExtension;
}
return $mimes;
}
} }
?> ?>

@ -0,0 +1,51 @@
<?php
namespace App\Helper\Frontend;
use App\Helper\JSONResponse;
use App\Models\Tv;
use Illuminate\Http\Request;
/**
* Note:
* if any of frontend component need specific api query / data specific.
* Please add function to get data here.
*
* But, if the parent component still needs to pass the url to the
* child component, the url api can be placed in the app/Http/Controllers/api/superadmin/GeneralController
*/
class ApiUtilities {
// for components/app/tv/button/excel.vue
public static function tvExcel(Request $request) {
$request->validate(['a' => 'nullable|string']);
switch($request->a) {
case 'excelTemplate':
return Tv::getExcelTemplate();
break;
case 'validateData':
$tvCodes = $request->tvCodes ?? [];
$tvs = $request->tvs ?? [];
$oValidation = TV::validateExcel($tvs, $tvCodes);
return JSONResponse::Success(['oValidation' => $oValidation]);
break;
case 'uploadExcel':
$tvCodes = $request->tvCodes ?? [];
$tvs = $request->tvs ?? [];
$oValidation = TV::validateExcel($tvs, $tvCodes);
$result = TV::uploadExcel($tvs, $oValidation, $request->user());
return JSONResponse::Success($result);
break;
case 'exportData':
return Tv::getExportData($request);
break;
case 'excelDetail':
return Tv::getExcelDetail($request);
break;
}
throw new \Exception('Invalid Request Command');
}
}
?>

@ -0,0 +1,180 @@
<?php
namespace App\Helper\STS;
use App\Helper\JSONResponse;
use App\Models\StsLog;
use App\Models\Tv;
use Carbon\Carbon;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
class Indokargo {
const STATUS_SUCCESS = 'Success';
const STATUS_FAILURE = 'Failure';
// const STATUS_WARNING = 'Warning';
//------------------------------------------------------------------------
//-- UTILITIES
private static function _checkIkResponse($res) {
$status = $res['status'];
if($res['status'] == self::STATUS_SUCCESS) return;
else if($res['status'] == self::STATUS_FAILURE) {
// note: Indokargo error message can be array or string
$errorMessage = $res['error']['error_message'];
if(is_array($errorMessage)) throw ValidationException::withMessages($errorMessage);
else throw new \Exception($errorMessage);
}
else {
throw new \Exception("Indokargo status is not valid (ik status = $status)" );
}
}
private static function _HttpTransaction(StsLog $stsLog, $callback) {
try {
return $callback();
} catch(ConnectionException $e) {
// if error cause by connection error, try again later
$stsLog->error_info = [
'message' => $e->getMessage(),
'errors' => $e->getTrace()
];
$stsLog->result = StsLog::STATUS_FAILED;
$stsLog->is_retry = true;
$stsLog->save();
return JSONResponse::Success(['is_warning' => true,
'message' => "Failed to sync data with indokargo data, don't worry, we will sync data later"]);
} catch(\Throwable $th) {
DB::rollBack();
$stsLog->error_info = [
'message' => $th->getMessage(),
'errors' => $th->getTrace()
];
$stsLog->result = StsLog::STATUS_FAILED;
$stsLog->save();
throw $th;
}
}
//-- END UTILITIES
//------------------------------------------------------------------------
public static function createTVAddress(Request $request, int $tvFk, int $lastSeq = 0) {
$seq = $lastSeq + 1;
/**
* local id = TV FK, partnerId = ik_address_id
* Why not new tv request id as local_id?
* - New Tv request data will be deleted after TV data is inserted into the local DB
*/
$stsLog = new StsLog();
$stsLog->partner = StsLog::PARTNER_INDOKARGO;
$stsLog->is_outgoing = true;
$stsLog->module = StsLog::MODULE_TV;
$stsLog->service_name = StsLog::SERVICE_CREATE_TV_ADDRESS;
$stsLog->local_id = $tvFk;
$stsLog->partner_id = null;
$stsLog->seq = $seq;
$stsLog->request_data = $request->all();
$stsLog->request_time = Carbon::now();
return self::_HttpTransaction($stsLog, function() use($tvFk, $request, $stsLog) {
$request->validate(['code' => 'required|string']);
$result = Http::indokargo()->post('tv/address/create', $request->all());
$res = $result->throw()->json();
// update stsLog
$stsLog->response_data = $res;
$stsLog->response_time = Carbon::now();
self::_checkIkResponse($res);
$ikAddress = $res['data']['0'];
$ikAddressId = $ikAddress['id'];
$stsLog->partner_id = $ikAddressId;
// check ik address id is exist (almost impossible to happen, 0.01%)
$isDuplicateIkAddressId = TV::where([
['ik_address_id', '=', $ikAddressId],
['id', '!=', $tvFk]
])->first();
if($isDuplicateIkAddressId) throw new \Exception("IK Address ID Already exist in current db ($ikAddressId)");
DB::beginTransaction();
$tv = Tv::findOrFail($tvFk);
$tv->ik_address_id = $ikAddressId;
$tv->save();
$stsLog->result = StsLog::STATUS_SUCCESS;
$stsLog->save();
DB::commit();
return JSONResponse::Success(['is_warning' => false, 'message' => 'Success To Save Data']);
});
}
public static function updateTVAddress(Request $request, int $tvFk, String $ikAddressId, int $lastSeq = 0) {
$seq = $lastSeq + 1;
$request->merge(['ik_address_id' => $ikAddressId]);
$stsLog = new StsLog();
$stsLog->partner = StsLog::PARTNER_INDOKARGO;
$stsLog->is_outgoing = true;
$stsLog->module = StsLog::MODULE_TV;
$stsLog->service_name = StsLog::SERVICE_UPDATE_TV_ADDRESS;
$stsLog->local_id = $tvFk;
$stsLog->partner_id = $ikAddressId;
$stsLog->seq = $seq;
$stsLog->request_data = $request->all();
$stsLog->request_time = Carbon::now();
return self::_HttpTransaction($stsLog, function() use($stsLog, $request) {
$request->validate(['code' => 'required|string', 'ik_address_id' => 'required|string']);
$result = Http::indokargo()->post('tv/address/update-tv', $request->all());
$res = $result->throw()->json();
// update stsLog
$stsLog->response_data = $res;
$stsLog->response_time = Carbon::now();
self::_checkIkResponse($res);
$stsLog->result = StsLog::STATUS_SUCCESS;
$stsLog->save();
DB::commit();
return JSONResponse::Success(['message' => "Success to save data"]);
});
}
public static function changeStatusAddress(Request $request, int $tvFk, String $ikAddressId, int $lastSeq = 0) {
$seq = $lastSeq + 1;
$request->merge(['ik_address_id' => $ikAddressId]);
$stsLog = new StsLog();
$stsLog->partner = StsLog::PARTNER_INDOKARGO;
$stsLog->is_outgoing = true;
$stsLog->module = StsLog::MODULE_TV;
$stsLog->service_name = StsLog::SERVICE_CHANGE_STATUS_TV_ADDRESS;
$stsLog->local_id = $tvFk;
$stsLog->partner_id = $request->ik_address_id;
$stsLog->seq = $seq;
$stsLog->request_data = $request->all();
$stsLog->request_time = Carbon::now();
return self::_HttpTransaction($stsLog, function() use($stsLog, $request) {
$request->validate(['ik_address_id' => 'required|string', 'is_active'=> 'required|boolean']);
$result = Http::indokargo()->post('tv/address/change-status', $request->all());
$res = $result->throw()->json();
// update stsLog
$stsLog->response_data = $res;
$stsLog->response_time = Carbon::now();
self::_checkIkResponse($res);
$stsLog->result = StsLog::STATUS_SUCCESS;
$stsLog->save();
DB::commit();
return JSONResponse::Success(['is_warning' => false, 'message' => 'Success To Change Status']);
});
}
}
?>

@ -41,7 +41,7 @@ class AuthController extends Controller {
} }
public function changePassword(Request $request) { public function changePassword(Request $request) {
$request->user()->changePassword($request); $request->merge(['id' => $request->user()->id]);
return JSONResponse::Success(); return User::changePasswordFromRequest($request);
} }
} }

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\api\mobile;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\ApkUpdate;
use App\Models\VideoUpdate;
use Illuminate\Http\Request;
class CheckUpdateController extends Controller {
public function videoUpdateLatest(Request $request) {
$lastestVideoUpdate = VideoUpdate::getLatestSelected();
return JSONResponse::Success(['latestVideoUpload' => $lastestVideoUpdate]);
}
public function apkUpdateLatest(Request $request) {
$latestApkUpdate = ApkUpdate::getLatest();
return JSONResponse::Success(['latestApkUpdate' => $latestApkUpdate]);
}
}

@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\api\mobile;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\ApkUpdate;
use App\Models\NewTvRequest;
use App\Models\Tv;
use App\Models\VideoUpdate;
use Illuminate\Http\Request;
class TvController extends Controller {
public function newRequest(Request $request) {
return NewTvRequest::checkOrCreateFromRequest($request);
}
public function checkUpdate(Request $request) {
$request->validate(['id' => 'nullable|integer',
'apk_version_code' => 'required|integer',
'apk_version_name' => 'required|string']);
$tv = null;
$latestApkUpdate = null;
$latestVideoUpdate = null;
if($request->id) {
$tv = Tv::find($request->id);
$tv->last_connected_at = now();
$tv->apk_version_code = $request->apk_version_code;
$tv->apk_version_name = $request->apk_version_name;
$tv->save();
$latestApkUpdate = ApkUpdate::getLatest();
$latestVideoUpdate = VideoUpdate::getLatestSelected();
}
return JSONResponse::Success(['tv' => $tv, 'latestApkUpdate' => $latestApkUpdate, 'latestVideoUpdate' => $latestVideoUpdate]);
}
}

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\api\superadmin;
use App\Helper\DatabaseHelper;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\ApkUpdate;
use Illuminate\Http\Request;
class ApkUploadController extends Controller {
public function init(Request $request) {
$request->validate([
'perPage' => 'nullable|integer|min:1',
...DatabaseHelper::getOrderBysValidations(),
'search' => DatabaseHelper::getSearchValidation()
]);
$data = ApkUpdate::multiSearch($request->search, ['name'])
->multiOrderBy($request->orderBys, 'version_code desc')
->paginate($request->perPage ?? 10 );
return JSONResponse::Success(['data' => $data]);
}
public function save(Request $request) { return ApkUpdate::upsertFromRequest($request); }
public function update(Request $request) { return ApkUpdate::upsertFromRequest($request); }
public function delete(Request $request) { return ApkUpdate::deleteFromRequest($request); }
}

@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\api\superadmin;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\Tv;
use Illuminate\Http\Request;
class GeneralController extends Controller {
public function tvSearch(Request $request) {
$request->validate(['search' => 'nullable|string']);
$tvs = Tv::multiSearch($request->search, ['code'])->orderBy('code', 'asc')->limit(10)->get();
return JSONResponse::Success(['tvs' => $tvs ]);
}
}

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\api\superadmin;
use App\Helper\DatabaseHelper;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
class UserManagementController extends Controller {
public function init(Request $request) {
$request->validate([
'perPage' => 'nullable|integer|min:1',
...DatabaseHelper::getOrderBysValidations(),
'search' => DatabaseHelper::getSearchValidation()
]);
$data = User::multiSearch($request->search, ['email', 'username'])
->multiOrderBy($request->orderBys, 'created_at desc')
->paginate($request->perPage ?? 10 );
return JSONResponse::Success(['data' => $data]);
}
public function save(Request $request) { return User::upsertFromRequest($request); }
public function update(Request $request) { return User::upsertFromRequest($request); }
public function changePassword(Request $request) { return User::changePasswordFromRequest($request); }
public function delete(Request $request) { return User::deleteFromRequest($request); }
public function changeStatus(Request $request) { return User::changeStatusFromRequest($request); }
}

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\api\superadmin\tv;
use App\Helper\DatabaseHelper;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\NewTvRequest;
use Illuminate\Http\Request;
class NewTvRequestController extends Controller {
public function init(Request $request) {
NewTvRequest::deleteExpiredRequests();
$request->validate([
'perPage' => 'nullable|integer|min:1',
...DatabaseHelper::getOrderBysValidations(),
'search' => DatabaseHelper::getSearchValidation()
]);
$newTvRequests = NewTvRequest::with('tv')
->addColumnCanApprove()
->addColumnCanReject()
->multiSearch($request->search, ['code'])
->multiOrderBy($request->orderBys, 'created_at desc')
->paginate($request->perPage ?? 10);
return JSONResponse::Success(['data' => $newTvRequests ]);
}
public function approve(Request $request) { return NewTvRequest::approveFromRequest($request); }
public function reject(Request $request) { return NewTvRequest::rejectFromRequest($request); }
}

@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\api\superadmin\tv;
use App\Helper\DatabaseHelper;
use App\Helper\Frontend\ApiUtilities;
use App\Helper\JSONResponse;
use App\Http\Controllers\Controller;
use App\Models\ApkUpdate;
use App\Models\Tv;
use Illuminate\Http\Request;
class TvController extends Controller {
public function init(Request $request) {
$request->validate([
'perPage' => 'nullable|integer|min:1',
'isFirstTime' => 'nullable|boolean',
]);
$additionalData = [];
if($request->isFirstTime) {
$additionalData['apkUpdates'] = ApkUpdate::select('version_code', 'version_name')
->orderBy('version_code', 'desc')
->get();
}
$newTvRequests = TV::validateAndGetEloquentFromRequest($request)->paginate($request->perPage ?? 10);
return JSONResponse::Success(['data' => $newTvRequests, ...$additionalData ]);
}
public function excel(Request $request) { return ApiUtilities::tvExcel($request); }
public function update(Request $request) { return Tv::updateFromRequest($request); }
public function changeStatus(Request $request) { return Tv::changeStatusFromRequest($request); }
}

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class MobileMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response {
$serverMobileAuthToken = env('MOBILE_AUTH_TOKEN', '');
if(!$serverMobileAuthToken) throw new \Exception('Mobile Auth token in server is not found');
$clientMobileAuthToken = $request->header('mobile-token', '');
if(!$clientMobileAuthToken) $clientMobileAuthToken = $request->header('mobile-auth-token', '');
if($serverMobileAuthToken != $clientMobileAuthToken) throw new \Exception('Invalid Mobile Auth Token');
return $next($request);
}
}

@ -2,11 +2,141 @@
namespace App\Models; namespace App\Models;
use App\Helper\Common;
use App\Helper\FileHelper;
use App\Helper\JSONResponse;
use App\Helper\Traits\Models\CanMultiOrderBy;
use App\Helper\Traits\Models\CanMultiSearch;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\File;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
class ApkUpdate extends Model { class ApkUpdate extends Model {
use HasFactory; use HasFactory;
use CanMultiSearch;
use CanMultiOrderBy;
protected $table = 'apk_updates'; protected $table = 'apk_updates';
protected $hidden = ['file'];
protected $appends = ['file_url'];
// ---------------------------------------------------------------------------------------
// -- RELATED TO SCOPE
public function scopeNewest(Builder $query){
return $query->orderByDesc('version_code');
}
// -- END RELATED TO SCOPE
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// -- RELATED TO GET DATA
public static function getLatest(){ return self::latest('version_code')->first(); }
// -- END RELATED TO GET DATA
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// -- RELATED TO MODIFICATION DATA FROM REQUEST
public static function upsertFromRequest(Request $request) {
$request->validate([
'id' => 'nullable|integer|exists:App\Models\ApkUpdate,id',
'name' => 'required|string',
'file' => 'required_without:id|file|' . FileHelper::convertToStrLaraValidation(FileHelper::$allowedApkExtensions),
'version_code' => ['required', 'integer', 'min:1', Rule::unique('apk_updates', 'version_code')->ignore($request->id)],
'version_name' => 'required|string',
'change_note' => 'nullable|string'
], [
'file' => ['required_without' => 'The file field is required.']
]);
$delOldDbFileLocation = '';
$newDbFileLocation = '';
try {
// save photo
if($request->file) $newDbFileLocation = self::saveFile($request->file)['db_url'];
// try to upsert data
DB::beginTransaction();
$apkUpdate = null;
if(!$request->id) $apkUpdate = new ApkUpdate();
else $apkUpdate = ApkUpdate::findOrFail($request->id);
// del old db file location if has old file
if($newDbFileLocation) {
if($apkUpdate->file) $delOldDbFileLocation = $apkUpdate->file;
$apkUpdate->file = $newDbFileLocation;
}
$apkUpdate->name = $request->name;
$apkUpdate->version_code = $request->version_code;
$apkUpdate->version_name = $request->version_name;
$apkUpdate->change_note = $request->change_note;
$apkUpdate->save();
// delete old file if exist
if($delOldDbFileLocation) self::deleteFile($delOldDbFileLocation);
DB::commit();
return JSONResponse::Success();
} catch (\Throwable $th) {
DB::rollBack();
if($newDbFileLocation) self::deleteFile($newDbFileLocation);
throw $th;
}
}
public static function deleteFromRequest(Request $request) {
$request->validate(['id' => 'required|integer|exists:App\Models\ApkUpdate,id']);
try {
DB::beginTransaction();
$apkUpdate = ApkUpdate::findOrFail($request->id);
$oldDbFile = $apkUpdate->file;
$apkUpdate->delete();
if($oldDbFile) self::deleteFile($oldDbFile);
DB::commit();
return JSONResponse::Success();
} catch (\Throwable $th) {
DB::rollBack();
throw $th;
}
}
// -- END RELATED TO MODIFICATION DATA FROM REQUEST
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// -- FILE UTILITIES
protected function fileUrl(): Attribute {
return Attribute::make(
fn() => $this->file ? Storage::disk('s3')->url($this->file) : ''
);
}
private static function fileFolder() { return env('STORAGE_FOLDER', 'tivi') . '/apk-upload'; }
public static function saveFile($file) {
if (!$file->isValid()) throw new \Exception('File is not valid');
//Save file to local data
$fileName = self::getFileName($file);
$path = self::fileFolder();
Storage::disk('s3')->put($path . '/' . $fileName, file_get_contents(new File($file)), 'public');
return ['db_url' => "$path/$fileName"];
}
private static function getFileName($file) {
$tz = Carbon::now()->timestamp;
$extension = $file->getClientOriginalExtension();
$name = "$tz-" . Common::generateRandomString();
return "$name.$extension";
}
public static function deleteFile($dbUrl) { Storage::disk('s3')->delete($dbUrl); }
// -- END FILE UTILITIES
// ---------------------------------------------------------------------------------------
} }

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TvAppInfo extends Model {
use HasFactory;
protected $table = 'tv_app_infos';
protected $primaryKey = 'tv_fk';
public $incrementing = false;
//------------------------------------------------------------
// -- RELATED TO RELATIONSHIP
/// BELONGS TO
public function tv(): BelongsTo { return $this->belongsTo(Tv::class, 'tv_fk', 'id'); }
// -- END RELATED TO RELATIONSHIP
//------------------------------------------------------------
}

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TvConnectLog extends Model {
use HasFactory;
//------------------------------------------------------------
// -- RELATED TO RELATIONSHIP
/// BELONGS TO
public function tv(): BelongsTo { return $this->belongsTo(Tv::class, 'tv_fk', 'id'); }
// -- END RELATED TO RELATIONSHIP
//------------------------------------------------------------
}

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TvSession extends Model {
use HasFactory;
//------------------------------------------------------------
// -- RELATED TO RELATIONSHIP
/// BELONGS TO
public function tv(): BelongsTo { return $this->belongsTo(Tv::class, 'tv_fk', 'id'); }
// -- END RELATED TO RELATIONSHIP
//------------------------------------------------------------
}

@ -0,0 +1,225 @@
<?php
namespace App\Models;
use App\Helper\JSONResponse;
use App\Helper\STS\Indokargo;
use App\Helper\Traits\Models\CanMultiOrderBy;
use App\Helper\Traits\Models\CanMultiSearch;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class NewTvRequest extends Model {
use HasFactory;
use CanMultiSearch;
use CanMultiOrderBy;
protected $table = 'new_tv_requests';
protected $casts = ['device_info'=>AsArrayObject::class];
//------------------------------------------------------------
// -- RELATED TO RELATIONSHIP
// HAS ONE
public function self(): HasOne { return $this->hasOne(self::class, 'id', 'id'); }
/// BELONGS TO
public function tv(): BelongsTo { return $this->belongsTo(Tv::class, 'tv_fk', 'id'); }
// -- END RELATED TO RELATIONSHIP
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO SCOPE
public function scopeAddColumnCanApprove(Builder $query) {
$query->withCount(['self as can_approve' => function($q) {
$q->whereNull('approved_at');
}]);
}
public function scopeAddColumnCanReject(Builder $query) {
$query->withCount(['self as can_reject' => function($q) {
$q->whereNull('approved_at');
}]);
}
// -- END RELATED TO SCOPE
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO DATA FUNCTION
private static function _generateRandomCode(): string {
$totalDigit = 6;
$randomDigit = '';
while(true) {
$randomDigit = strtoupper(Str::random($totalDigit));
$isExist = NewTvRequest::where('code', $randomDigit)->first();
if(!$isExist) break;
}
return $randomDigit;
}
/**
* Rule:
* 1. if it has not been approved for more than the expiry date, deleted it
* 2. if it has been approved & has been responded more than expiry date, delete it
* - case: when has been approved, but intenet connection missing, how can device know
* that the new request tv has been approved?
*/
private static function _getMaxExpiredTime() :Carbon { return Carbon::now()->subHour(); }
public static function deleteExpiredRequests() {
$expiredTime = self::_getMaxExpiredTime()->toDateTimeString();
NewTvRequest::where(function($q) use ($expiredTime) {
$q->whereNull('approved_at')->where('created_at', '<=', $expiredTime);
})->orWhere(function($q) use ($expiredTime) {
$q->where('responded_at', '<=', $expiredTime );
})->delete();
}
// -- END RELATED TO DATA FUNCTION
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO FROM REQUEST FUNCTION
private static function _checkOrCreateResponse(NewTvRequest $newTvReq) {
if($newTvReq->approved_at) {
// make sure waiting tv_device responded before deleted
if(empty($newTvReq->responded_at)) {
$newTvReq->responded_at = now();
$newTvReq->save();
}
return JSONResponse::Success([
'message' => 'approved',
'new_tv_request' => $newTvReq,
'tv' => $newTvReq->tv
]);
}
return JSONResponse::Success([
'message' => 'not activate',
'new_tv_request' => $newTvReq,
'tv' => null
]);
}
public static function checkOrCreateFromRequest(Request $request) {
$request->validate([
'id' => 'nullable|string|required_with:code|integer',
'code' => 'nullable|string|required_with:id',
'device_info' => 'required|array'
]);
if(!empty($request->id)) {
$checkTvReq = NewTvRequest::where('code', $request->code)->find($request->id);
if($checkTvReq) return self::_checkOrCreateResponse($checkTvReq);
}
$newTvReq = new self;
$newTvReq->code = self::_generateRandomCode();
$newTvReq->device_info = $request->device_info;
$newTvReq->save();
return self::_checkOrCreateResponse($newTvReq);
}
public static function approveFromRequest(Request $request) {
$request->validate([
'id' => 'required|integer|exists:App\Models\NewTvRequest',
'tv' => 'nullable|array',
'existingTv' => 'nullable|array',
'action' => 'required|string',
]);
$newTvReq = NewTvRequest::addColumnCanApprove()
->findOrFail($request->id);
if(!$newTvReq->can_approve) throw new \Exception('Cannot approve current request');
try {
$tv = null;
if($request->action == 'existing') {
$existingTvRequest = new Request($request->existingTv);
$existingTvRequest->validate([
'id' => 'required|integer|exists:App\Models\Tv',
]);
$tv = TV::findOrFail($existingTvRequest->id);
} else if ($request->action == 'new') {
$tvRequest = new Request($request->tv);
if($tvRequest->code) $tvRequest->merge(['code' => strtoupper($tvRequest->code)]);
$tvRequest->validate([
'company_name' => 'nullable|string',
'address' => 'nullable|string',
'street_address' => 'nullable|string',
'col1' => 'nullable|string',
'col2' => 'nullable|string',
'col3' => 'nullable|string',
'col4' => 'nullable|string',
'col5' => 'nullable|string',
'col6' => 'nullable|string',
'col7' => 'nullable|string',
'col8' => 'nullable|string',
'col9' => 'nullable|string',
'col10' => 'nullable|string',
'notes' => 'nullable|string',
]);
DB::beginTransaction();
$tv = new Tv();
$tv->code = TV::generateUniqueCode();
$tv->company_name = $tvRequest->company_name;
$tv->address = $tvRequest->address;
$tv->street_address = $tvRequest->street_address;
$tv->col1 = $tvRequest->col1;
$tv->col2 = $tvRequest->col2;
$tv->col3 = $tvRequest->col3;
$tv->col4 = $tvRequest->col4;
$tv->col5 = $tvRequest->col5;
$tv->col6 = $tvRequest->col6;
$tv->col7 = $tvRequest->col7;
$tv->col8 = $tvRequest->col8;
$tv->col9 = $tvRequest->col9;
$tv->col10 = $tvRequest->col10;
$tv->notes = $tvRequest->notes;
$tv->device_info = $newTvReq->device_info;
$tv->installed_at = now();
$tv->save();
TvLog::historyCreate($request->user(), $tv->id, $tv);
// TODO: waiting execution until update from ops
// NEED TO REFACTOR (cause by add existing tv code)
// // try to sys_to_sys with indokargo
// DB::commit();
// $jsonResponse = Indokargo::createTVAddress($tvRequest, $tv->id);
// return $jsonResponse;
} else {
throw new \Exception('INVALID FORM ACTION');
}
$newTvReq->approved_at = now();
$newTvReq->tv_fk = $tv->id;
$newTvReq->save();
DB::commit();
return JSONResponse::Success(['message'=>'Success to approve new tv request']);
} catch(\Throwable $th) {
DB::rollback();
throw $th;
}
}
public static function rejectFromRequest(Request $request) {
$request->validate([
'id' => 'required|integer|exists:App\Models\NewTvRequest',
]);
$newTvRequest = NewTvRequest::addColumnCanReject()
->findOrFail($request->id);
if(!$newTvRequest->can_reject) throw new \Exception('Cannot reject current request');
$newTvRequest->delete();
return JSONResponse::Success(['data' => $newTvRequest ]);
}
// -- END RELATED TO FROM REQUEST FUNCTION
//------------------------------------------------------------
}

@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Outlet extends Model {
use HasFactory;
protected $table = 'outlets';
//------------------------------------------------------------
// -- RELATED TO MIGRATION
/// HAS ONE
public function tv_app_info(): HasOne { return $this->hasOne(Tv::class, 'outlet_fk', 'id'); }
// -- END RELATED TO MIGRATION2
//------------------------------------------------------------
}

@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StsLog extends Model {
use HasFactory;
protected $table = 'sts_logs';
protected $fillable = [
'partner',
'is_outgoing',
'module',
'service_name',
'local_id',
'partner_id',
'seq',
'result',
'error_info',
'request_data',
'request_time',
'response_data',
'response_time',
'is_retry',
'created_at',
'updated_at'
];
protected $casts = [
'error_info' => AsArrayObject::class,
'request_data' => AsArrayObject::class,
'response_data' => AsArrayObject::class,
];
const STATUS_SUCCESS = 'success';
const STATUS_FAILED = 'failed';
const STATUS_WARNING = 'warning';
const PARTNER_INDOKARGO = 'indokargo';
// related to indokargo
const MODULE_TV = 'tv';
const SERVICE_CREATE_TV_ADDRESS = 'create-tv-address';
const SERVICE_UPDATE_TV_ADDRESS = 'update-tv-address';
const SERVICE_CHANGE_STATUS_TV_ADDRESS = 'change-statustv-address';
}

@ -0,0 +1,372 @@
<?php
namespace App\Models;
use App\Helper\Common;
use App\Helper\DatabaseHelper;
use App\Helper\JSONResponse;
use App\Helper\STS\Indokargo;
use App\Helper\Traits\Models\CanMultiOrderBy;
use App\Helper\Traits\Models\CanMultiSearch;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class Tv extends Model {
use HasFactory;
use CanMultiSearch;
use CanMultiOrderBy;
protected $table = 'tvs';
protected $hidden = ['ik_cust_id', 'ik_address_id'];
protected $casts = ['device_info'=>AsArrayObject::class];
//------------------------------------------------------------
// -- RELATED TO RELATIONSHIP
/// HAS ONE
public function new_tv_request(): HasOne { return $this->hasOne(NewTvRequest::class, 'tv_fk', 'id'); }
public function tv_app_info(): HasOne { return $this->hasOne(TvAppInfo::class, 'tv_fk', 'id'); }
/// HAS MANY
public function tv_connect_logs(): HasMany { return $this->hasMany(TvConnectLog::class, 'tv_fk', 'id'); }
public function tv_sessions(): HasMany { return $this->hasMany(TvSession::class, 'tv_fk', 'id'); }
/// BELONGS TO
public function outlet(): BelongsTo { return $this->belongsTo(Outlet::class, 'outlet_fk', 'id'); }
// -- END RELATED TO RELATIONSHIP
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO DATA FUNCTION
public static function generateUniqueCode() {
// init
$countSuffixDigit = 5;
date_default_timezone_set('Asia/Jakarta');
$prefixCode = 'TV' . date('Ym');
// try to get unique suffix
$uniqueCode = '';
while(true) {
$suffixCode = strtoupper(Str::random($countSuffixDigit));
$uniqueCode = $prefixCode . $suffixCode;
$isUnique = TV::where('code', $uniqueCode)->first();
if(!$isUnique) break;
}
return $uniqueCode;
}
// -- END RELATED TO DATA FUNCTION
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO REQUEST
public static function validateAndGetEloquentFromRequest(Request $request) {
$request->validate([
...DatabaseHelper::getOrderBysValidations(),
'search' => DatabaseHelper::getSearchValidation(),
'apkVersionCode' => 'nullable|integer',
'tz' => 'required|timezone',
'isActive' => 'nullable|boolean',
'installedAt' => 'nullable|array',
'installedAt.from' => 'nullable|date',
'installedAt.to' => 'nullable|date',
'lastConnectedAt' => 'nullable|array',
'lastConnectedAt.from' => 'nullable|date',
'lastConnectedAt.to' => 'nullable|date',
'lastConnectedDay' => 'nullable|int'
]);
Common::setTimezone($request->tz);
return Tv::when($request->isActive != null, function($q) use($request) {
$q->where('is_active', $request->isActive);
})
->when($request->apkVersionCode, function($q, $apkVersionCode) {
$q->where('apk_version_code', $apkVersionCode);
})
->when($request->installedAt['from'] ?? '', function($q, $from) {
$q->where(DB::raw('DATE(installed_at)'), '>=', $from);
})
->when($request->installedAt['to'] ?? '', function($q, $to) {
$q->where(DB::raw('DATE(installed_at)'), '<=', $to);
})
->when($request->lastConnectedAt['from'] ?? '', function($q, $from) {
$q->where(DB::raw('DATE(last_connected_at)'), '>=', $from);
})
->when($request->lastConnectedAt['to'] ?? '', function($q, $to) {
$q->where(DB::raw('DATE(last_connected_at)'), '<=', $to);
})
->multiSearch($request->search, ['code', 'company_name'])
->multiOrderBy($request->orderBys, 'created_at desc');
}
public static function updateFromRequest(Request $request) {
if($request->code) $request->merge(['code' => strtoupper($request->code)]);
$request->validate([
'id' => 'required|integer|exists:App\Models\Tv',
'code' => ['required','string',Rule::unique('tvs', 'code')
->when($request->id, function($q, $id) { $q->whereNot('id', $id);})],
'company_name' => 'nullable|string',
'address' => 'nullable|string',
'street_address' => 'nullable|string',
'col1' => 'nullable|string',
'col2' => 'nullable|string',
'col3' => 'nullable|string',
'col4' => 'nullable|string',
'col5' => 'nullable|string',
'col6' => 'nullable|string',
'col7' => 'nullable|string',
'col8' => 'nullable|string',
'col9' => 'nullable|string',
'col10' => 'nullable|string',
'notes' => 'nullable|string',
]);
try {
DB::beginTransaction();
$tv = TV::findOrFail($request->id);
$oldTv = $tv->replicate();
$tv->company_name = $request->company_name;
$tv->address = $request->address;
$tv->street_address = $request->street_address;
$tv->code = $request->code;
$tv->col1 = $request->col1;
$tv->col2 = $request->col2;
$tv->col3 = $request->col3;
$tv->col4 = $request->col4;
$tv->col5 = $request->col5;
$tv->col6 = $request->col6;
$tv->col7 = $request->col7;
$tv->col8 = $request->col8;
$tv->col9 = $request->col9;
$tv->col10 = $request->col10;
$tv->notes = $request->notes;
$tv->update();
TvLog::historyUpdate($request->user(), $tv->id, $oldTv, $tv);
DB::commit();
return JSONResponse::Success(['message'=>'Success to update tv data']);
// TODO: waiting from ops workflow (Dont forget to resync co_name, address, street address in ik)
// // try to sys_to_sys with indokargo
// $jsonResponse = Indokargo::updateTVAddress($request, $tv->id, $tv->ik_address_id);
// DB::commit();
// return $jsonResponse;
} catch(\Throwable $th) {
DB::rollback();
throw $th;
}
}
public static function changeStatusFromRequest(Request $request) {
$request->validate(['id' => 'required|integer|exists:App\Models\TV,id']);
try {
DB::beginTransaction();
$tv = Tv::findOrFail($request->id);
$tv->is_active = !$tv->is_active;
$tv->save();
DB::commit();
return JSONResponse::Success(['message'=>'Success to change tv status']);
// TODO: waiting from ops workflow (Dont forget to resync co_name, address, street address in ik)
// // try to sys_to_sys with indokargo
// $jsonResponse = Indokargo::changeStatusAddress(new Request(['is_active' => $tv->is_active]),
// $tv->id, $tv->ik_address_id);
// DB::commit();
// return $jsonResponse;
} catch(\Throwable $th) {
DB::rollback();
throw $th;
}
}
// -- END RELATED TO REQUEST
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO EXCEL
public static function getExcelDetail(Request $request) {
return JSONResponse::Success(['rows' => TV::validateAndGetEloquentFromRequest($request)->get()]);
}
// ---- RELATED TO EXPORT IMPORT
const EXCEL_SUCCESS = 'success';
const EXCEL_FAILED = 'failed';
const EXCEL_NO_CHANGE = 'no_change';
const EXCEL_UPDATE = 'update';
const EXCEL_TEMPLATE_COLS = ['code', 'company_name', 'address', 'street_address', 'notes',
'col1', 'col2', 'col3', 'col4', 'col5',
'col6', 'col7', 'col8', 'col9', 'col10',
'is_active'];
public static function getExcelTemplate() {
$row = [];
foreach(self::EXCEL_TEMPLATE_COLS as $col) { $row[$col] = ''; }
$rows = [$row];
return JSONResponse::Success(['rows' => $rows]);
}
public static function getExportData(Request $request) {
$rows = TV::validateAndGetEloquentFromRequest($request)->get();
return JSONResponse::Success(['rows' => $rows, 'cols' => self::EXCEL_TEMPLATE_COLS]);
}
// ------ RELATED TO IMPORT
private static function _checkExcelFormat($tvRows) {
if(!$tvRows) throw new \Exception("No Data");
$firstTvRow = $tvRows[0];
if(!$firstTvRow) throw new \Exception('Column not found');
// check if excel is empty or not
$firstTvRow['row'] = null;
if(!array_filter($firstTvRow)) throw new \Exception("Excel is empty");
// check is header col is exists
$errors = [];
foreach(self::EXCEL_TEMPLATE_COLS as $col) {
if(!array_key_exists($col, $firstTvRow)) $errors[$col] = "Column $col is Required";
}
if($errors) throw ValidationException::withMessages($errors);
}
private static function _getCountTvCodes($tvRows, $tvCodes) {
if(!$tvCodes) $tvCodes = array_column($tvRows, 'code');
$tvCodes = array_map(function($code) { return DatabaseHelper::trimUpperNull($code) ?? ''; }, $tvCodes);
return array_count_values($tvCodes);
}
private static function _changeModelFromTvRow(Tv $tv, $tvRow) {
$tv->company_name = $tvRow['company_name'];
$tv->address = $tvRow['address'];
$tv->street_address = $tvRow['street_address'];
$tv->code = $tvRow['code'];
$tv->col1 = $tvRow['col1'];
$tv->col2 = $tvRow['col2'];
$tv->col3 = $tvRow['col3'];
$tv->col4 = $tvRow['col4'];
$tv->col5 = $tvRow['col5'];
$tv->col6 = $tvRow['col6'];
$tv->col7 = $tvRow['col7'];
$tv->col8 = $tvRow['col8'];
$tv->col9 = $tvRow['col9'];
$tv->col10 = $tvRow['col10'];
$tv->notes = $tvRow['notes'];
return $tv;
}
public static function validateExcel($tvRows, $tvCodes = []) {
self::_checkExcelFormat($tvRows);
$countTvCodes = self::_getCountTvCodes($tvRows, $tvCodes);
$endStatus = self::EXCEL_SUCCESS;
$results = [];
foreach($tvRows as $tvRow) {
$status = self::EXCEL_NO_CHANGE;
$message = '';
$tvRow['code'] = strtoupper($tvRow['code'] ?? '');
try {
// STEP 1: check validation
$validator = Validator::make($tvRow, [
'row' => 'required|integer',
'code' => 'required|string',
'company_name' => 'nullable|string',
'address' => 'nullable|string',
'street_address' => 'nullable|string',
'col1' => 'nullable|string',
'col2' => 'nullable|string',
'col3' => 'nullable|string',
'col4' => 'nullable|string',
'col5' => 'nullable|string',
'col6' => 'nullable|string',
'col7' => 'nullable|string',
'col8' => 'nullable|string',
'col9' => 'nullable|string',
'col10' => 'nullable|string',
'notes' => 'nullable|string',
]);
if($validator->fails()) {
$errors = $validator->errors()->toArray();
$messages = [];
foreach($errors as $eMessages) { $messages = array_merge($messages, $eMessages); }
throw new \Exception(implode(', ', $messages));
}
// STEP 2: check code is duplicate or not or not
$code = $tvRow['code'];
if(($countTvCodes[$code] ?? 0) > 1) throw new \Exception('Code is Duplicate in Excel');
// STEP 3: check code existing in database or not
$tvCheck = Tv::where('code', 'ilike', $code)->first();
if(!$tvCheck) throw new \Exception("TV Code '$code' not found in database");
// STEP 4: check has update data or not
$tvCheck = self::_changeModelFromTvRow($tvCheck, $tvRow);
if($tvCheck->isDirty()) {
$status = self::EXCEL_UPDATE;
$message = DatabaseHelper::compileDirtyEloquentToArrMessage($tvCheck);
}
} catch (\Throwable $th) {
$endStatus = self::EXCEL_FAILED;
$status = self::EXCEL_FAILED;
$message = $th->getMessage();
}
$results[] = ['row' => $tvRow['row'], 'status' => $status, 'message' => $message];
}
return ['status' => $endStatus, 'results' => $results];
}
public static function uploadExcel($tvRows, $oValidation, User $user) {
$validationResults = $oValidation['results'];
$countUploads = [
self::EXCEL_UPDATE => 0, self::EXCEL_NO_CHANGE => 0, self::EXCEL_FAILED => 0
];
$additionalErrors = [];
foreach($validationResults as $result) {
$validateStatus = $result['status'];
try {
switch($validateStatus) {
case self::EXCEL_FAILED:
throw new \Exception($result['message']);
break;
case self::EXCEL_UPDATE:
// get sparepart data
$idxTvRow = array_search($result['row'], array_column($tvRows, 'row'));
if($idxTvRow === false) throw new \Exception('Row Not Found');
$tvRow = $tvRows[$idxTvRow];
$tvRow['code'] = strtoupper($tvRow['code'] ?? '');
// try to upsert
DB::beginTransaction();
$tv = TV::where('code', 'ilike', $tvRow['code'])->firstOrFail();
$oldTV = $tv->replicate();
$newTv = self::_changeModelFromTvRow($tv, $tvRow);
if(!$newTv->isDirty()) throw new \Exception('No Change');
$newTv->save();
// save data log
TvLog::historyUpdateExcel($user, $newTv->id, $oldTV, $newTv);
DB::commit();
break;
case self::EXCEL_NO_CHANGE:
default:
break;
}
} catch (\Throwable $th) {
DB::rollBack();
$validateStatus = self::EXCEL_FAILED;
$additionalErrors[] = 'row ' . ($result['row'] ?? '-') . ' => ' . $th->getMessage();
}
$countUploads[$validateStatus]++;
}
return ['countUploads' => $countUploads, 'additionalErrors' => $additionalErrors];
}
// ------ END RELATED TO IMPORT
// ---- END RELATED TO EXPORT IMPORT
// -- END RELATED TO EXCEL
//------------------------------------------------------------
}

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TvLog extends Model {
use HasFactory;
protected $table = 'tv_logs';
protected $hidden = ['ik_cust_id', 'ik_address_id'];
protected $casts = ['from'=>'object', 'to'=>'object'];
const TYPES = ['create', 'update', 'update-excel'];
public static function historyCreate(User $user, int $tvFk, Tv $newTv) { self::saveHistory('create', $user, $tvFk, null, $newTv); }
public static function historyUpdate(User $user, int $tvFk, Tv $oldTv, Tv $newTv) { self::saveHistory('update', $user, $tvFk, $oldTv, $newTv); }
public static function historyUpdateExcel(User $user, int $tvFk, Tv $oldTv, Tv $newTv) { self::saveHistory('update-excel', $user, $tvFk, $oldTv, $newTv); }
private static function saveHistory(String $type, ?User $user, int $tvFk, ?Tv $oldTv, ?Tv $newTv) {
if(!in_array($type, self::TYPES)) throw new \Exception("Type '$type' No Valid");
$tvLog = new TvLog();
$tvLog->tv_fk = $tvFk;
$tvLog->type = $type;
if($oldTv) {
$oldTv = $oldTv->toArray();
unset($oldTv['id']);
$tvLog->from =$oldTv;
}
if($newTv) {
$newTv = $newTv->toArray();
unset($newTv['id']);
$tvLog->to = $newTv;
}
if($user) { $tvLog->user_fk = $user->id; }
$tvLog->save();
}
}

@ -5,16 +5,23 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Helper\FileHelper; use App\Helper\FileHelper;
use App\Helper\JSONResponse;
use App\Helper\Traits\Models\CanMultiOrderBy;
use App\Helper\Traits\Models\CanMultiSearch;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable class User extends Authenticatable
{ {
use HasApiTokens, HasFactory, Notifiable; use HasApiTokens, HasFactory, Notifiable;
use CanMultiSearch;
use CanMultiOrderBy;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -49,8 +56,98 @@ class User extends Authenticatable
'password' => 'hashed', 'password' => 'hashed',
]; ];
public function changePassword(Request $request) { public static function upsertFromRequest(Request $request) {
$request->validate([ $request->validate([
'id' => 'nullable|integer|exists:App\Models\User,id',
'name' => 'required|string',
'username' => 'required|string',
'email' => 'required|email',
'password' => 'required_without:id|string|min:8',
'is_active' => 'required_with:id|boolean',
], [
'password.required_without' => 'The password field is required.',
'is_active.required_with' => 'The is active field is required.'
]);
try {
// try to upsert data
DB::beginTransaction();
$user = null;
if(!$request->id) {
$user = new User();
$user->is_active = $request->is_active;
$user->password = Hash::make($request->password);
}
else $user = User::findOrFail($request->id);
$user->name = $request->email;
$user->email = $request->email;
$user->username = $request->username;
$user->checkUniqueFieldBeforeExecuteDB();
$user->save();
// renew data;
DB::commit();
return JSONResponse::Success();
} catch (\Throwable $th) {
DB::rollBack();
throw $th;
}
}
public static function deleteFromRequest(Request $request) {
$request->validate(['id' => 'required|integer|exists:App\Models\User,id']);
try {
DB::beginTransaction();
$user = User::findOrFail($request->id);
if($user->is_selected) throw new \Exception("Cannot delete video when 'is Selected' is true");
$oldDbFile = $user->file;
$user->delete();
if($oldDbFile) self::deleteFile($oldDbFile);
DB::commit();
return JSONResponse::Success();
} catch (\Throwable $th) {
DB::rollBack();
throw $th;
}
}
public static function changeStatusFromRequest(Request $request) {
$request->validate(['id' => 'required|integer|exists:App\Models\User,id']);
$user = User::findOrFail($request->id);
$user->preventChangeForSelfUser($request->user());
$user->is_active = !$user->is_active;
$user->save();
return JSONResponse::Success();
}
public function preventChangeForSelfUser(User $currentUser) {
if($currentUser->id == $this->id) throw new \Exception("You cannot 'delete' / 'change status' your own user");
}
public function checkUniqueFieldBeforeExecuteDB() {
// check email
$isUsernameExist = User::where('username', $this->username)
->when($this->id, function(Builder $q, $userId) {
$q->where('id', '!=', $userId);
})->first();
if($isUsernameExist) throw new \Exception("Username '" . $this->username . "' has already used by another user");
// check email
$isEmailExist = User::where('email', $this->email)
->when($this->id, function(Builder $q, $userId) {
$q->where('id', '!=', $userId);
})->first();
if($isEmailExist) throw new \Exception("Email '" . $this->email . "' has already used by another user");
}
public static function changePasswordFromRequest(Request $request) {
$request->validate([
'id' => 'required|integer|exists:App\Models\User,id',
'newPassword' => 'required|string|min:8', 'newPassword' => 'required|string|min:8',
'confirmNewPassword' => 'required|string|min:8', 'confirmNewPassword' => 'required|string|min:8',
]); ]);
@ -59,18 +156,21 @@ class User extends Authenticatable
throw new \Exception("New Password & Confirm New Pasword are not same"); throw new \Exception("New Password & Confirm New Pasword are not same");
} }
$this->password = Hash::make($request->newPassword); $user = User::findOrFail($request->id);
$this->save(); $user->password = Hash::make($request->newPassword);
$user->save();
return JSONResponse::Success();
} }
public function getObjSession($currentAccessToken) { public function getObjSession($currentAccessToken) {
return [ return [
'name' => $this->name,
'username' => $this->username, 'username' => $this->username,
'email' => $this->email, 'email' => $this->email,
'token' => $currentAccessToken, 'token' => $currentAccessToken,
'allowedFileExtension' => [ 'allowedFileExtension' => [
'video' => FileHelper::convertToStrJsValidation(FileHelper::$allowedVideoExtensions), 'video' => FileHelper::convertToStrJsValidation(FileHelper::$allowedVideoExtensions),
'apk' => FileHelper::convertToStrJsValidation(FileHelper::$allowedVideoExtensions) 'apk' => FileHelper::convertToStrJsValidation(FileHelper::$allowedApkExtensions)
] ]
]; ];
} }

@ -8,6 +8,7 @@ use App\Helper\JSONResponse;
use App\Helper\Traits\Models\CanMultiOrderBy; use App\Helper\Traits\Models\CanMultiOrderBy;
use App\Helper\Traits\Models\CanMultiSearch; use App\Helper\Traits\Models\CanMultiSearch;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -23,12 +24,38 @@ class VideoUpdate extends Model {
protected $table = 'video_updates'; protected $table = 'video_updates';
protected $hidden = ['file']; protected $hidden = ['file'];
protected $appends = ['file_url']; protected $appends = ['file_url', 'file_size_mb'];
// ---------------------------------------------------------------------------------------
// -- RELATED TO SCOPE
public function scopeSelected(Builder $query){
return $query->where('is_selected', true);
}
// -- END RELATED TO SCOPE
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// -- RELATED TO GET DATA
public static function getLatestSelected(){ return self::selected()->latest('updated_at')->first(); }
// -- END RELATED TO GET DATA
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// -- RELATED TO ATTRIBUTE
protected function fileSizeMb(): Attribute {
return Attribute::make(
fn() => $this->file_size_kb != null ? round($this->file_size_kb / 1024, 2) : null
);
}
// -- END RELATED TO ATTRIBUTE
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// -- RELATED TO MODIFICATION DATA FROM REQUEST
public static function upsertFromRequest(Request $request) { public static function upsertFromRequest(Request $request) {
$request->validate([ $request->validate([
'id' => 'nullable|integer|exists:App\Models\VideoUpdate,id', 'id' => 'nullable|integer|exists:App\Models\VideoUpdate,id',
'is_selected' => 'nullable|in:true,false', 'is_selected' => 'nullable|boolean',
'file' => 'required_without:id|file|' . FileHelper::convertToStrLaraValidation(FileHelper::$allowedVideoExtensions), 'file' => 'required_without:id|file|' . FileHelper::convertToStrLaraValidation(FileHelper::$allowedVideoExtensions),
'file_name' => 'required|string', 'file_name' => 'required|string',
], [ ], [
@ -37,9 +64,13 @@ class VideoUpdate extends Model {
$delOldDbFileLocation = ''; $delOldDbFileLocation = '';
$newDbFileLocation = ''; $newDbFileLocation = '';
$newFileSizeKb = 0;
try { try {
// save photo // save video to storage & get file size
if($request->file) $newDbFileLocation = self::saveFile($request->file)['db_url']; if($request->file) {
$newDbFileLocation = self::saveFile($request->file)['db_url'];
$newFileSizeKb = round($request->file('file')->getSize() / 1024, 2);
}
// try to upsert data // try to upsert data
DB::beginTransaction(); DB::beginTransaction();
@ -51,11 +82,16 @@ class VideoUpdate extends Model {
if($newDbFileLocation) { if($newDbFileLocation) {
if($videoUpdate->file) $delOldDbFileLocation = $videoUpdate->file; if($videoUpdate->file) $delOldDbFileLocation = $videoUpdate->file;
$videoUpdate->file = $newDbFileLocation; $videoUpdate->file = $newDbFileLocation;
$videoUpdate->file_size_kb = $newFileSizeKb;
} }
$videoUpdate->file_name = $request->file_name; $videoUpdate->file_name = $request->file_name;
if($request->is_selected == 'true') { if($request->is_selected) {
VideoUpdate::where('is_selected', true)->update(['is_selected' => false]); VideoUpdate::where('is_selected', true)
->when($request->id, function(Builder $query, $id) {
$query->where('id', '!=', $id);
})
->update(['is_selected' => false]);
$videoUpdate->is_selected = true; $videoUpdate->is_selected = true;
} else $videoUpdate->is_selected = false; } else $videoUpdate->is_selected = false;
@ -116,8 +152,11 @@ class VideoUpdate extends Model {
throw $th; throw $th;
} }
} }
// -- END RELATED MODIFICATION DATA FROM REQUEST
// ---------------------------------------------------------------------------------------
// -- File UTILITIES // ---------------------------------------------------------------------------------------
// -- FILE UTILITIES
protected function fileUrl(): Attribute { protected function fileUrl(): Attribute {
return Attribute::make( return Attribute::make(
fn() => $this->file ? Storage::disk('s3')->url($this->file) : '' fn() => $this->file ? Storage::disk('s3')->url($this->file) : ''
@ -143,5 +182,6 @@ class VideoUpdate extends Model {
return "$name.$extension"; return "$name.$extension";
} }
public static function deleteFile($dbUrl) { Storage::disk('s3')->delete($dbUrl); } public static function deleteFile($dbUrl) { Storage::disk('s3')->delete($dbUrl); }
// -- END File UTILITIES // -- END FILE UTILITIES
// ---------------------------------------------------------------------------------------
} }

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -17,8 +18,15 @@ class AppServiceProvider extends ServiceProvider
/** /**
* Bootstrap any application services. * Bootstrap any application services.
*/ */
public function boot(): void public function boot(): void {
{ Http::macro('indokargo', function() {
// $headers = [
'oauth' => env('INDOKARGO_API_OAUTH', ''),
'session' => env('INDOKARGO_API_SESSION_ID', '')
];
return Http::withHeaders($headers)->timeout(120)
->baseUrl(env('INDOKARGO_API_URL', '').'/v1/');
});
} }
} }

@ -53,7 +53,7 @@ return [
'url' => env('AWS_URL'), 'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'), 'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false, 'throw' => true,
], ],
], ],

@ -0,0 +1,4 @@
Notes:
- Auto Update app
- Auto Update video
- Laravel Sanctum

@ -16,11 +16,11 @@ return new class extends Migration
$table->string('name'); $table->string('name');
$table->string('username'); $table->string('username');
$table->string('email')->unique(); $table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable(); $table->timestampTz('email_verified_at')->nullable();
$table->string('password'); $table->string('password');
$table->boolean('is_active'); $table->boolean('is_active');
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestampsTz();
}); });
} }

@ -14,7 +14,7 @@ return new class extends Migration
Schema::create('password_reset_tokens', function (Blueprint $table) { Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary(); $table->string('email')->primary();
$table->string('token'); $table->string('token');
$table->timestamp('created_at')->nullable(); $table->timestampTz('created_at')->nullable();
}); });
} }

@ -18,7 +18,7 @@ return new class extends Migration
$table->text('queue'); $table->text('queue');
$table->longText('payload'); $table->longText('payload');
$table->longText('exception'); $table->longText('exception');
$table->timestamp('failed_at')->useCurrent(); $table->timestampTz('failed_at')->useCurrent();
}); });
} }

@ -17,9 +17,9 @@ return new class extends Migration
$table->string('name'); $table->string('name');
$table->string('token', 64)->unique(); $table->string('token', 64)->unique();
$table->text('abilities')->nullable(); $table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable(); $table->timestampTz('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable(); $table->timestampTz('expires_at')->nullable();
$table->timestamps(); $table->timestampsTz();
}); });
} }

@ -14,7 +14,7 @@ return new class extends Migration {
$table->string('file_name', 100); $table->string('file_name', 100);
$table->string('file'); $table->string('file');
$table->boolean('is_selected'); $table->boolean('is_selected');
$table->timestamps(); $table->timestampsTz();
}); });
} }

@ -15,7 +15,8 @@ return new class extends Migration
$table->string('name', 100); $table->string('name', 100);
$table->string('file'); $table->string('file');
$table->integer('version_code'); $table->integer('version_code');
$table->timestamps(); $table->string('change_note')->nullable();
$table->timestampsTz();
}); });
} }

@ -0,0 +1,2 @@
Notes:
- TV information & Logs

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void {
Schema::create('outlets', function (Blueprint $table) {
$table->id();
$table->string('code', 50)->index();
$table->string('name', 255)->nullable()->index();
$table->string('address', 255)->nullable();
$table->string('street_address', 255)->nullable();
$table->string('phone', 50)->nullable()->index();
$table->double('lat')->default(0);
$table->double('long')->default(0);
$table->string('notes', 255)->nullable();
$table->string('country', 255);
$table->string('ik_country_id', 255);
$table->string('state', 255);
$table->string('ik_state_id', 255);
$table->string('region', 255);
$table->string('ik_region_id', 255);
$table->string('city', 255);
$table->string('ik_city_id', 255);
$table->string('zipcode', 255)->nullable();
$table->timestampTz('last_visited_at')->nullable();
$table->timestampsTz();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::drop('outlets');
}
};

@ -0,0 +1,109 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void {
Schema::create('tvs', function (Blueprint $table) {
$table->id();
$table->string('code', 50)->unique();
$table->string('ik_address_id', 255)->nullable();
$table->integer('apk_version_code')->nullable();
$table->string('apk_version_name')->nullable();
$table->string('company_name')->nullable();
$table->string('address')->nullable();
$table->string('street_address')->nullable();
$table->string('col1', 255)->nullable();
$table->string('col2', 255)->nullable();
$table->string('col3', 255)->nullable();
$table->string('col4', 255)->nullable();
$table->string('col5', 255)->nullable();
$table->string('col6', 255)->nullable();
$table->string('col7', 255)->nullable();
$table->string('col8', 255)->nullable();
$table->string('col9', 255)->nullable();
$table->string('col10', 255)->nullable();
$table->string('notes', 255)->nullable();
$table->boolean('is_active')->default(true);
$table->jsonb('device_info')->nullable();
$table->timestampTz('installed_at');
$table->timestampTz('last_connected_at')->nullable();
$table->timestampsTz();
});
Schema::create('new_tv_requests', function (Blueprint $table) {
$table->id();
$table->string('code', 50);
$table->jsonb('device_info')->nullable();
$table->foreignId('tv_fk')->nullable();
$table->timestampTz('approved_at')->nullable();
$table->timestampTz('responded_at')->nullable();
$table->timestampsTz();
});
Schema::create('tv_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('tv_fk');
$table->string('type');
$table->json('from')->nullable();
$table->json('to')->nullable();
$table->foreignId('user_fk')->nullable();
$table->timestampsTz();
$table->foreign('tv_fk')->references('id')->on('tvs')->cascadeOnDelete();
$table->foreign('user_fk')->references('id')->on('users');
});
// Schema::create('tv_sessions', function (Blueprint $table) {
// $table->id();
// $table->foreignId('tv_fk')->index();
// $table->timestampTz('started_at')->index();
// $table->timestampTz('finished_at')->nullable()->index();
// $table->boolean('is_playing_video');
// $table->jsonb('current_videos')->nullable();
// $table->timestampsTz();
// $table->foreign('tv_fk')->references('id')->on('tvs');
// });
// Schema::create('tv_app_infos', function (Blueprint $table) {
// $table->foreignId('tv_fk')->unique()->index();
// $table->timestampTz('installed_at')->nullable()->index();
// $table->jsonb('current_videos')->nullable();
// $table->timestampTz('last_updated_video')->nullable()->index();
// $table->timestampsTz();
// $table->foreign('tv_fk')->references('id')->on('tvs');
// });
// Schema::create('tv_app_logs', function (Blueprint $table) {
// $table->id();
// $table->foreignId('tv_fk')->index();
// $table->string('requested_to');
// $table->string('ip', 50);
// $table->jsonb('request_data')->nullable();
// $table->enum('result', ['success', 'failed']);
// $table->jsonb('response_data')->nullable();
// $table->timestampsTz();
// $table->foreign('tv_fk')->references('id')->on('tvs');
// });
}
/**
* Reverse the migrations.
*/
public function down(): void {
// Schema::drop('tv_app_logs');
// Schema::drop('tv_app_infos');
// Schema::drop('tv_sessions');
Schema::drop('tv_logs');
Schema::drop('new_tv_requests');
Schema::drop('tvs');
}
};

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void {
Schema::create('sts_logs', function (Blueprint $table) {
$table->id();
$table->string('partner', 255)->nullable();
$table->boolean('is_outgoing');
$table->string('module', 255)->nullable();
$table->string('service_name', 255)->nullable();
$table->string('local_id', 255)->nullable();
$table->string('partner_id', 255)->nullable();
$table->enum('result', ['success', 'failed', 'warning']);
$table->jsonb('error_info')->nullable();
$table->tinyInteger('seq');
$table->jsonb('request_data')->nullable();
$table->timestampTz('request_time')->nullable();
$table->jsonb('response_data')->nullable();
$table->timestampTz('response_time')->nullable();
$table->boolean('is_retry')->default('false');
$table->timestampsTz();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::drop('sts_logs');
}
};

@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('video_updates', function(Blueprint $table) {
$table->double('file_size_kb')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('video_updates', function(Blueprint $table) {
$table->dropColumn('file_size_kb');
});
}
};

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('apk_updates', function (Blueprint $table) {
$table->string('version_name', 50)->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('apk_updates', function (Blueprint $table) {
$table->dropColumn('version_name');
});
}
};

@ -17,9 +17,9 @@ class DatabaseSeeder extends Seeder
\App\Models\User::factory()->create([ \App\Models\User::factory()->create([
'name' => 'Test ', 'name' => 'Test ',
'username' => 'username', 'username' => 'superadmin',
'email' => 'admin@mail.com', 'email' => 'superadmin@mail.com',
'password' => Crypt::encrypt('superadmin') 'password' => Crypt::encrypt('socrates')
]); ]);
} }
} }

@ -1,8 +1,11 @@
<?php <?php
use App\Http\Controllers\api\AuthController; use App\Http\Controllers\api\AuthController;
use App\Http\Controllers\api\superadmin\ApkUploadController;
use App\Http\Controllers\api\superadmin\UserManagementController;
use App\Http\Controllers\api\superadmin\VideoUploadController; use App\Http\Controllers\api\superadmin\VideoUploadController;
use App\Http\Middleware\Cors; use App\Http\Middleware\Cors;
use App\Http\Middleware\MobileMiddleware;
use App\Http\Middleware\UserAuthMiddleware; use App\Http\Middleware\UserAuthMiddleware;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -27,13 +30,10 @@ Route::controller(AuthController::class )->group(function() {
}); });
}); });
Route::middleware(USER_MIDDLEWARES)->prefix('superadmin')->group(function() { Route::middleware(USER_MIDDLEWARES)->group(function() {
Route::controller(VideoUploadController::class)->group(function() { Route::prefix('/superadmin')->group(base_path('routes/api/superadmin.php'));
Route::post('/video-upload', 'init'); });
Route::post('/video-upload/save', 'save');
Route::post('/video-upload/update', 'update'); Route::middleware(MobileMiddleware::class)->group(function() {
Route::post('/video-upload/delete', 'delete'); Route::prefix('/mobile')->group(base_path('routes/api/mobile.php'));
Route::post('/video-upload/change-selected-video', 'changeSelectedVideo');
});
}); });
// tmux session, tmux attach session -t

@ -0,0 +1,16 @@
<?php
use App\Http\Controllers\api\mobile\CheckUpdateController;
use App\Http\Controllers\api\mobile\TvController;
use Illuminate\Support\Facades\Route;
Route::controller(CheckUpdateController::class)->group(function() {
Route::post('/check-update/video-update/latest', 'videoUpdateLatest');
Route::post('/check-update/apk-update/latest', 'apkUpdateLatest');
});
Route::controller(TvController::class)->group(function() {
Route::post('/tv/new-request', 'newRequest');
Route::post('/tv/check-update', 'checkUpdate');
});
?>

@ -0,0 +1,52 @@
<?php
use App\Http\Controllers\api\superadmin\ApkUploadController;
use App\Http\Controllers\api\superadmin\GeneralController;
use App\Http\Controllers\api\superadmin\tv\NewTvRequestController;
use App\Http\Controllers\api\superadmin\tv\TvController;
use App\Http\Controllers\api\superadmin\UserManagementController;
use App\Http\Controllers\api\superadmin\VideoUploadController;
use Illuminate\Support\Facades\Route;
Route::controller(GeneralController::class)->group(function() {
Route::post('/general/tv/search', 'tvSearch');
});
Route::controller(VideoUploadController::class)->group(function() {
Route::post('/video-upload', 'init');
Route::post('/video-upload/save', 'save');
Route::post('/video-upload/update', 'update');
Route::post('/video-upload/delete', 'delete');
Route::post('/video-upload/change-selected-video', 'changeSelectedVideo');
});
Route::controller(ApkUploadController::class)->group(function() {
Route::post('/apk-upload', 'init');
Route::post('/apk-upload/save', 'save');
Route::post('/apk-upload/update', 'update');
Route::post('/apk-upload/delete', 'delete');
});
Route::controller(UserManagementController::class)->group(function() {
Route::post('/user-management', 'init');
Route::post('/user-management/save', 'save');
Route::post('/user-management/update', 'update');
Route::post('/user-management/change-password', 'changePassword');
Route::post('/user-management/change-status', 'changeStatus');
Route::post('/user-management/delete', 'delete');
});
Route::controller(TvController::class)->group(function() {
Route::post('/tv/tv', 'init');
Route::post('/tv/tv/update', 'update');
Route::post('/tv/tv/change-status', 'changeStatus');
Route::post('/tv/tv/excel', 'excel');
});
Route::controller(NewTvRequestController::class)->group(function() {
Route::post('/tv/new-tv-request', 'init');
Route::post('/tv/new-tv-request/approve', 'approve');
Route::post('/tv/new-tv-request/reject', 'reject');
Route::post('/tv/new-tv-request/tv/search', 'tvSearch');
});
?>
Loading…
Cancel
Save