Compare commits

...

36 Commits
mobile ... main

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 1 year ago
ricky rx 9ae7e9f7b3 feat: add version_code & name on tv 1 year ago
ricky rx 363f11136e chore: remove indokargo resync data feature 1 year 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

@ -3,11 +3,16 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
MOBILE_TOKEN=Gaobd5OKPdGARLGTD03vSFStrADAxmQ9
MOBILE_AUTH_TOKEN=Gaobd5OKPdGARLGTD03vSFStrADAxmQ9
# for CORS
SANCTUM_STATEFUL_DOMAINS=[http://localhost:3000]
# for Indokargo
IK_BE_DOMAIN=
IK_SESSION_ID=
IK_OAUTH_ID=
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
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>
<p align="center">
<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>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
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:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
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.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
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).
### Note: if you forgot how to open readme from vs code:
- Ctrl K + V (Side bar readme)
- Crtl Shift V (Full size read me)
# Laravel Tivi
## Instalation
- open /etc/php/8.2/fpm/php.ini
- upload_max_filesize = 100M
- post_max_size = 100M
- Pull Project on server
- Setting .env from .env.example
- note: fill SANCTUM_LARAVEL_DOMAINS with frontend url
- example: SANCTUM_STATEFUL_DOMAINS=[http://tv.bintangtechnology.com,https://tv.bintangtechnology.com,]
- run command
- sudo chmod -R 777 storage -> laravel storage log permission
- php artisan storage:link -> if u use public storage in laravel, not s3
- composer update {or} /usr/bin/php8.2 /usr/local/bin/composer update
- php artisan migrate --seed {or} php8.2 artisan migrate --seed
- php artisan key:generate {or} php artisan key:generate
- note:
- usually, cors problem cause by chmod on /storage or upload_max_filesize / post_max_size (only happen when u debug with firefox)
## Update
- open project loaction (ex: /var/www/api_tivi)
- run command
- git pull --all
- composer install
- php artisan migrate (if it has database changes)

@ -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;
use Illuminate\Support\Facades\DB;
class Common {
public static function convertRequestConfig(?array $requestConfig): array {
$config = [];
@ -26,4 +28,9 @@ class Common {
public static function trueOrFalse(mixed $value): bool {
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 compileDirtyEloquentToArrMessage($model) {
$dirties = $model->getDirty();
foreach($dirties as $key => $dirty) {
$dirties[$key] = $model->getOriginal($key) . ' => ' . $dirty;
}
return $dirties;
}
}
?>

@ -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']);
});
}
}
?>

@ -10,11 +10,11 @@ use Illuminate\Http\Request;
class CheckUpdateController extends Controller {
public function videoUpdateLatest(Request $request) {
$lastestVideoUpdate = VideoUpdate::selected()->latest('updated_at')->first();
$lastestVideoUpdate = VideoUpdate::getLatestSelected();
return JSONResponse::Success(['latestVideoUpload' => $lastestVideoUpdate]);
}
public function apkUpdateLatest(Request $request) {
$latestApkUpdate = ApkUpdate::latest('version_code')->first();
$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,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,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); }
}

@ -14,11 +14,12 @@ class MobileMiddleware
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response {
$serverMobileToken = env('MOBILE_TOKEN', '');
if(!$serverMobileToken) throw new \Exception('Mobile token in server is not found');
$serverMobileAuthToken = env('MOBILE_AUTH_TOKEN', '');
if(!$serverMobileAuthToken) throw new \Exception('Mobile Auth token in server is not found');
$clientMobileToken = $request->header('mobile-token', '');
if($serverMobileToken != $clientMobileToken) throw new \Exception('Invalid Mobile Token');
$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);
}
}

@ -16,6 +16,7 @@ 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 {
use HasFactory;
@ -34,6 +35,12 @@ class ApkUpdate extends Model {
// -- 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) {
@ -41,7 +48,8 @@ class ApkUpdate extends Model {
'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',
'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.']
@ -66,6 +74,7 @@ class ApkUpdate extends Model {
}
$apkUpdate->name = $request->name;
$apkUpdate->version_code = $request->version_code;
$apkUpdate->version_name = $request->version_name;
$apkUpdate->change_note = $request->change_note;
$apkUpdate->save();

@ -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();
}
}

@ -63,10 +63,10 @@ class User extends Authenticatable
'username' => 'required|string',
'email' => 'required|email',
'password' => 'required_without:id|string|min:8',
'is_active' => 'required_with:id|in:true,false',
'is_active' => 'required_with:id|boolean',
], [
'password' => ['required_with' => 'The password field is required.'],
'is_active' => ['required_with' => 'The is active field is required.']
'password.required_without' => 'The password field is required.',
'is_active.required_with' => 'The is active field is required.'
]);
try {

@ -24,7 +24,7 @@ class VideoUpdate extends Model {
protected $table = 'video_updates';
protected $hidden = ['file'];
protected $appends = ['file_url'];
protected $appends = ['file_url', 'file_size_mb'];
// ---------------------------------------------------------------------------------------
// -- RELATED TO SCOPE
@ -34,12 +34,28 @@ class VideoUpdate extends Model {
// -- 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) {
$request->validate([
'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_name' => 'required|string',
], [
@ -48,9 +64,13 @@ class VideoUpdate extends Model {
$delOldDbFileLocation = '';
$newDbFileLocation = '';
$newFileSizeKb = 0;
try {
// save photo
if($request->file) $newDbFileLocation = self::saveFile($request->file)['db_url'];
// save video to storage & get file size
if($request->file) {
$newDbFileLocation = self::saveFile($request->file)['db_url'];
$newFileSizeKb = round($request->file('file')->getSize() / 1024, 2);
}
// try to upsert data
DB::beginTransaction();
@ -62,10 +82,11 @@ class VideoUpdate extends Model {
if($newDbFileLocation) {
if($videoUpdate->file) $delOldDbFileLocation = $videoUpdate->file;
$videoUpdate->file = $newDbFileLocation;
$videoUpdate->file_size_kb = $newFileSizeKb;
}
$videoUpdate->file_name = $request->file_name;
if($request->is_selected == 'true') {
if($request->is_selected) {
VideoUpdate::where('is_selected', true)
->when($request->id, function(Builder $query, $id) {
$query->where('id', '!=', $id);

@ -2,6 +2,7 @@
namespace App\Providers;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -17,8 +18,15 @@ class AppServiceProvider extends ServiceProvider
/**
* 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'),
'endpoint' => env('AWS_ENDPOINT'),
'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('username');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->timestampTz('email_verified_at')->nullable();
$table->string('password');
$table->boolean('is_active');
$table->rememberToken();
$table->timestamps();
$table->timestampsTz();
});
}

@ -14,7 +14,7 @@ return new class extends Migration
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$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->longText('payload');
$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('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
$table->timestampTz('last_used_at')->nullable();
$table->timestampTz('expires_at')->nullable();
$table->timestampsTz();
});
}

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

@ -16,7 +16,7 @@ return new class extends Migration
$table->string('file');
$table->integer('version_code');
$table->string('change_note')->nullable();
$table->timestamps();
$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([
'name' => 'Test ',
'username' => 'username',
'email' => 'admin@mail.com',
'password' => Crypt::encrypt('superadmin')
'username' => 'superadmin',
'email' => 'superadmin@mail.com',
'password' => Crypt::encrypt('socrates')
]);
}
}

@ -1,10 +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');
});
?>

@ -1,10 +1,17 @@
<?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');
@ -29,4 +36,17 @@ Route::controller(UserManagementController::class)->group(function() {
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