diff --git a/.env.example b/.env.example index 4885627..5d6a826 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/app/Console/Commands/StsReschedule.php b/app/Console/Commands/StsReschedule.php new file mode 100644 index 0000000..54fed10 --- /dev/null +++ b/app/Console/Commands/StsReschedule.php @@ -0,0 +1,86 @@ +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); + } + } +} diff --git a/app/Helper/Common.php b/app/Helper/Common.php index 89c4f6c..f1c18e8 100644 --- a/app/Helper/Common.php +++ b/app/Helper/Common.php @@ -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'"); + } } \ No newline at end of file diff --git a/app/Helper/DatabaseHelper.php b/app/Helper/DatabaseHelper.php index 9c2d82e..7145de8 100644 --- a/app/Helper/DatabaseHelper.php +++ b/app/Helper/DatabaseHelper.php @@ -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; + } } ?> \ No newline at end of file diff --git a/app/Helper/Frontend/ApiUtilities.php b/app/Helper/Frontend/ApiUtilities.php new file mode 100644 index 0000000..9055dc3 --- /dev/null +++ b/app/Helper/Frontend/ApiUtilities.php @@ -0,0 +1,51 @@ +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'); + } +} +?> \ No newline at end of file diff --git a/app/Helper/STS/Indokargo.php b/app/Helper/STS/Indokargo.php new file mode 100644 index 0000000..563e95f --- /dev/null +++ b/app/Helper/STS/Indokargo.php @@ -0,0 +1,180 @@ +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']); + }); + } +} + +?> \ No newline at end of file diff --git a/app/Http/Controllers/api/mobile/CheckUpdateController.php b/app/Http/Controllers/api/mobile/CheckUpdateController.php index 73e7c42..e040116 100644 --- a/app/Http/Controllers/api/mobile/CheckUpdateController.php +++ b/app/Http/Controllers/api/mobile/CheckUpdateController.php @@ -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]); } } diff --git a/app/Http/Controllers/api/mobile/TvController.php b/app/Http/Controllers/api/mobile/TvController.php new file mode 100644 index 0000000..093fde4 --- /dev/null +++ b/app/Http/Controllers/api/mobile/TvController.php @@ -0,0 +1,38 @@ +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]); + } +} diff --git a/app/Http/Controllers/api/superadmin/GeneralController.php b/app/Http/Controllers/api/superadmin/GeneralController.php new file mode 100644 index 0000000..24bfd28 --- /dev/null +++ b/app/Http/Controllers/api/superadmin/GeneralController.php @@ -0,0 +1,16 @@ +validate(['search' => 'nullable|string']); + $tvs = Tv::multiSearch($request->search, ['code'])->orderBy('code', 'asc')->limit(10)->get(); + return JSONResponse::Success(['tvs' => $tvs ]); + } +} diff --git a/app/Http/Controllers/api/superadmin/tv/NewTvRequestController.php b/app/Http/Controllers/api/superadmin/tv/NewTvRequestController.php new file mode 100644 index 0000000..0052c55 --- /dev/null +++ b/app/Http/Controllers/api/superadmin/tv/NewTvRequestController.php @@ -0,0 +1,32 @@ +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); } +} diff --git a/app/Http/Controllers/api/superadmin/tv/TvController.php b/app/Http/Controllers/api/superadmin/tv/TvController.php new file mode 100644 index 0000000..5ddb537 --- /dev/null +++ b/app/Http/Controllers/api/superadmin/tv/TvController.php @@ -0,0 +1,33 @@ +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); } +} diff --git a/app/Http/Middleware/MobileMiddleware.php b/app/Http/Middleware/MobileMiddleware.php index faddb3b..6ef57ff 100644 --- a/app/Http/Middleware/MobileMiddleware.php +++ b/app/Http/Middleware/MobileMiddleware.php @@ -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); } } diff --git a/app/Models/ApkUpdate.php b/app/Models/ApkUpdate.php index da582b0..05cdf84 100644 --- a/app/Models/ApkUpdate.php +++ b/app/Models/ApkUpdate.php @@ -34,6 +34,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 +47,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|unique:apk_updates,version_code', + 'version_name' => 'required|string', 'change_note' => 'nullable|string' ], [ 'file' => ['required_without' => 'The file field is required.'] @@ -66,6 +73,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(); diff --git a/app/Models/KIV_TvAppInfo.php b/app/Models/KIV_TvAppInfo.php new file mode 100644 index 0000000..c832606 --- /dev/null +++ b/app/Models/KIV_TvAppInfo.php @@ -0,0 +1,22 @@ +belongsTo(Tv::class, 'tv_fk', 'id'); } + // -- END RELATED TO RELATIONSHIP + //------------------------------------------------------------ +} diff --git a/app/Models/KIV_TvConnectLog.php b/app/Models/KIV_TvConnectLog.php new file mode 100644 index 0000000..faede24 --- /dev/null +++ b/app/Models/KIV_TvConnectLog.php @@ -0,0 +1,18 @@ +belongsTo(Tv::class, 'tv_fk', 'id'); } + // -- END RELATED TO RELATIONSHIP + //------------------------------------------------------------ +} diff --git a/app/Models/KIV_TvSession.php b/app/Models/KIV_TvSession.php new file mode 100644 index 0000000..d5d0741 --- /dev/null +++ b/app/Models/KIV_TvSession.php @@ -0,0 +1,18 @@ +belongsTo(Tv::class, 'tv_fk', 'id'); } + // -- END RELATED TO RELATIONSHIP + //------------------------------------------------------------ +} diff --git a/app/Models/NewTvRequest.php b/app/Models/NewTvRequest.php new file mode 100644 index 0000000..775338a --- /dev/null +++ b/app/Models/NewTvRequest.php @@ -0,0 +1,225 @@ +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 + //------------------------------------------------------------ +} diff --git a/app/Models/Outlet.php b/app/Models/Outlet.php new file mode 100644 index 0000000..4349332 --- /dev/null +++ b/app/Models/Outlet.php @@ -0,0 +1,20 @@ +hasOne(Tv::class, 'outlet_fk', 'id'); } + // -- END RELATED TO MIGRATION2 + //------------------------------------------------------------ +} diff --git a/app/Models/StsLog.php b/app/Models/StsLog.php new file mode 100644 index 0000000..26ef4e4 --- /dev/null +++ b/app/Models/StsLog.php @@ -0,0 +1,49 @@ + 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'; +} diff --git a/app/Models/Tv.php b/app/Models/Tv.php new file mode 100644 index 0000000..51231c0 --- /dev/null +++ b/app/Models/Tv.php @@ -0,0 +1,372 @@ +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 + //------------------------------------------------------------ +} diff --git a/app/Models/TvLog.php b/app/Models/TvLog.php new file mode 100644 index 0000000..2af7207 --- /dev/null +++ b/app/Models/TvLog.php @@ -0,0 +1,39 @@ +'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(); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index cdb2f7e..52e4a00 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -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 { diff --git a/app/Models/VideoUpdate.php b/app/Models/VideoUpdate.php index 30b480c..bf8eb74 100644 --- a/app/Models/VideoUpdate.php +++ b/app/Models/VideoUpdate.php @@ -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,6 +82,7 @@ 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; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..0f630e2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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/'); + }); } } diff --git a/database/migrations/2014_10_11_000000_V1.0.1_MARKER b/database/migrations/2014_10_11_000000_V1.0.1_MARKER new file mode 100644 index 0000000..c9e720f --- /dev/null +++ b/database/migrations/2014_10_11_000000_V1.0.1_MARKER @@ -0,0 +1,4 @@ +Notes: +- Auto Update app +- Auto Update video +- Laravel Sanctum \ No newline at end of file diff --git a/database/migrations/2024_05_15_000000_V2.0.0_MARKER b/database/migrations/2024_05_15_000000_V2.0.0_MARKER new file mode 100644 index 0000000..f2532ed --- /dev/null +++ b/database/migrations/2024_05_15_000000_V2.0.0_MARKER @@ -0,0 +1,2 @@ +Notes: +- TV information & Logs \ No newline at end of file diff --git a/database/migrations/2024_05_18_033101_outlet b/database/migrations/2024_05_18_033101_outlet new file mode 100644 index 0000000..923f94e --- /dev/null +++ b/database/migrations/2024_05_18_033101_outlet @@ -0,0 +1,43 @@ +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'); + } +}; diff --git a/database/migrations/2024_05_18_033105_tv.php b/database/migrations/2024_05_18_033105_tv.php new file mode 100644 index 0000000..c3aedad --- /dev/null +++ b/database/migrations/2024_05_18_033105_tv.php @@ -0,0 +1,109 @@ +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'); + } +}; diff --git a/database/migrations/2024_05_18_033128_sts_log.php b/database/migrations/2024_05_18_033128_sts_log.php new file mode 100644 index 0000000..8677641 --- /dev/null +++ b/database/migrations/2024_05_18_033128_sts_log.php @@ -0,0 +1,39 @@ +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'); + } +}; diff --git a/database/migrations/2024_06_11_095119_add_col_total_size_kb_to_video_update.php b/database/migrations/2024_06_11_095119_add_col_total_size_kb_to_video_update.php new file mode 100644 index 0000000..4674d5c --- /dev/null +++ b/database/migrations/2024_06_11_095119_add_col_total_size_kb_to_video_update.php @@ -0,0 +1,26 @@ +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'); + }); + } +}; diff --git a/database/migrations/2024_06_18_152619_add_col_version_name_to_apk_update.php b/database/migrations/2024_06_18_152619_add_col_version_name_to_apk_update.php new file mode 100644 index 0000000..237eea8 --- /dev/null +++ b/database/migrations/2024_06_18_152619_add_col_version_name_to_apk_update.php @@ -0,0 +1,28 @@ +string('version_name', 50)->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('apk_updates', function (Blueprint $table) { + $table->dropColumn('version_name'); + }); + } +}; diff --git a/routes/api/mobile.php b/routes/api/mobile.php index 0a5c364..20a37c7 100644 --- a/routes/api/mobile.php +++ b/routes/api/mobile.php @@ -1,10 +1,16 @@ 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'); +}); ?> \ No newline at end of file diff --git a/routes/api/superadmin.php b/routes/api/superadmin.php index 4f74481..0dd9d48 100644 --- a/routes/api/superadmin.php +++ b/routes/api/superadmin.php @@ -1,10 +1,17 @@ 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'); +}); ?> \ No newline at end of file