Compare commits

..

2 Commits

@ -8,6 +8,11 @@ MOBILE_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

@ -0,0 +1,112 @@
<?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)" );
}
}
//-- 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 = false;
$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();
try {
$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']);
} 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;
}
}
}
?>

@ -0,0 +1,31 @@
<?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::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); }
}

@ -2,11 +2,182 @@
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('activated_at');
}]);
}
public function scopeAddColumnCanReject(Builder $query) {
$query->withCount(['self as can_reject' => function($q) {
$q->whereNull('activated_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;
}
// -- END RELATED TO DATA FUNCTION
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO FROM REQUEST FUNCTION
private static function _checkOrCreateResponse(NewTvRequest $newTvReq) {
if($newTvReq->is_activated) {
// make sure waiting tv_device responded before deleted
if(empty($newTvReq->responded_at)) {
$newTvReq->responded_at = now();
$newTvReq->save();
}
return JSONResponse::Success([
'message' => 'activated',
'is_active' => true,
'new_tv_request' => $newTvReq,
'tv' => $newTvReq->tv
]);
}
return JSONResponse::Success([
'message' => 'not activate',
'is_active' => false,
'new_tv_request' => $newTvReq,
'tv' => null
]);
}
public static function approveFromRequest(Request $request) {
$request->validate([
'id' => 'required|integer|exists:App\Models\NewTvRequest',
'tv' => 'required|array',
]);
$tvRequest = new Request($request->tv);
if($tvRequest->filled('code')) $tvRequest->code = strtoupper($tvRequest->code);
$tvRequest->validate([
'code' => 'required|string|unique:App\Models\Tv,code',
'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',
]);
$newTvReq = NewTvRequest::addColumnCanApprove()
->findOrFail($request->id);
if(!$newTvReq->can_approve) throw new \Exception('Cannot approve current request');
try {
DB::beginTransaction();
$tv = new Tv();
$tv->code = $tvRequest->code;
$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->save();
$newTvReq->activated_at = now();
$newTvReq->tv_fk = $tv->id;
$newTvReq->save();
// try to sys_to_sys with indokargo
$jsonResponse = Indokargo::createTVAddress($tvRequest, $tv->id);
DB::commit();
return $jsonResponse;
} 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 ]);
}
/**
* Rule:
* 1. if it has not been activated for more than the expiry date, deleted it
* 2. if it has been activated & has been responded more than expiry date, delete it
* - case: when has been activated, but intenet connection missing, how can device know
* that the new request tv has been activated?
*/
private static function _getMaxExpiredTime() :Carbon { return Carbon::now()->subHour(); }
public static function deleteExpiredRequests() {
$expiredTime = self::_getMaxExpiredTime()->toString();
NewTvRequest::where(function($q) use ($expiredTime) {
$q->whereNull('activated_at')->where('created_at', '<=', $expiredTime);
})->orWhere(function($q) use ($expiredTime) {
$q->where('responded_at', '<=', $expiredTime );
})->delete();
}
// -- END RELATED TO FROM REQUEST FUNCTION
//------------------------------------------------------------
}

@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -9,4 +10,38 @@ 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-addrass';
}

@ -4,44 +4,31 @@ namespace App\Models;
use App\Helper\JSONResponse;
use AWS\CRT\HTTP\Request;
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\Support\Str;
class Tv extends Model {
use HasFactory;
protected $table = 'tvs';
protected $hidden = ['ik_cust_id', 'ik_address_id'];
protected $casts = ['device_info'=>AsArrayObject::class];
//------------------------------------------------------------
// -- RELATED TO MIGRATION
// -- 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 MIGRATION
//------------------------------------------------------------
//------------------------------------------------------------
// -- RELATED TO DATA FUNCTION
public static function generateRandomCode(): string {
$totalDigit = 6;
$randomDigit = '';
while(true) {
$randomDigit = strtoupper(Str::random($totalDigit));
$isExist = Tv::where('code', $randomDigit)->first();
if(!$isExist) break;
}
return $randomDigit;
}
// -- END RELATED TO DATA FUNCTION
// -- END RELATED TO RELATIONSHIP
//------------------------------------------------------------
//------------------------------------------------------------

@ -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)
->baseUrl(env('INDOKARGO_API_URL', '').'/v1/');
});
}
}

@ -13,8 +13,7 @@ return new class extends Migration {
$table->id();
$table->foreignId('outlet_fk')->nullable();
$table->string('code', 50)->unique();
$table->string('ik_address_id', 255);
$table->string('notes', 255)->nullable();
$table->string('ik_address_id', 255)->nullable();
$table->string('col1', 255)->nullable();
$table->string('col2', 255)->nullable();
$table->string('col3', 255)->nullable();
@ -25,7 +24,9 @@ return new class extends Migration {
$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')->nullable();
$table->timestampTz('last_connected_at')->nullable();
$table->timestampsTz();
@ -35,6 +36,7 @@ return new class extends Migration {
$table->id();
$table->string('code', 50);
$table->jsonb('device_info')->nullable();
$table->foreignId('tv_fk')->nullable();
$table->timestampTz('activated_at')->nullable();
$table->timestampTz('responded_at')->nullable();
$table->timestampsTz();

@ -23,7 +23,9 @@ return new class extends Migration
$table->tinyInteger('seq');
$table->jsonb('request_data')->nullable();
$table->timestampTz('request_time')->nullable();
$table->boolean('is_retry');
$table->jsonb('response_data')->nullable();
$table->timestampTz('response_time')->nullable();
$table->boolean('is_retry')->default('false');
$table->timestampsTz();
});
}

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\api\superadmin\ApkUploadController;
use App\Http\Controllers\api\superadmin\tv\NewTvRequestController;
use App\Http\Controllers\api\superadmin\UserManagementController;
use App\Http\Controllers\api\superadmin\VideoUploadController;
use Illuminate\Support\Facades\Route;
@ -29,4 +30,9 @@ Route::controller(UserManagementController::class)->group(function() {
Route::post('/user-management/delete', 'delete');
});
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');
});
?>
Loading…
Cancel
Save