first commit
This commit is contained in:
commit
7590ed6f32
18
.editorconfig
Normal file
18
.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[docker-compose.yml]
|
||||||
|
indent_size = 4
|
||||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
*.blade.php diff=html
|
||||||
|
*.css diff=css
|
||||||
|
*.html diff=html
|
||||||
|
*.md diff=markdown
|
||||||
|
*.php diff=php
|
||||||
|
|
||||||
|
/.github export-ignore
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
|
.styleci.yml export-ignore
|
||||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/.phpunit.cache
|
||||||
|
/node_modules
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
.phpunit.result.cache
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
auth.json
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/.fleet
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
66
README.md
Normal file
66
README.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<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 over 2000 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 [Patreon page](https://patreon.com/taylorotwell).
|
||||||
|
|
||||||
|
### Premium Partners
|
||||||
|
|
||||||
|
- **[Vehikl](https://vehikl.com/)**
|
||||||
|
- **[Tighten Co.](https://tighten.co)**
|
||||||
|
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||||
|
- **[64 Robots](https://64robots.com)**
|
||||||
|
- **[Cubet Techno Labs](https://cubettech.com)**
|
||||||
|
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||||
|
- **[Many](https://www.many.co.uk)**
|
||||||
|
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
|
||||||
|
- **[DevSquad](https://devsquad.com)**
|
||||||
|
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||||
|
- **[OP.GG](https://op.gg)**
|
||||||
|
- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
|
||||||
|
- **[Lendio](https://lendio.com)**
|
||||||
|
|
||||||
|
## 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).
|
||||||
27
app/Console/Kernel.php
Normal file
27
app/Console/Kernel.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console;
|
||||||
|
|
||||||
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
class Kernel extends ConsoleKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the application's command schedule.
|
||||||
|
*/
|
||||||
|
protected function schedule(Schedule $schedule): void
|
||||||
|
{
|
||||||
|
// $schedule->command('inspire')->hourly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the commands for the application.
|
||||||
|
*/
|
||||||
|
protected function commands(): void
|
||||||
|
{
|
||||||
|
$this->load(__DIR__.'/Commands');
|
||||||
|
|
||||||
|
require base_path('routes/console.php');
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Exceptions/Handler.php
Normal file
30
app/Exceptions/Handler.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Handler extends ExceptionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The list of the inputs that are never flashed to the session on validation exceptions.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $dontFlash = [
|
||||||
|
'current_password',
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the exception handling callbacks for the application.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->reportable(function (Throwable $e) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
844
app/Http/Controllers/Android/AndroidController.php
Normal file
844
app/Http/Controllers/Android/AndroidController.php
Normal file
|
|
@ -0,0 +1,844 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Android;
|
||||||
|
|
||||||
|
use App\Models\Android\Topic;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Android\Task;
|
||||||
|
use App\Models\Android\Task_waiting;
|
||||||
|
use App\Models\Android\Enrollment;
|
||||||
|
use App\Models\Android\SubmitstestCase;
|
||||||
|
use App\Models\Android\SubmitsFinalSubmission;
|
||||||
|
use App\Models\Android\Submits;
|
||||||
|
use App\Models\Android\Testcase;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
class AndroidController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
- LECTURER
|
||||||
|
*/
|
||||||
|
public function lecturer_material(){
|
||||||
|
|
||||||
|
$dt_keseluruhan = array();
|
||||||
|
$topic = Topic::all();
|
||||||
|
|
||||||
|
$notifikasi = $this->notify_validator();
|
||||||
|
|
||||||
|
$total_mhs = 0;
|
||||||
|
|
||||||
|
if ( $topic->count() > 0 ) {
|
||||||
|
|
||||||
|
foreach ( $topic AS $isi ) {
|
||||||
|
|
||||||
|
// count data task
|
||||||
|
$isi->total = Task::where('android_topic_id', $isi->id)->count();
|
||||||
|
|
||||||
|
$total_mhs_bytopik = Enrollment::where('android_topic_id', $isi->id)->whereNotIn('status', ['cancel'])->get()->count();
|
||||||
|
$isi->enroll = $total_mhs_bytopik;
|
||||||
|
|
||||||
|
array_push( $dt_keseluruhan, $isi );
|
||||||
|
|
||||||
|
|
||||||
|
$total_mhs = $total_mhs + $total_mhs_bytopik;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return view('android.teacher.material', compact('dt_keseluruhan', 'total_mhs', 'notifikasi'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function lecturer_overview( $id ){
|
||||||
|
|
||||||
|
$topic = Topic::findOrFail( $id );
|
||||||
|
$task = Task::where('android_topic_id', $id)->get();
|
||||||
|
|
||||||
|
|
||||||
|
$dt_enrollment = array();
|
||||||
|
$enrollment = Enrollment::where('android_topic_id', $id)->get();
|
||||||
|
|
||||||
|
foreach ( $enrollment As $isi ) {
|
||||||
|
|
||||||
|
// informasi validation
|
||||||
|
$total_request = 0;
|
||||||
|
$total_validate = 0;
|
||||||
|
$user_id = $isi->user->id;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ambil informasi data testcase
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'android_topic_id' => $id
|
||||||
|
);
|
||||||
|
$submit = Submits::where($where)->get();
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - -
|
||||||
|
$NA = 0;
|
||||||
|
$total_submit = $submit->count();
|
||||||
|
|
||||||
|
// - - - - - - - -
|
||||||
|
|
||||||
|
if ( $submit->count() > 0 ) {
|
||||||
|
|
||||||
|
|
||||||
|
$total_NA = 0;
|
||||||
|
foreach ( $submit AS $isi_s ) {
|
||||||
|
|
||||||
|
// ambil data testcase
|
||||||
|
$SubmitstestCase = SubmitstestCase::where("android_submit_id", $isi_s->id)->get();
|
||||||
|
$isi->testcase = $SubmitstestCase;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// auto
|
||||||
|
$where_passed = [
|
||||||
|
|
||||||
|
'android_submit_id' => $isi_s->id,
|
||||||
|
'status_validate' => "passed"
|
||||||
|
];
|
||||||
|
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
|
||||||
|
|
||||||
|
// ambil informasi data task untuk memanggil bobot
|
||||||
|
$info_testcase = Testcase::where("task_id", $isi_s->android_task_id)->get();
|
||||||
|
|
||||||
|
$bobot = 0;
|
||||||
|
foreach ( $info_testcase AS $isiit ) {
|
||||||
|
|
||||||
|
$bobot += $isiit->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skor = 0;
|
||||||
|
foreach ( $test_passed AS $isitp ) {
|
||||||
|
|
||||||
|
$skor += $isitp->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nilai = 0;
|
||||||
|
if ( $skor > 0 && $bobot > 0 ) {
|
||||||
|
|
||||||
|
$nilai = $skor / $bobot * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_NA += $nilai;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// autograding
|
||||||
|
// $passed = 0;
|
||||||
|
// foreach ( $SubmitstestCase AS $det ) {
|
||||||
|
|
||||||
|
// if ( $det->status_validate == "passed" ) {
|
||||||
|
|
||||||
|
// $passed++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $total_NA += ($passed / $SubmitstestCase->count() * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo $total_submit;
|
||||||
|
$NA = $total_NA / $total_submit;
|
||||||
|
|
||||||
|
|
||||||
|
$total_request = $submit->count();
|
||||||
|
foreach ( $submit AS $det ) {
|
||||||
|
|
||||||
|
if ( $det->validator == "validated" ) $total_validate++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$progress = 0;
|
||||||
|
$total_task = Task::where("android_topic_id", $isi->android_topic_id)->count();
|
||||||
|
$status = $isi->status;
|
||||||
|
|
||||||
|
// check progress
|
||||||
|
if ( $status != "cancel") {
|
||||||
|
|
||||||
|
$task_waiting = Task_waiting::where( $where );
|
||||||
|
$total_task_waiting = $task_waiting->count();
|
||||||
|
|
||||||
|
if ( $total_task_waiting > 0 ) {
|
||||||
|
|
||||||
|
$info = Task::find( $task_waiting->first()->android_task_id );
|
||||||
|
$progress = $total_task_waiting / $total_task * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$isi->total_request = $total_request;
|
||||||
|
$isi->total_validate = $total_validate;
|
||||||
|
$isi->NA = $NA;
|
||||||
|
$isi->progress = $progress;
|
||||||
|
|
||||||
|
|
||||||
|
array_push( $dt_enrollment, $isi );
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'task' => $task,
|
||||||
|
'topic' => $topic,
|
||||||
|
'enrollment' => $dt_enrollment
|
||||||
|
);
|
||||||
|
// print_r( $Enrollment->user->name );
|
||||||
|
|
||||||
|
return view('android.teacher.overview', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function lecturer_waiting() {
|
||||||
|
|
||||||
|
// ambil data
|
||||||
|
$dt_keseluruhan = array();
|
||||||
|
$topic = Topic::all();
|
||||||
|
|
||||||
|
$notifikasi = $this->notify_validator();
|
||||||
|
|
||||||
|
$total_mhs = 0;
|
||||||
|
|
||||||
|
if ( $topic->count() > 0 ) {
|
||||||
|
|
||||||
|
foreach ( $topic AS $isi ) {
|
||||||
|
|
||||||
|
// count data task
|
||||||
|
$isi->total = Task::where('android_topic_id', $isi->id)->count();
|
||||||
|
|
||||||
|
$total_mhs_bytopik = Enrollment::where('android_topic_id', $isi->id)->whereNotIn('status', ['cancel'])->get()->count();
|
||||||
|
$isi->enroll = $total_mhs_bytopik;
|
||||||
|
|
||||||
|
array_push( $dt_keseluruhan, $isi );
|
||||||
|
|
||||||
|
|
||||||
|
$total_mhs = $total_mhs + $total_mhs_bytopik;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cek */
|
||||||
|
$submits = Submits::where('validator', 'process')->get();
|
||||||
|
$dt_need_validator = array();
|
||||||
|
|
||||||
|
if ( $submits->count() > 0 ) {
|
||||||
|
|
||||||
|
// group by
|
||||||
|
$dt_submit_topic = array();
|
||||||
|
foreach ( $submits AS $index => $isi ) {
|
||||||
|
|
||||||
|
if ( $index > 0 ) {
|
||||||
|
|
||||||
|
$find = in_array($isi->android_topic_id, array_column($dt_submit_topic, 'android_topic_id'));
|
||||||
|
if ( $find == false ) {
|
||||||
|
|
||||||
|
array_push( $dt_submit_topic, [
|
||||||
|
'android_topic_id' => $isi->android_topic_id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
array_push( $dt_submit_topic, [
|
||||||
|
'android_topic_id' => $isi->android_topic_id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// akumulasi mahasiswa
|
||||||
|
foreach ( $dt_submit_topic AS $isi ) {
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'android_topic_id' => $isi,
|
||||||
|
'validator' => "process"
|
||||||
|
);
|
||||||
|
$all_mhs = Submits::where($where)->get();
|
||||||
|
$jumlah = $all_mhs->count();
|
||||||
|
|
||||||
|
$dt_all_mahasiswa_by_topic = array();
|
||||||
|
|
||||||
|
|
||||||
|
$title = "";
|
||||||
|
if ( $jumlah > 0 ) {
|
||||||
|
|
||||||
|
foreach ( $all_mhs AS $mhs ) {
|
||||||
|
|
||||||
|
$title = $mhs->topic->title;
|
||||||
|
array_push( $dt_all_mahasiswa_by_topic, $mhs );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// push to validator
|
||||||
|
array_push( $dt_need_validator, [
|
||||||
|
|
||||||
|
'android_topic_id' => $isi,
|
||||||
|
'title' => $title,
|
||||||
|
'jumlah' => $jumlah,
|
||||||
|
'all_mhs' => $dt_all_mahasiswa_by_topic
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('android.teacher.validator_waiting', compact('notifikasi', 'total_mhs', 'dt_need_validator'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// detail task
|
||||||
|
public function lecturer_waiting_preview( $id_submit ) {
|
||||||
|
|
||||||
|
$submit = Submits::findOrFail( $id_submit );
|
||||||
|
// $testcase = SubmitstestCase::where("android_submit_id", $id_submit)->->get();
|
||||||
|
$testcase = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where("android_submit_id", $id_submit)->select("android_submits_testcase.*", "case", "score")->get();
|
||||||
|
|
||||||
|
// echo json_encode($testcase);
|
||||||
|
return view('android.teacher.validator_detail', compact('submit', 'testcase'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// do validate
|
||||||
|
public function lecturer_do_validate( $id_testcase, $android_submit_id ) {
|
||||||
|
|
||||||
|
|
||||||
|
// update all status waiting to failed (default)
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'android_submit_id' => $android_submit_id,
|
||||||
|
'status_validate' => "waiting"
|
||||||
|
);
|
||||||
|
SubmitstestCase::where($where)->update(['status_validate' => "failed"]);
|
||||||
|
|
||||||
|
$submittestcase = SubmitstestCase::findOrFail( $id_testcase );
|
||||||
|
$submit = Submits::findOrFail( $android_submit_id );
|
||||||
|
|
||||||
|
$submit->validator = "validated";
|
||||||
|
$submit->save();
|
||||||
|
|
||||||
|
|
||||||
|
if ( $submittestcase->status_validate == "failed" ) {
|
||||||
|
$submittestcase->status_validate = "passed";
|
||||||
|
} else if ( $submittestcase->status_validate == "passed" ) {
|
||||||
|
$submittestcase->status_validate = "failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
$submittestcase->save();
|
||||||
|
|
||||||
|
|
||||||
|
// echo $;
|
||||||
|
// return "oke";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function lecturer_load_point_testcase( $android_submit_id ) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$where = [
|
||||||
|
|
||||||
|
'android_submit_id' => $android_submit_id,
|
||||||
|
'status_validate' => "passed"
|
||||||
|
];
|
||||||
|
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where)->get();
|
||||||
|
|
||||||
|
|
||||||
|
// ambil data submit berdasarkan id
|
||||||
|
$submit = Submits::findOrFail( $android_submit_id );
|
||||||
|
$task = Task::findOrFail( $submit->android_task_id );
|
||||||
|
|
||||||
|
$info_testcase = Testcase::where("task_id", $task->id)->get();
|
||||||
|
|
||||||
|
$bobot = 0;
|
||||||
|
foreach ( $info_testcase AS $isi ) {
|
||||||
|
|
||||||
|
$bobot += $isi->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$skor = 0;
|
||||||
|
foreach ( $test_passed AS $isi ) {
|
||||||
|
|
||||||
|
$skor += $isi->score;
|
||||||
|
}
|
||||||
|
// hitung bobot per testcase
|
||||||
|
$nilai = 0;
|
||||||
|
|
||||||
|
if ( $skor > 0 && $bobot > 0 ) {
|
||||||
|
|
||||||
|
$nilai = $skor / $bobot * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(["point" => number_format($nilai, 2), "bobot" => $bobot, 'skor' => $test_passed->count()]);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// overview student
|
||||||
|
public function lecturer_overview_student( $topic_id, $user_id ) {
|
||||||
|
|
||||||
|
$enrollment = Enrollment::where('android_topic_id', $topic_id)->first();
|
||||||
|
$user = User::findOrFail( $user_id );
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
'user_id' => $user_id
|
||||||
|
);
|
||||||
|
$submit = Submits::where( $where )->get();
|
||||||
|
|
||||||
|
$dt_keseluruhan = array();
|
||||||
|
$NA = 0;
|
||||||
|
|
||||||
|
if ( $submit->count() > 0 ) {
|
||||||
|
|
||||||
|
$total_NA = 0;
|
||||||
|
$total_submit = $submit->count();
|
||||||
|
foreach ( $submit AS $isi ) {
|
||||||
|
|
||||||
|
|
||||||
|
// ambil data info nilai yang passed
|
||||||
|
$where_passed = [
|
||||||
|
|
||||||
|
'android_submit_id' => $isi->id,
|
||||||
|
'status_validate' => "passed"
|
||||||
|
];
|
||||||
|
// $test_passed = SubmitstestCase::where($where_passed)->get();
|
||||||
|
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
|
||||||
|
|
||||||
|
// ambil informasi data task untuk memanggil bobot
|
||||||
|
$info_testcase = Testcase::where("task_id", $isi->android_task_id)->get();
|
||||||
|
|
||||||
|
$bobot = 0;
|
||||||
|
// $dt = [];
|
||||||
|
foreach ( $info_testcase AS $isiit ) {
|
||||||
|
|
||||||
|
$bobot += $isiit->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skor = 0;
|
||||||
|
foreach ( $test_passed AS $isitp ) {
|
||||||
|
|
||||||
|
$skor += $isitp->score;
|
||||||
|
}
|
||||||
|
// hitung bobot per testcase
|
||||||
|
$nilai = 0;
|
||||||
|
if ( $skor > 0 && $bobot > 0 ) {
|
||||||
|
|
||||||
|
$nilai = $skor / $bobot * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ambil data testcase
|
||||||
|
$isi->testcase = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where('android_submit_id', $isi->id)->get();
|
||||||
|
$isi->nilai = $nilai;
|
||||||
|
$isi->bobot = $bobot;
|
||||||
|
|
||||||
|
// echo $nilai.' = '.($nilai / $bobot * 100);
|
||||||
|
// echo "<br>";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// autograding
|
||||||
|
$total_NA += $nilai;
|
||||||
|
// echo $nilai;
|
||||||
|
// echo "<hr>";
|
||||||
|
|
||||||
|
array_push($dt_keseluruhan, $isi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo $total_NA;
|
||||||
|
// echo " / ";
|
||||||
|
// echo $total_submit;
|
||||||
|
// echo " = ";
|
||||||
|
$NA = $total_NA / $total_submit;
|
||||||
|
// echo $total_NA;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// submission
|
||||||
|
$submission = SubmitsFinalSubmission::where( $where );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// echo $NA;
|
||||||
|
return view('android.teacher.overview_student', compact('enrollment', 'dt_keseluruhan', 'NA', 'user', 'submission'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function lecturer_confirm_student( $topic_id, $user_id, $enroll_id ){
|
||||||
|
|
||||||
|
$enrollment = Enrollment::findOrFail( $enroll_id );
|
||||||
|
|
||||||
|
$enrollment->status = "complete";
|
||||||
|
$enrollment->save();
|
||||||
|
|
||||||
|
return redirect("teacher/android23/overview-student/$topic_id/$user_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// notifikasi validator
|
||||||
|
public function notify_validator() {
|
||||||
|
|
||||||
|
$dt_notify = array();
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
// get submit topic
|
||||||
|
$submitTopic = Submits::where('validator', 'process')->groupBy('android_topic_id')->get();
|
||||||
|
|
||||||
|
|
||||||
|
foreach ( $submitTopic AS $isi ) {
|
||||||
|
|
||||||
|
$topic_id = $isi->android_topic_id;
|
||||||
|
$hitung = Submits::where('android_topic_id', $topic_id)->where('validator', 'process')->get()->count();
|
||||||
|
|
||||||
|
$total += $hitung;
|
||||||
|
|
||||||
|
$isi->waiting = $hitung;
|
||||||
|
array_push( $dt_notify, $isi );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'total' => $total,
|
||||||
|
'notify'=> $dt_notify
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
public function index() {
|
||||||
|
|
||||||
|
|
||||||
|
$dt_keseluruhan = array();
|
||||||
|
$topic = Topic::all();
|
||||||
|
|
||||||
|
if ( $topic->count() > 0 ) {
|
||||||
|
|
||||||
|
foreach ( $topic AS $isi ) {
|
||||||
|
|
||||||
|
// count data task
|
||||||
|
$isi->total = Task::where('android_topic_id', $isi->id)->count();
|
||||||
|
array_push( $dt_keseluruhan, $isi );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('android.admin.topic.index', compact('dt_keseluruhan'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// tambah topik
|
||||||
|
function add(Request $request) {
|
||||||
|
|
||||||
|
$nama_file = "";
|
||||||
|
$directory_upload = "android23/profile";
|
||||||
|
|
||||||
|
|
||||||
|
// cek direktori
|
||||||
|
$direktori = 'android23/document/'.$request->folder_path;
|
||||||
|
if ( !is_dir( $direktori ) ){
|
||||||
|
|
||||||
|
mkdir( $direktori );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $request->hasFile('picturePath') ) {
|
||||||
|
|
||||||
|
$file = $request->file('picturePath');
|
||||||
|
$nama_file = $file->getClientOriginalName();
|
||||||
|
$file->move($directory_upload, $nama_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'title' => $request->title,
|
||||||
|
'description' => $request->description,
|
||||||
|
'folder_path' => $request->folder_path,
|
||||||
|
'picturePath' => $nama_file,
|
||||||
|
'status' => $request->status
|
||||||
|
);
|
||||||
|
|
||||||
|
Topic::insert($data);
|
||||||
|
|
||||||
|
return redirect('android23/topic');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// update topik
|
||||||
|
function update( Request $request, $id ) {
|
||||||
|
|
||||||
|
$topik = Topic::where("id", $id)->first();
|
||||||
|
$nama_file = $topik->picturePath;
|
||||||
|
|
||||||
|
$directory_upload = "android23/profile";
|
||||||
|
|
||||||
|
if ( $request->hasFile('picturePath') ) {
|
||||||
|
|
||||||
|
$file = $request->file('picturePath');
|
||||||
|
$nama_file = $file->getClientOriginalName();
|
||||||
|
$file->move($directory_upload, $nama_file);
|
||||||
|
|
||||||
|
|
||||||
|
// delete old pic
|
||||||
|
if ( !empty( $topik->picturePath ) ) {
|
||||||
|
|
||||||
|
unlink( $directory_upload .'/'. $topik->picturePath );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'title' => $request->title,
|
||||||
|
'description' => $request->description,
|
||||||
|
'folder_path' => $request->folder_path,
|
||||||
|
'picturePath' => $nama_file,
|
||||||
|
'status' => $request->status
|
||||||
|
);
|
||||||
|
|
||||||
|
Topic::where('id', $id)->update($data);
|
||||||
|
return redirect('android23/topic');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// delete
|
||||||
|
function delete( $id ) {
|
||||||
|
|
||||||
|
Topic::where("id", $id)->delete();
|
||||||
|
return redirect('android23/topic');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// learning task
|
||||||
|
function learning_view( $id ) {
|
||||||
|
|
||||||
|
$topic = Topic::where('id', $id)->first();
|
||||||
|
$dt_task = Task::where('android_topic_id', $id)->get();
|
||||||
|
|
||||||
|
// sum bobot keseluruhan
|
||||||
|
// $total = Task::where('android_topic_id', $id)->sum('bobot');
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
$task = array();
|
||||||
|
foreach ( $dt_task AS $isi ) {
|
||||||
|
|
||||||
|
// ambil data testcase
|
||||||
|
$testcase = Testcase::where("task_id", $isi->id)->get();
|
||||||
|
$isi->testcase = $testcase;
|
||||||
|
|
||||||
|
array_push( $task, $isi );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// print_r( $total );
|
||||||
|
return view('android.admin.topic.learning', compact('topic', 'task', 'total'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// tambah learnng
|
||||||
|
function learning_add( Request $request, $id, $tipe ) {
|
||||||
|
|
||||||
|
$topic = Topic::where('id', $id)->first();
|
||||||
|
$nama_file = "";
|
||||||
|
$directory_upload = "android23/document/". $topic->folder_path;
|
||||||
|
|
||||||
|
if ( $request->hasFile('material') ) {
|
||||||
|
|
||||||
|
$file = $request->file('material');
|
||||||
|
$nama_file = $file->getClientOriginalName();
|
||||||
|
$file->move($directory_upload, $nama_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert data task
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'android_topic_id' => $id,
|
||||||
|
'task_no' => $request->task_no,
|
||||||
|
'task_name' => $request->title,
|
||||||
|
'caption' => $request->caption,
|
||||||
|
'material' => $nama_file,
|
||||||
|
'tipe' => $tipe,
|
||||||
|
// 'testcase' => implode(',', $tags)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$id_task = Task::create($data)->id;
|
||||||
|
|
||||||
|
|
||||||
|
// cek apakah mengisi data
|
||||||
|
|
||||||
|
|
||||||
|
$dt_testcase = array();
|
||||||
|
if ( $request->has('tags') && $tipe == "submission" ){
|
||||||
|
|
||||||
|
// convert tags to obj
|
||||||
|
$data_tags = json_decode($request->tags);
|
||||||
|
|
||||||
|
foreach ( $data_tags AS $val ){
|
||||||
|
|
||||||
|
array_push( $dt_testcase, [
|
||||||
|
|
||||||
|
'task_id' => $id_task,
|
||||||
|
'case' => $val->value,
|
||||||
|
'score' => 0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// print_r( $dt_testcase );
|
||||||
|
// insert data testcase
|
||||||
|
Testcase::insert( $dt_testcase );
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('android23/topic/learning/'. $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function learning_update( Request $request, $id_topic, $id_task ) {
|
||||||
|
|
||||||
|
$topik = Topic::where("id", $id_topic)->first();
|
||||||
|
$task = Task::where("id", $id_task)->first();
|
||||||
|
$nama_file = $task->material;
|
||||||
|
|
||||||
|
$directory_upload = "android23/document/$topik->folder_path";
|
||||||
|
|
||||||
|
if ( $request->hasFile('material') ) {
|
||||||
|
|
||||||
|
$file = $request->file('material');
|
||||||
|
$nama_file = $file->getClientOriginalName();
|
||||||
|
$file->move($directory_upload, $nama_file);
|
||||||
|
|
||||||
|
|
||||||
|
// delete old pic
|
||||||
|
if ( !empty( $task->material ) ) {
|
||||||
|
|
||||||
|
unlink( $directory_upload .'/'. $task->material );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'task_no' => $request->task_no,
|
||||||
|
'task_name' => $request->title,
|
||||||
|
'caption' => $request->caption,
|
||||||
|
'material' => $nama_file,
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
Task::where('id', $id_task)->update($data);
|
||||||
|
return redirect('android23/topic/learning/'. $id_topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function learning_remove( $id_topic, $id_task ) {
|
||||||
|
|
||||||
|
// remove data testcase
|
||||||
|
Testcase::where('task_id', $id_task)->delete();
|
||||||
|
|
||||||
|
Task::where('id', $id_task)->delete();
|
||||||
|
return redirect('android23/topic/learning/'. $id_topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Testcase */
|
||||||
|
public function learning_update_testcase( Request $request ) {
|
||||||
|
|
||||||
|
// task id
|
||||||
|
$task_id = $request->task_id;
|
||||||
|
|
||||||
|
$dt_testcase_baru = array();
|
||||||
|
|
||||||
|
foreach ( $request->case AS $index => $isi ) {
|
||||||
|
|
||||||
|
array_push( $dt_testcase_baru, [
|
||||||
|
|
||||||
|
'task_id' => $task_id,
|
||||||
|
'case' => $isi,
|
||||||
|
'score' => $request->score[$index]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// hapus data testcase lama
|
||||||
|
Testcase::where('task_id', $task_id)->delete();
|
||||||
|
Testcase::insert( $dt_testcase_baru );
|
||||||
|
|
||||||
|
return redirect('android23/topic/learning/'. $request->topic_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function learning_add_testcase( Request $request, $topic_id, $task_id ) {
|
||||||
|
|
||||||
|
$dt_testcase = array();
|
||||||
|
if ( $request->has('tags') ){
|
||||||
|
|
||||||
|
// convert tags to obj
|
||||||
|
$data_tags = json_decode($request->tags);
|
||||||
|
|
||||||
|
foreach ( $data_tags AS $val ){
|
||||||
|
|
||||||
|
array_push( $dt_testcase, [
|
||||||
|
|
||||||
|
'task_id' => $task_id,
|
||||||
|
'case' => $val->value,
|
||||||
|
'score' => 0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert data testcase
|
||||||
|
Testcase::insert( $dt_testcase );
|
||||||
|
}
|
||||||
|
return redirect('android23/topic/learning/'. $topic_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function learning_reset_testcase( $topic_id, $task_id ) {
|
||||||
|
|
||||||
|
Testcase::where('task_id', $task_id)->delete();
|
||||||
|
return redirect('android23/topic/learning/'. $topic_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function learning_remove_testcase( $topic_id, $testcase_id ){
|
||||||
|
|
||||||
|
$testcase = Testcase::find( $testcase_id );
|
||||||
|
$testcase->delete();
|
||||||
|
|
||||||
|
return redirect('android23/topic/learning/'. $topic_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Android/AssignController.php
Normal file
11
app/Http/Controllers/Android/AssignController.php
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Android23;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AssignController extends Controller
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
42
app/Http/Controllers/Android/EnrollController.php
Normal file
42
app/Http/Controllers/Android/EnrollController.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Android;
|
||||||
|
|
||||||
|
use App\Models\Android\Enrollment;
|
||||||
|
use App\Models\Android\Topic;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EnrollController extends Controller
|
||||||
|
{
|
||||||
|
public function enroll( Request $request, $topic_id = null ){
|
||||||
|
|
||||||
|
// cek
|
||||||
|
$topik = Topic::findOrFail($topic_id);
|
||||||
|
|
||||||
|
$where = ['android_topic_id' => $topic_id, 'user_id' => Auth::user()->id];
|
||||||
|
$enroll_status = Enrollment::where($where)->count();
|
||||||
|
|
||||||
|
if ( $enroll_status == 0 ) {
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'status' => "process"
|
||||||
|
);
|
||||||
|
|
||||||
|
Enrollment::create( $data );
|
||||||
|
return redirect()->route('material');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// sebelumnya sudah pernah melakukan enroll
|
||||||
|
return redirect()->route('material')->withErrors('pesan', 'Invalid Enrollment Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
670
app/Http/Controllers/Android/MaterialController.php
Normal file
670
app/Http/Controllers/Android/MaterialController.php
Normal file
|
|
@ -0,0 +1,670 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Http\Controllers\Android;
|
||||||
|
|
||||||
|
use App\Models\Android\Enrollment;
|
||||||
|
use App\Models\Android\Submits;
|
||||||
|
use App\Models\Android\SubmitsTestCase;
|
||||||
|
use App\Models\Android\SubmitsFinalSubmission;
|
||||||
|
use App\Models\Android\Task;
|
||||||
|
use App\Models\Android\Task_waiting;
|
||||||
|
use App\Models\Android\Topic;
|
||||||
|
use App\Models\Android\Testcase;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class MaterialController extends Controller
|
||||||
|
{
|
||||||
|
public function index() {
|
||||||
|
|
||||||
|
$topic = DB::table("android_topics");
|
||||||
|
$data_keseluruhan = array();
|
||||||
|
$id = Auth::user()->id;
|
||||||
|
|
||||||
|
$recent_task = array();
|
||||||
|
|
||||||
|
if ( $topic->count() > 0 ) {
|
||||||
|
|
||||||
|
foreach ( $topic->get() AS $isi ){
|
||||||
|
|
||||||
|
// init
|
||||||
|
$status = "enroll";
|
||||||
|
$progress = 0;
|
||||||
|
$grade = "-";
|
||||||
|
$total_task = Task::where("android_topic_id", $isi->id)->count();
|
||||||
|
$recent = "-";
|
||||||
|
$akses = "-";
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'android_topic_id' => $isi->id,
|
||||||
|
'user_id' => $id
|
||||||
|
);
|
||||||
|
|
||||||
|
// check
|
||||||
|
$enrollment = DB::table("android_enrollment")->where($where);
|
||||||
|
if ( $enrollment->count() != 0 ) {
|
||||||
|
|
||||||
|
$dt_enroll = $enrollment->first();
|
||||||
|
$status = $dt_enroll->status;
|
||||||
|
|
||||||
|
$akses = $enrollment->first()->created_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check progress
|
||||||
|
if ( $status != "cancel" ) {
|
||||||
|
|
||||||
|
$task_waiting = Task_waiting::where( $where );
|
||||||
|
|
||||||
|
$total_task_waiting = $task_waiting->count();
|
||||||
|
if ( $total_task_waiting > 0 ) {
|
||||||
|
|
||||||
|
$info = Task::find( $task_waiting->first()->android_task_id );
|
||||||
|
$progress = $total_task_waiting / $total_task * 100;
|
||||||
|
|
||||||
|
$recent = $info->task_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$NA = 0;
|
||||||
|
if ( $status == "complete" ) {
|
||||||
|
|
||||||
|
$where_enroll = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $isi->id
|
||||||
|
);
|
||||||
|
$enrollment = Enrollment::where($where_enroll)->first();
|
||||||
|
|
||||||
|
|
||||||
|
$dt_all_submit = array();
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id, // 1577
|
||||||
|
'android_submits.android_topic_id' => $isi->id // 11
|
||||||
|
);
|
||||||
|
$submits = Submits::select('android_submits.id','android_task.task_name', 'android_submits.created_at', 'android_submits.duration')
|
||||||
|
->join('android_task', 'android_task.id', '=', 'android_submits.android_task_id')->where($where)->get();
|
||||||
|
|
||||||
|
|
||||||
|
//mulai sini autograding
|
||||||
|
|
||||||
|
$estimate = 0;
|
||||||
|
$total_submit = $submits->count();
|
||||||
|
|
||||||
|
if ( $submits->count() > 0 ) {
|
||||||
|
|
||||||
|
$total_test = 0;
|
||||||
|
|
||||||
|
foreach ( $submits AS $isi_sb ) {
|
||||||
|
|
||||||
|
$submit_id = $isi_sb->id;
|
||||||
|
|
||||||
|
|
||||||
|
$where_passed = [
|
||||||
|
|
||||||
|
'android_submit_id' => $isi_sb->id,
|
||||||
|
'status_validate' => "passed"
|
||||||
|
];
|
||||||
|
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
|
||||||
|
|
||||||
|
// ambil informasi data task untuk memanggil bobot
|
||||||
|
$info_testcase = Testcase::where("task_id", $isi_sb->android_task_id)->get();
|
||||||
|
|
||||||
|
$bobot = 0;
|
||||||
|
foreach ( $info_testcase AS $isiit ) {
|
||||||
|
|
||||||
|
$bobot += $isiit->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skor = 0;
|
||||||
|
foreach ( $test_passed AS $isitp ) {
|
||||||
|
|
||||||
|
$skor += $isitp->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nilai = 0;
|
||||||
|
if ( $skor > 0 && $bobot > 0 ) {
|
||||||
|
|
||||||
|
$nilai = $skor / $bobot * 100;
|
||||||
|
// echo "masuk";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ----
|
||||||
|
|
||||||
|
// $dt_submit_testcase = array();
|
||||||
|
|
||||||
|
// $testcase = SubmitsTestCase::where("android_submit_id", $submit_id)->get();
|
||||||
|
|
||||||
|
// // autograding
|
||||||
|
// $passed = 0;
|
||||||
|
// foreach ( $testcase AS $det ) {
|
||||||
|
|
||||||
|
// if ( $det->status_validate == "passed" ) {
|
||||||
|
|
||||||
|
// $passed++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $persentage = ($passed / $testcase->count() * 100);
|
||||||
|
$total_test += $nilai;
|
||||||
|
$estimate += $isi_sb->duration;
|
||||||
|
|
||||||
|
|
||||||
|
// echo $nilai.'<br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$NA = $total_test / $total_submit;
|
||||||
|
|
||||||
|
|
||||||
|
// echo $total_test.' - '. $total_submit;
|
||||||
|
// echo $NA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$isi->status_waiting = $status;
|
||||||
|
$isi->progress = $progress;
|
||||||
|
$isi->grade = $grade;
|
||||||
|
$isi->total_task = $total_task;
|
||||||
|
$isi->akses_materi = $akses;
|
||||||
|
$isi->recent = $recent;
|
||||||
|
$isi->NA = $NA;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// recent task
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'android_task.android_topic_id' => $isi->id,
|
||||||
|
'user_id' => $id
|
||||||
|
);
|
||||||
|
$recent_task = Task_waiting::select("task_name", "title")
|
||||||
|
->join("android_task", "android_task.id", "=", "android_task_waiting.android_task_id")
|
||||||
|
->join("android_topics", "android_topics.id", "=", "android_task_waiting.android_topic_id")
|
||||||
|
->where( $where )->orderBy('android_task.created_at', 'desc')->get()->unique('android_topic_id');
|
||||||
|
|
||||||
|
array_push( $data_keseluruhan, $isi );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return view('android.student.material.index', compact('data_keseluruhan', 'recent_task'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// upload
|
||||||
|
public function upload( Request $request ) {
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
|
||||||
|
'file' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fileName = time().'-'.request()->file->getClientOriginalExtension();
|
||||||
|
request()->file->move(public_path('files'), $fileName);
|
||||||
|
|
||||||
|
return request()->json([
|
||||||
|
'success' => "You have successfully upload file"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function task( Request $request, $topic_id ) {
|
||||||
|
|
||||||
|
// ambil data informasi topik
|
||||||
|
$topic = Topic::findOrFail( $topic_id );
|
||||||
|
$where = array(
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_task_id' => $request->id
|
||||||
|
);
|
||||||
|
$submit_byId = Submits::where($where);
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
|
||||||
|
// cek memiliki id atau tidak
|
||||||
|
if ($request->filled('id')) {
|
||||||
|
|
||||||
|
$id_task_recommend = $request->id;
|
||||||
|
|
||||||
|
// ambil informasi data task berdasarkan id
|
||||||
|
$task = Task::findOrFail($id_task_recommend);
|
||||||
|
$testcase = Testcase::where("task_id", $id_task_recommend)->get();
|
||||||
|
|
||||||
|
// $task = Task::where('id', $id_task_recommend);
|
||||||
|
|
||||||
|
|
||||||
|
$submit_information = array();
|
||||||
|
$submit_testcase = array();
|
||||||
|
if ( $submit_byId->count() > 0 ) {
|
||||||
|
|
||||||
|
$submits = $submit_byId->first();
|
||||||
|
|
||||||
|
$submitTestCase = SubmitsTestCase::where("android_submit_id", $submits->id)->get();
|
||||||
|
|
||||||
|
$submit_information = $submits;
|
||||||
|
$submit_testcase = $submitTestCase;
|
||||||
|
|
||||||
|
|
||||||
|
// echo json_encode($submits->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// cek apakah sudah mengakses materi ?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// next material
|
||||||
|
$next = Task::where('id', '>', $task->id);
|
||||||
|
|
||||||
|
if ( $next->count() > 0 ) {
|
||||||
|
|
||||||
|
$url = '/android23/task/'.$topic_id.'?id='. $next->min('id');
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$url = '/android23/task/'.$topic_id.'?id='. $id_task_recommend.'&type=final';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$dt_task = array();
|
||||||
|
$tasks = Task::where("android_topic_id", $topic_id)->orderBy('task_no', 'ASC')->get();
|
||||||
|
|
||||||
|
foreach ( $tasks AS $isi_tsk ) {
|
||||||
|
|
||||||
|
$where = [
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
'android_task_id' => $isi_tsk->id,
|
||||||
|
|
||||||
|
];
|
||||||
|
$task_waiting = Task_waiting::where($where)->get();
|
||||||
|
|
||||||
|
if ( $task_waiting->count() == 0 ) {
|
||||||
|
|
||||||
|
$isi_tsk->status_akses = false;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$isi_tsk->status_akses = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
array_push( $dt_task, $isi_tsk );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'topic_id' => $topic_id,
|
||||||
|
'id' => $id_task_recommend,
|
||||||
|
'task' => $task,
|
||||||
|
'testcase' => $testcase,
|
||||||
|
'topic' => $topic,
|
||||||
|
'all_task' => $dt_task,
|
||||||
|
'submit_information' => $submit_information,
|
||||||
|
'submit_testcase' => $submit_testcase,
|
||||||
|
'request' => $request,
|
||||||
|
'taskwaiting' => $task_waiting->count(),
|
||||||
|
'urlku' => $url
|
||||||
|
);
|
||||||
|
|
||||||
|
// echo $url;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// cek apabila request sudah final
|
||||||
|
if ( $request->type == "final" ) {
|
||||||
|
|
||||||
|
$where_enroll = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $topic_id
|
||||||
|
);
|
||||||
|
$enrollment = Enrollment::where($where_enroll)->first();
|
||||||
|
|
||||||
|
|
||||||
|
$dt_all_submit = array();
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id, // 1577
|
||||||
|
'android_submits.android_topic_id' => $topic_id // 11
|
||||||
|
);
|
||||||
|
$submits = Submits::select('android_task_id','android_submits.id','android_task.task_name', 'android_submits.created_at', 'android_submits.duration')
|
||||||
|
->join('android_task', 'android_task.id', '=', 'android_submits.android_task_id')->where($where)->get();
|
||||||
|
|
||||||
|
|
||||||
|
//mulai sini autograding
|
||||||
|
$NA = 0;
|
||||||
|
$estimate = 0;
|
||||||
|
$total_submit = $submits->count();
|
||||||
|
|
||||||
|
if ( $submits->count() > 0 ) {
|
||||||
|
|
||||||
|
$total_test = 0;
|
||||||
|
|
||||||
|
foreach ( $submits AS $isi ) {
|
||||||
|
|
||||||
|
$submit_id = $isi->id;
|
||||||
|
$dt_submit_testcase = array();
|
||||||
|
|
||||||
|
$testcase = SubmitsTestCase::where("android_submit_id", $submit_id)->get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// autograding
|
||||||
|
$where_passed = [
|
||||||
|
|
||||||
|
'android_submit_id' => $isi->id,
|
||||||
|
'status_validate' => "passed"
|
||||||
|
];
|
||||||
|
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
|
||||||
|
|
||||||
|
// ambil informasi data task untuk memanggil bobot
|
||||||
|
$info_testcase = Testcase::where("task_id", $isi->android_task_id)->get();
|
||||||
|
|
||||||
|
$bobot = 0;
|
||||||
|
foreach ( $info_testcase AS $isiit ) {
|
||||||
|
|
||||||
|
$bobot += $isiit->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skor = 0;
|
||||||
|
foreach ( $test_passed AS $isitp ) {
|
||||||
|
|
||||||
|
$skor += $isitp->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nilai = 0;
|
||||||
|
if ( $skor > 0 && $bobot > 0 ) {
|
||||||
|
|
||||||
|
$nilai = $skor / $bobot * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_test += $nilai;
|
||||||
|
|
||||||
|
|
||||||
|
// echo $isi->android_task_id.'<br>';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// autograding (old)
|
||||||
|
// $passed = 0;
|
||||||
|
// foreach ( $testcase AS $det ) {
|
||||||
|
|
||||||
|
// if ( $det->status_validate == "passed" ) {
|
||||||
|
|
||||||
|
// $passed++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $persentage = ($passed / $testcase->count() * 100);
|
||||||
|
// $total_test += $persentage;
|
||||||
|
// total_test = total_NA + persentage
|
||||||
|
// total_test = 0 + 57
|
||||||
|
// total_test = 57
|
||||||
|
|
||||||
|
// total_test = total_test + persentnage
|
||||||
|
// total test = 57 + 75
|
||||||
|
$estimate += $isi->duration;
|
||||||
|
|
||||||
|
array_push( $dt_all_submit, [
|
||||||
|
|
||||||
|
'info' => $isi,
|
||||||
|
'persentage' => $nilai,
|
||||||
|
'testcase' => $testcase
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$NA = $total_test / $total_submit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$add = array(
|
||||||
|
|
||||||
|
'submits' => $dt_all_submit,
|
||||||
|
'NA' => $NA,
|
||||||
|
'submit_submission' => SubmitsFinalSubmission::all(),
|
||||||
|
'enrollment' => $enrollment,
|
||||||
|
'estimate' => $estimate
|
||||||
|
);
|
||||||
|
|
||||||
|
// merge
|
||||||
|
$data = array_merge($data, $add);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return view
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// last recomend
|
||||||
|
$id_task_recommend = "";
|
||||||
|
$where = [
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
];
|
||||||
|
$recommend = Task_waiting::where( $where )->orderBy('created_at', 'DESC');
|
||||||
|
if ( $recommend->count() == 0 ) {
|
||||||
|
|
||||||
|
// memberikan id awal
|
||||||
|
// ambil materi awal task berdasarkan topic id
|
||||||
|
$dt_task = Task::where('android_topic_id', $topic_id)->first();
|
||||||
|
$id_task_recommend = $dt_task->id;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$dt_task = $recommend->first();
|
||||||
|
$id_task_recommend = $dt_task->android_task_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo "test";
|
||||||
|
// redirect . ..
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// access materi update
|
||||||
|
$cektaskwaiting = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
'android_task_id' => $id_task_recommend
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// cek
|
||||||
|
$cekTask = Task_waiting::where( $cektaskwaiting )->get();
|
||||||
|
if ( $cekTask->count() == 0 ) {
|
||||||
|
|
||||||
|
Task_waiting::create( $cektaskwaiting );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// print_r( $data );
|
||||||
|
if ( $request->filled('id') ) {
|
||||||
|
|
||||||
|
return view('android.student/material/task', $data);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return redirect('android23/task/'. $topic_id.'?id='. $id_task_recommend);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//validation result
|
||||||
|
public function validation() {
|
||||||
|
|
||||||
|
// informasi login sebagai
|
||||||
|
$id = Auth::user()->id;
|
||||||
|
|
||||||
|
$kondisi = ["user_id" => $id, 'status' => "complete"];
|
||||||
|
$enrollment = Enrollment::where($kondisi)->get();
|
||||||
|
|
||||||
|
$dt_keseluruhan = array();
|
||||||
|
|
||||||
|
if ( $enrollment->count() > 0 ) {
|
||||||
|
|
||||||
|
foreach ( $enrollment AS $isi ) {
|
||||||
|
|
||||||
|
// id topik
|
||||||
|
$topic_id = $isi->android_topic_id;
|
||||||
|
$topic = Topic::find( $topic_id );
|
||||||
|
|
||||||
|
$where_task = ["android_topic_id" => $topic_id, 'user_id' => $id];
|
||||||
|
// $task = Task::where( $where_task )->get();
|
||||||
|
|
||||||
|
// ambil data submits
|
||||||
|
$submit = Submits::where($where_task)->get();
|
||||||
|
|
||||||
|
$isi->submit = $submit;
|
||||||
|
$isi->topic = $topic;
|
||||||
|
// $isi->task = $task;
|
||||||
|
|
||||||
|
|
||||||
|
array_push( $dt_keseluruhan, $isi );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo "oke";
|
||||||
|
return view('android.student.validation.index', compact('dt_keseluruhan'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function validation_detail( $topic_id ) {
|
||||||
|
|
||||||
|
$enrollment = Enrollment::where( "android_topic_id", $topic_id )->first();
|
||||||
|
|
||||||
|
|
||||||
|
$dt_all_submit = array();
|
||||||
|
$where = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id, // 1577
|
||||||
|
'android_submits.android_topic_id' => $topic_id // 11
|
||||||
|
);
|
||||||
|
$submits = Submits::select('android_task_id','android_submits.id','android_task.task_name', 'android_submits.created_at', 'android_submits.duration')
|
||||||
|
->join('android_task', 'android_task.id', '=', 'android_submits.android_task_id')->where($where)->get();
|
||||||
|
|
||||||
|
|
||||||
|
//mulai sini autograding
|
||||||
|
$NA = 0;
|
||||||
|
$estimate = 0;
|
||||||
|
$total_submit = $submits->count();
|
||||||
|
|
||||||
|
if ( $submits->count() > 0 ) {
|
||||||
|
|
||||||
|
$total_test = 0;
|
||||||
|
|
||||||
|
foreach ( $submits AS $isi ) {
|
||||||
|
|
||||||
|
$submit_id = $isi->id;
|
||||||
|
$dt_submit_testcase = array();
|
||||||
|
|
||||||
|
$testcase = SubmitsTestCase::where("android_submit_id", $submit_id)->get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// autograding
|
||||||
|
$where_passed = [
|
||||||
|
|
||||||
|
'android_submit_id' => $isi->id,
|
||||||
|
'status_validate' => "passed"
|
||||||
|
];
|
||||||
|
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
|
||||||
|
|
||||||
|
// ambil informasi data task untuk memanggil bobot
|
||||||
|
$info_testcase = Testcase::where("task_id", $isi->android_task_id)->get();
|
||||||
|
|
||||||
|
$bobot = 0;
|
||||||
|
foreach ( $info_testcase AS $isiit ) {
|
||||||
|
|
||||||
|
$bobot += $isiit->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skor = 0;
|
||||||
|
foreach ( $test_passed AS $isitp ) {
|
||||||
|
|
||||||
|
$skor += $isitp->score;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nilai = 0;
|
||||||
|
if ( $skor > 0 && $bobot > 0 ) {
|
||||||
|
|
||||||
|
$nilai = $skor / $bobot * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_test += $nilai;
|
||||||
|
|
||||||
|
|
||||||
|
// echo $isi->android_task_id.'<br>';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// autograding (old)
|
||||||
|
// $passed = 0;
|
||||||
|
// foreach ( $testcase AS $det ) {
|
||||||
|
|
||||||
|
// if ( $det->status_validate == "passed" ) {
|
||||||
|
|
||||||
|
// $passed++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $persentage = ($passed / $testcase->count() * 100);
|
||||||
|
// $total_test += $persentage;
|
||||||
|
// total_test = total_NA + persentage
|
||||||
|
// total_test = 0 + 57
|
||||||
|
// total_test = 57
|
||||||
|
|
||||||
|
// total_test = total_test + persentnage
|
||||||
|
// total test = 57 + 75
|
||||||
|
$estimate += $isi->duration;
|
||||||
|
|
||||||
|
array_push( $dt_all_submit, [
|
||||||
|
|
||||||
|
'info' => $isi,
|
||||||
|
'persentage' => $nilai,
|
||||||
|
'testcase' => $testcase
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$NA = $total_test / $total_submit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dt_keseluruhan = array(
|
||||||
|
|
||||||
|
'submits' => $dt_all_submit,
|
||||||
|
'NA' => $NA,
|
||||||
|
'submit_submission' => SubmitsFinalSubmission::all(),
|
||||||
|
'enrollment' => $enrollment,
|
||||||
|
'estimate' => $estimate
|
||||||
|
);
|
||||||
|
|
||||||
|
return view('android.student.validation.detail', compact('dt_keseluruhan'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
171
app/Http/Controllers/Android/SubmissionController.php
Normal file
171
app/Http/Controllers/Android/SubmissionController.php
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Android;
|
||||||
|
|
||||||
|
use App\Models\Android\Submits;
|
||||||
|
use App\Models\Android\SubmitsTestCase;
|
||||||
|
use App\Models\Android\Enrollment;
|
||||||
|
use App\Models\Android\SubmitsFinalSubmission;
|
||||||
|
use App\Models\Android\Topic;
|
||||||
|
use App\Models\Android\Task;
|
||||||
|
use App\Models\Android\Testcase;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class SubmissionController extends Controller
|
||||||
|
{
|
||||||
|
public function proses_tambah( Request $request ) {
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
|
||||||
|
'userfile' => 'required|image',
|
||||||
|
'duration' => 'required',
|
||||||
|
'comment' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$file = $request->file('userfile');
|
||||||
|
$extension = $file->getClientOriginalExtension();
|
||||||
|
|
||||||
|
// upload path
|
||||||
|
$upload_path = "android23/submission";
|
||||||
|
$fileName = uniqid().'-'.strtotime("now").'.'.$extension;
|
||||||
|
|
||||||
|
$file->move($upload_path, $fileName);
|
||||||
|
$task_id = $request->android_task_id;
|
||||||
|
$topic_id = $request->android_topic_id;
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
'android_task_id' => $task_id,
|
||||||
|
'duration' => $request->duration,
|
||||||
|
'upload' => $fileName,
|
||||||
|
'comment' => $request->comment,
|
||||||
|
'validator' => "process"
|
||||||
|
);
|
||||||
|
|
||||||
|
$submit = Submits::create( $data );
|
||||||
|
|
||||||
|
$dt_task = Task::findOrFail( $task_id );
|
||||||
|
$dt_testcasefromtask = Testcase::where("task_id", $task_id)->get();
|
||||||
|
|
||||||
|
|
||||||
|
// listdata test case
|
||||||
|
$data_submit = array();
|
||||||
|
|
||||||
|
if ( $request->has('task') ) {
|
||||||
|
foreach ( $dt_testcasefromtask AS $isi ) {
|
||||||
|
|
||||||
|
$status = false;
|
||||||
|
foreach ( $request->task AS $ts ){
|
||||||
|
|
||||||
|
if ( $ts == $isi->id ) {
|
||||||
|
|
||||||
|
$status = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$label = "failed";
|
||||||
|
if ( $status ) {
|
||||||
|
|
||||||
|
$label = "passed";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
array_push($data_submit, [
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_submit_id' => $submit->id,
|
||||||
|
'android_testcase_id'=> $isi->id,
|
||||||
|
'status'=> $label,
|
||||||
|
'status_validate' => 'waiting',
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
'updated_at' => Carbon::now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// print_r( $data_submit );
|
||||||
|
SubmitsTestCase::insert($data_submit);
|
||||||
|
}
|
||||||
|
return redirect('android23/task/'.$topic_id.'?id='.$task_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// submit final submission
|
||||||
|
public function submit_final_submission( Request $request, $topic_id ){
|
||||||
|
|
||||||
|
|
||||||
|
if ( !empty( $request->type ) ) {
|
||||||
|
// validation
|
||||||
|
if ( $request->type == "github" ) {
|
||||||
|
|
||||||
|
$rules = array(
|
||||||
|
'link' => 'required|string'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$rules = array(
|
||||||
|
'userfile' => 'required|file|mimes:zip|max:3072'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check rules
|
||||||
|
$request->validate($rules);
|
||||||
|
|
||||||
|
if ( $request->type == "zip" ) {
|
||||||
|
|
||||||
|
// file upload
|
||||||
|
$file = $request->file('userfile');
|
||||||
|
|
||||||
|
// upload path
|
||||||
|
$upload_path = "android23/final-submission";
|
||||||
|
$userfile = uniqid().'-'.strtotime("now").'.zip';
|
||||||
|
|
||||||
|
$file->move($upload_path, $userfile);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// github
|
||||||
|
$userfile = $request->link;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
|
||||||
|
'user_id' => Auth::user()->id,
|
||||||
|
'android_topic_id' => $topic_id,
|
||||||
|
'tipe' => $request->type,
|
||||||
|
'userfile' => $userfile
|
||||||
|
);
|
||||||
|
|
||||||
|
SubmitsFinalSubmission::create( $data );
|
||||||
|
|
||||||
|
|
||||||
|
$dataEnrollUpdate = array(
|
||||||
|
|
||||||
|
'status' => "review"
|
||||||
|
);
|
||||||
|
Enrollment::where(['user_id' => $data['user_id'], 'android_topic_id' => $data['android_topic_id']])->update( $dataEnrollUpdate );
|
||||||
|
|
||||||
|
// redirect
|
||||||
|
return redirect('android23/task/'.$topic_id.'?id='.$request->task_id.'&type=final');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function overview( $id ) {
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'topic_id' => $id,
|
||||||
|
'topic' => Topic::findOrFail( $id ),
|
||||||
|
'all_task' => Task::orderBy('task_no', 'ASC')->get(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return view('student.android23.material.overview', $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Http/Controllers/AuthController.php
Normal file
40
app/Http/Controllers/AuthController.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
public function proses( Request $request ) {
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
|
||||||
|
'email' => 'required',
|
||||||
|
'password' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$credential = request(['email', 'password']);
|
||||||
|
if ( Auth::attempt( $credential ) ) {
|
||||||
|
|
||||||
|
if ( Auth::user()->roleid == "admin" ) {
|
||||||
|
|
||||||
|
return redirect('android23/topic');
|
||||||
|
|
||||||
|
} else if ( Auth::user()->roleid == "teacher" ) {
|
||||||
|
|
||||||
|
return redirect('teacher/android23/material');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// student
|
||||||
|
return redirect('android23/material');
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
echo "okee err";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
app/Http/Controllers/Controller.php
Normal file
12
app/Http/Controllers/Controller.php
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
}
|
||||||
67
app/Http/Controllers/DataController.php
Normal file
67
app/Http/Controllers/DataController.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class DataController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$cards = [
|
||||||
|
[
|
||||||
|
'image' => './images/cards/Android.png',
|
||||||
|
'title' => 'Android programming with Java and Kotlin',
|
||||||
|
'topics' => '18 learning topics',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'image' => './images/cards/Flutter.png',
|
||||||
|
'title' => 'Mobile programming with Flutter',
|
||||||
|
'topics' => '18 learning topics',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'image' => './images/cards/Node.js.png',
|
||||||
|
'title' => 'Web application with Node.JS',
|
||||||
|
'topics' => '18 learning topics',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$cardsData = [
|
||||||
|
[
|
||||||
|
'image' => './images/cards/computer.png',
|
||||||
|
'title' => 'Fully Computer-Assisted Learning Platform',
|
||||||
|
'description' => 'Digital educational platform that utilizes artificial intelligence and machine learning to provide a comprehensive and interactive learning experience entirely driven by computer technology.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'image' => './images/cards/eos-icons_machine-learning.png',
|
||||||
|
'title' => 'Intelligence Guidance',
|
||||||
|
'description' => 'System or technology that provides automated support and guidance to learners, assisting them in their learning journey through intelligent algorithms and machine learning.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'image' => './images/cards/Grading.png',
|
||||||
|
'title' => 'Auto Grading',
|
||||||
|
'description' => 'Technology that automatically evaluates and scores assignments, exams, or assessments, eliminating the need for manual grading by instructors and providing efficient and consistent feedback to students.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$cardsData2 = [
|
||||||
|
[
|
||||||
|
'image' => './images/cards/Intelligence.png',
|
||||||
|
'title' => 'Intelligence Learning Guidance',
|
||||||
|
'description' => 'Intelligence Learning Guidance utilizes AI and smart algorithms to provide personalized support, adapting to learners needs and optimizing their educational outcomes.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'image' => './images/cards/coding.png',
|
||||||
|
'title' => 'Practical Coding Approach',
|
||||||
|
'description' => 'Focuses on teaching coding skills through real-world examples, projects, and problem-solving scenarios, enabling learners to develop practical coding proficiency and problem-solving abilities.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'image' => './images/cards/Machine.png',
|
||||||
|
'title' => 'Online Virtual Machine',
|
||||||
|
'description' => 'Virtual computing environment accessible over the internet, enabling users to run applications, perform tasks, and store data without requiring physical hardware.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('welcome', compact('cards', 'cardsData', 'cardsData2'));
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/Http/Controllers/NodeJS/Student/DashboardController.php
Normal file
45
app/Http/Controllers/NodeJS/Student/DashboardController.php
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NodeJS\Student;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\NodeJS\Project;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Yajra\DataTables\Facades\DataTables;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$projects = Project::skip(0)->take(3)->get();
|
||||||
|
if ($request->ajax()) {
|
||||||
|
$data = DB::connection('nodejsDB')->table('projects')
|
||||||
|
->select('projects.id', 'projects.title', DB::raw('COUNT(submissions.id) as submission_count'))
|
||||||
|
->leftJoin('submissions', function ($join) use ($user) {
|
||||||
|
$join->on('projects.id', '=', 'submissions.project_id')
|
||||||
|
->where('submissions.user_id', '=', $user->id);
|
||||||
|
})
|
||||||
|
->groupBy('projects.id');
|
||||||
|
|
||||||
|
|
||||||
|
return Datatables::of($data)
|
||||||
|
->addIndexColumn()
|
||||||
|
->addColumn('title', function ($row) {
|
||||||
|
$title_button = '<a href="/nodejs/submissions/project/' . $row->id . '" class="underline text-secondary">' . $row->title . '</a>';
|
||||||
|
return $title_button;
|
||||||
|
})
|
||||||
|
->addColumn('submission_count', function ($row) {
|
||||||
|
$submission_count = $row->submission_count ?? 0;
|
||||||
|
$text = $submission_count > 0 ? 'Submitted' : 'No Submission';
|
||||||
|
$span_color = $submission_count > 0 ? 'green' : 'gray';
|
||||||
|
$span = '<span class="inline-flex items-center justify-center px-2 py-1 rounded-lg text-xs font-bold leading-none bg-' . $span_color . '-100 text-' . $span_color . '-800">' . ucfirst($text) . '</span>';
|
||||||
|
return $span;
|
||||||
|
})
|
||||||
|
->rawColumns(['title', 'submission_count'])
|
||||||
|
->make(true);
|
||||||
|
}
|
||||||
|
return view('nodejs.dashboard.index', compact('projects'));
|
||||||
|
}
|
||||||
|
}
|
||||||
185
app/Http/Controllers/NodeJS/Student/ProjectController.php
Normal file
185
app/Http/Controllers/NodeJS/Student/ProjectController.php
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NodeJS\Student;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Exception;
|
||||||
|
use ZipArchive;
|
||||||
|
use App\Models\NodeJS\Project;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class ProjectController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$projects = Project::all();
|
||||||
|
return view('nodejs.projects.index', compact('projects'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request, $project_id)
|
||||||
|
{
|
||||||
|
$project = Project::find($project_id);
|
||||||
|
if (!$project) {
|
||||||
|
return redirect()->route('projects');
|
||||||
|
}
|
||||||
|
return view('nodejs.projects.show', compact('project'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showPDF(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->ajax()) {
|
||||||
|
if ($request->id) {
|
||||||
|
$mediaModel = new Media();
|
||||||
|
$mediaModel->setConnection('nodejsDB');
|
||||||
|
$media = $mediaModel->find($request->id);
|
||||||
|
if ($media) {
|
||||||
|
$path = $media->getUrl();
|
||||||
|
return response()->json($path, 200);
|
||||||
|
}
|
||||||
|
return response()->json(["message" => "media not found"], 404);
|
||||||
|
}
|
||||||
|
return response()->json(["message" => "no media was requested"], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function download(Request $request, $project_id)
|
||||||
|
{
|
||||||
|
if ($request->ajax()) {
|
||||||
|
$project = Project::find($project_id);
|
||||||
|
if (!$project) {
|
||||||
|
return response()->json(["message" => "project not found"], 404);
|
||||||
|
}
|
||||||
|
if ($request->type) {
|
||||||
|
switch ($request->type) {
|
||||||
|
case 'guides':
|
||||||
|
$zipMedia = $project->getMedia('project_zips')->where('file_name', 'guides.zip')->first();
|
||||||
|
if ($zipMedia) {
|
||||||
|
return response()->json($zipMedia->getUrl(), 200);
|
||||||
|
} else {
|
||||||
|
$guides = $project->getMedia('project_guides');
|
||||||
|
$tempDir = storage_path('app/public/assets/nodejs/projects/' . $project->title . '/zips');
|
||||||
|
if (!is_dir($tempDir)) mkdir($tempDir);
|
||||||
|
foreach ($guides as $guide) {
|
||||||
|
$path = $guide->getPath();
|
||||||
|
$filename = $guide->file_name;
|
||||||
|
copy($path, $tempDir . '/' . $filename);
|
||||||
|
}
|
||||||
|
$zipPath = $tempDir . '/guides.zip';
|
||||||
|
$zip = new ZipArchive;
|
||||||
|
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
|
||||||
|
$files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$zip->addFile(storage_path('app/' . $file), basename($file));
|
||||||
|
}
|
||||||
|
$zip->close();
|
||||||
|
foreach ($files as $file) {
|
||||||
|
unlink(storage_path('app/' . $file));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Exception('Failed to create zip archive');
|
||||||
|
}
|
||||||
|
$media = $project->addMedia($zipPath)->toMediaCollection('project_zips', 'nodejs_public_projects_files');
|
||||||
|
return response()->json($media->getUrl(), 200);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'supplements':
|
||||||
|
$zipMedia = $project->getMedia('project_zips')->where('file_name', 'supplements.zip')->first();
|
||||||
|
if ($zipMedia) {
|
||||||
|
return response()->json($zipMedia->getUrl(), 200);
|
||||||
|
} else {
|
||||||
|
$supplements = $project->getMedia('project_supplements');
|
||||||
|
$tempDir = storage_path('app/public/assets/nodejs/projects/' . $project->title . '/zips');
|
||||||
|
if (!is_dir($tempDir)) mkdir($tempDir);
|
||||||
|
foreach ($supplements as $supplement) {
|
||||||
|
$path = $supplement->getPath();
|
||||||
|
$filename = $supplement->file_name;
|
||||||
|
copy($path, $tempDir . '/' . $filename);
|
||||||
|
}
|
||||||
|
$zipPath = $tempDir . '/supplements.zip';
|
||||||
|
$zip = new ZipArchive;
|
||||||
|
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
|
||||||
|
$files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$zip->addFile(storage_path('app/' . $file), basename($file));
|
||||||
|
}
|
||||||
|
$zip->close();
|
||||||
|
foreach ($files as $file) {
|
||||||
|
unlink(storage_path('app/' . $file));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Exception('Failed to create zip archive');
|
||||||
|
}
|
||||||
|
$media = $project->addMedia($zipPath)->toMediaCollection('project_zips', 'nodejs_public_projects_files');
|
||||||
|
return response()->json($media->getUrl(), 200);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'tests':
|
||||||
|
$zipMedia = $project->getMedia('project_zips')->where('file_name', 'tests.zip')->first();
|
||||||
|
if ($zipMedia) {
|
||||||
|
return response()->json($zipMedia->getUrl(), 200);
|
||||||
|
} else {
|
||||||
|
$tests_api = $project->getMedia('project_tests_api');
|
||||||
|
$tests_web = $project->getMedia('project_tests_web');
|
||||||
|
$tests_images = $project->getMedia('project_tests_images');
|
||||||
|
|
||||||
|
$tempDir = storage_path('app/public/assets/nodejs/projects/' . $project->title . '/zips');
|
||||||
|
if (!is_dir($tempDir)) mkdir($tempDir);
|
||||||
|
if (!is_dir($tempDir . '/tests')) mkdir($tempDir . '/tests');
|
||||||
|
if (!is_dir($tempDir . '/tests/api')) mkdir($tempDir . '/tests/api');
|
||||||
|
if (!is_dir($tempDir . '/tests/web')) mkdir($tempDir . '/tests/web');
|
||||||
|
if (!is_dir($tempDir . '/tests/web/images')) mkdir($tempDir . '/tests/web/images');
|
||||||
|
|
||||||
|
foreach ($tests_api as $test) {
|
||||||
|
$path = $test->getPath();
|
||||||
|
$filename = $test->file_name;
|
||||||
|
copy($path, $tempDir . '/tests/api/' . $filename);
|
||||||
|
}
|
||||||
|
foreach ($tests_web as $test) {
|
||||||
|
$path = $test->getPath();
|
||||||
|
$filename = $test->file_name;
|
||||||
|
copy($path, $tempDir . '/tests/web/' . $filename);
|
||||||
|
}
|
||||||
|
foreach ($tests_images as $test) {
|
||||||
|
$path = $test->getPath();
|
||||||
|
$filename = $test->file_name;
|
||||||
|
copy($path, $tempDir . '/tests/web/images/' . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zipPath = $tempDir . '/tests.zip';
|
||||||
|
$zip = new ZipArchive;
|
||||||
|
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
|
||||||
|
$zip->addEmptyDir('api');
|
||||||
|
$zip->addEmptyDir('web');
|
||||||
|
$zip->addEmptyDir('web/images');
|
||||||
|
$api_files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips/tests/api');
|
||||||
|
foreach ($api_files as $file) {
|
||||||
|
$zip->addFile(storage_path('app/' . $file), 'api/' . basename($file));
|
||||||
|
}
|
||||||
|
$api_files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips/tests/web');
|
||||||
|
foreach ($api_files as $file) {
|
||||||
|
$zip->addFile(storage_path('app/' . $file), 'web/' . basename($file));
|
||||||
|
}
|
||||||
|
$image_files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips/tests/web/images');
|
||||||
|
foreach ($image_files as $file) {
|
||||||
|
$zip->addFile(storage_path('app/' . $file), 'web/images/' . basename($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->close();
|
||||||
|
Process::fromShellCommandline("rm -rf {$tempDir}/tests")->run();
|
||||||
|
} else {
|
||||||
|
throw new Exception('Failed to create zip archive');
|
||||||
|
}
|
||||||
|
$media = $project->addMedia($zipPath)->toMediaCollection('project_zips', 'nodejs_public_projects_files');
|
||||||
|
return response()->json($media->getUrl(), 200);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return response()->json(["message" => "no type was requested"], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
757
app/Http/Controllers/NodeJS/Student/SubmissionController.php
Normal file
757
app/Http/Controllers/NodeJS/Student/SubmissionController.php
Normal file
|
|
@ -0,0 +1,757 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NodeJS\Student;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\NodeJS\AddEnvFile;
|
||||||
|
use App\Jobs\NodeJS\CloneRepository;
|
||||||
|
use App\Jobs\NodeJS\CopyTestsFolder;
|
||||||
|
use App\Jobs\NodeJS\DeleteTempDirectory;
|
||||||
|
use App\Jobs\NodeJS\ExamineFolderStructure;
|
||||||
|
use App\Jobs\NodeJS\NpmInstall;
|
||||||
|
use App\Jobs\NodeJS\NpmRunStart;
|
||||||
|
use App\Jobs\NodeJS\NpmRunTests;
|
||||||
|
use App\Jobs\NodeJS\ReplacePackageJson;
|
||||||
|
use App\Jobs\NodeJS\UnzipZipFiles;
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Project;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use App\Models\NodeJS\SubmissionHistory;
|
||||||
|
use App\Models\NodeJS\TemporaryFile;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use Yajra\DataTables\Facades\DataTables;
|
||||||
|
|
||||||
|
class SubmissionController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$projects = Project::all();
|
||||||
|
if ($request->ajax()) {
|
||||||
|
$data = DB::connection('nodejsDB')->table('projects')
|
||||||
|
->select(
|
||||||
|
'projects.id',
|
||||||
|
'projects.title',
|
||||||
|
// DB::raw('(SELECT COUNT(DISTINCT submissions.id) FROM submissions WHERE submissions.project_id = projects.id AND submissions.user_id = ?) as submission_count'),
|
||||||
|
DB::raw('(SELECT COUNT(*) FROM submission_histories INNER JOIN submissions ON submissions.id = submission_histories.submission_id WHERE submissions.project_id = projects.id AND submissions.user_id = ?) as attempts_count'),
|
||||||
|
DB::raw('(SELECT status FROM submissions WHERE submissions.project_id = projects.id AND submissions.user_id = ? ORDER BY id DESC LIMIT 1) as submission_status')
|
||||||
|
)
|
||||||
|
->groupBy('projects.id', 'projects.title')
|
||||||
|
->setBindings([
|
||||||
|
// $user->id,
|
||||||
|
$user->id,
|
||||||
|
$user->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return DataTables::of($data)
|
||||||
|
->addIndexColumn()
|
||||||
|
->addColumn('title', function ($row) {
|
||||||
|
$title_button = '<a href="/nodejs/submissions/project/' . $row->id . '" class="underline text-secondary">' . $row->title . '</a>';
|
||||||
|
return $title_button;
|
||||||
|
})
|
||||||
|
->addColumn('submission_status', function ($row) {
|
||||||
|
$status = $row->submission_status ?? 'No Submission';
|
||||||
|
$status_color = ($status == 'completed') ? 'green' : (($status == 'pending') ? 'blue' : (($status == 'processing') ? 'secondary' : 'red'));
|
||||||
|
$status_button = $status != 'No Submission' ? '<span class="inline-flex items-center justify-center px-2 py-1 rounded-lg text-xs font-bold leading-none bg-' . $status_color . '-100 text-' . $status_color . '-800">' . ucfirst($status) . '</span>'
|
||||||
|
: '<span class="inline-flex items-center justify-center px-2 py-1 rounded-lg text-xs font-bold leading-none bg-gray-100 text-gray-800">No Submission</span>';
|
||||||
|
return $status_button;
|
||||||
|
})
|
||||||
|
->addColumn('action', function ($row) use ($user) {
|
||||||
|
$submission = Submission::where('project_id', $row->id)->where('user_id', $user->id)->orderBy('id', 'DESC')->first();
|
||||||
|
$buttons = '
|
||||||
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||||
|
<div @click="open = ! open">
|
||||||
|
<button
|
||||||
|
class="flex items-center text-sm font-medium text-gray-900 hover:text-gray-500 dark:text-white dark:hover:text-gray-300 hover:underline">
|
||||||
|
<svg class="ml-1 h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
|
||||||
|
aria-hidden="true">
|
||||||
|
<g id="Menu / Menu_Alt_02">
|
||||||
|
<path id="Vector" d="M11 17H19M5 12H19M11 7H19" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="transform opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="transform opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-75"
|
||||||
|
x-transition:leave-start="transform opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="transform opacity-0 scale-95"
|
||||||
|
class="absolute z-50 mt-2 w-48 rounded-md shadow-lg origin-top"
|
||||||
|
style="display: none;"
|
||||||
|
@click="open = false">
|
||||||
|
<div class="rounded-md ring-1 ring-black ring-opacity-5 py-1 bg-white dark:bg-gray-700">
|
||||||
|
';
|
||||||
|
if ($submission !== null) {
|
||||||
|
|
||||||
|
$deleteButton = ' <a data-submission-id="' . $submission->id . '" data-request-type="delete" onclick="requestServer($(this))" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out">Delete submission</a> ';
|
||||||
|
$restartButton = ' <a data-submission-id="' . $submission->id . '" data-request-type="restart" onclick="requestServer($(this))" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out">Restart submission</a> ';
|
||||||
|
$changeSourceCodeButton = ' <a data-submission-id="' . $submission->id . '" data-request-type="change-source-code" onclick="requestServer($(this))" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out">Change source code</a> ';
|
||||||
|
if ($submission->status == 'failed' || $submission->status == 'pending') {
|
||||||
|
if (!$submission->isGithubUrl()) {
|
||||||
|
$buttons .= $restartButton . $changeSourceCodeButton . $deleteButton . '</div></div>';
|
||||||
|
} else {
|
||||||
|
$buttons .= $restartButton . $deleteButton . '</div></div>';
|
||||||
|
}
|
||||||
|
} else if ($submission->status == 'processing') {
|
||||||
|
$buttons .= $restartButton . $deleteButton . '</div></div>';
|
||||||
|
} else if ($submission->status == 'completed') {
|
||||||
|
$buttons .= $deleteButton . '</div></div>';
|
||||||
|
} else {
|
||||||
|
$buttons .= '</div></div>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$buttons = '';
|
||||||
|
}
|
||||||
|
return $buttons;
|
||||||
|
})
|
||||||
|
->editColumn('attempts_count', function ($row) {
|
||||||
|
$attempts_count = $row->attempts_count ?? 0;
|
||||||
|
return $attempts_count + 1;
|
||||||
|
})
|
||||||
|
->rawColumns(['title', 'submission_status', 'action'])
|
||||||
|
->make(true);
|
||||||
|
}
|
||||||
|
return view('nodejs.submissions.index', compact('projects'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function upload(Request $request, $project_id)
|
||||||
|
{
|
||||||
|
if ($request->hasFile('folder_path')) {
|
||||||
|
$project_title = Project::find($project_id)->title;
|
||||||
|
|
||||||
|
$file = $request->file('folder_path');
|
||||||
|
$file_name = $file->getClientOriginalName();
|
||||||
|
$folder_path = 'public/nodejs/tmp/submissions/' . $request->user()->id . '/' . $project_title;
|
||||||
|
$file->storeAs($folder_path, $file_name);
|
||||||
|
|
||||||
|
TemporaryFile::create([
|
||||||
|
'folder_path' => $folder_path,
|
||||||
|
'file_name' => $file_name,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $folder_path;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'project_id' => 'required|exists:nodejsDB.projects,id',
|
||||||
|
'folder_path' => 'required_without:github_url',
|
||||||
|
'github_url' => 'required_without:folder_path',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (Submission::where('project_id', $request->project_id)->where('user_id', $request->user()->id)->exists()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission already exists',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$submission = new Submission();
|
||||||
|
$submission->user_id = $request->user()->id;
|
||||||
|
$submission->project_id = $request->project_id;
|
||||||
|
if ($request->has('folder_path')) {
|
||||||
|
$submission->type = Submission::$FILE;
|
||||||
|
$submission->path = $request->folder_path;
|
||||||
|
|
||||||
|
$temporary_file = TemporaryFile::where('folder_path', $request->folder_path)->first();
|
||||||
|
|
||||||
|
if ($temporary_file) {
|
||||||
|
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
|
||||||
|
$submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
|
||||||
|
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
|
||||||
|
rmdir(storage_path('app/' . $request->folder_path));
|
||||||
|
}
|
||||||
|
$temporary_file->delete();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$submission->type = Submission::$URL;
|
||||||
|
$submission->path = $request->github_url;
|
||||||
|
}
|
||||||
|
$submission->status = Submission::$PENDING;
|
||||||
|
$submission->start = now();
|
||||||
|
$submission->save();
|
||||||
|
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission created successfully',
|
||||||
|
'submission' => $submission,
|
||||||
|
], 201);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission failed',
|
||||||
|
'error' => $th->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showAllSubmissionsBasedOnProject(Request $request, $project_id)
|
||||||
|
{
|
||||||
|
$project = Project::find($project_id);
|
||||||
|
$submissions = Submission::where('project_id', $project_id)
|
||||||
|
->where('user_id', $request->user()->id)->get();
|
||||||
|
$submission_history = SubmissionHistory::whereIn('submission_id', $submissions->pluck('id')->toArray())->get();
|
||||||
|
|
||||||
|
if (!$project) {
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
return view('nodejs.submissions.show', compact('project', 'submissions', 'submission_history'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request, $submission_id)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
|
||||||
|
if ($submission) {
|
||||||
|
$steps = $submission->getExecutionSteps();
|
||||||
|
return view('nodejs.submissions.show', compact('submission', 'steps'));
|
||||||
|
}
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function history(Request $request, $history_id)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = SubmissionHistory::where('id', $history_id)->where('user_id', $user->id)->first();
|
||||||
|
if ($submission) {
|
||||||
|
$steps = $submission->getExecutionSteps();
|
||||||
|
return view('nodejs.submissions.show', compact('submission', 'steps'));
|
||||||
|
}
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(Request $request, $submission_id)
|
||||||
|
{
|
||||||
|
$isNotHistory = filter_var($request->isNotHistory, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = $isNotHistory ? Submission::where('id', $submission_id)->where('user_id', $user->id)->first() : SubmissionHistory::where('id', $submission_id)->where('user_id', $user->id)->first();
|
||||||
|
if (!$submission) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission not found',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$completion_percentage = round($submission->getTotalCompletedSteps() / $submission->getTotalSteps() * 100);
|
||||||
|
if ($submission->status === Submission::$PENDING) {
|
||||||
|
return $this->returnSubmissionResponse(($isNotHistory ? "Submission is processing" : "History"), $submission->status, $submission->results, $currentStep ?? null, $completion_percentage);
|
||||||
|
} else if ($submission->status === Submission::$FAILED) {
|
||||||
|
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has failed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
|
||||||
|
} else if ($submission->status === Submission::$COMPLETED) {
|
||||||
|
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has completed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
|
||||||
|
} else if ($submission->status === Submission::$PROCESSING) {
|
||||||
|
$step = $isNotHistory ? $submission->getCurrentExecutionStep() : null;
|
||||||
|
if ($step) {
|
||||||
|
return $this->returnSubmissionResponse(
|
||||||
|
$isNotHistory ? 'Step ' . $step->executionStep->name . ' is ' . $submission->results->{$step->executionStep->name}->status : "History",
|
||||||
|
$submission->status,
|
||||||
|
$submission->results,
|
||||||
|
$step,
|
||||||
|
$completion_percentage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->returnSubmissionResponse(
|
||||||
|
($isNotHistory ? 'Submission is processing meanwhile there is no step to execute' : "History"),
|
||||||
|
$submission->status,
|
||||||
|
$submission->results,
|
||||||
|
$step,
|
||||||
|
$completion_percentage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->submission_id == null || $request->isNotHistory == null) return response()->json([
|
||||||
|
'message' => 'Submission ID is required',
|
||||||
|
], 404);
|
||||||
|
|
||||||
|
$isNotHistory = filter_var($request->isNotHistory, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = $isNotHistory ? Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first() : SubmissionHistory::where('id', $request->submission_id)->where('user_id', $user->id)->first();
|
||||||
|
|
||||||
|
if ($submission) {
|
||||||
|
$completion_percentage = round($submission->getTotalCompletedSteps() / $submission->getTotalSteps() * 100);
|
||||||
|
if ($submission->status === Submission::$PENDING) {
|
||||||
|
if ($isNotHistory) {
|
||||||
|
$submission->initializeResults();
|
||||||
|
$submission->updateStatus(Submission::$PROCESSING);
|
||||||
|
$currentStep = $submission->getCurrentExecutionStep();
|
||||||
|
}
|
||||||
|
return $this->returnSubmissionResponse(($isNotHistory ? "Submission is processing" : "History"), $submission->status, $submission->results, $currentStep ?? null, $completion_percentage);
|
||||||
|
} else if ($submission->status === Submission::$COMPLETED) {
|
||||||
|
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has completed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
|
||||||
|
} else if ($submission->status === Submission::$FAILED) {
|
||||||
|
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has failed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
|
||||||
|
} else if ($submission->status === Submission::$PROCESSING) {
|
||||||
|
$step = $isNotHistory ? $submission->getCurrentExecutionStep() : null;
|
||||||
|
if ($step) {
|
||||||
|
if ($submission->results->{$step->executionStep->name}->status == Submission::$PENDING) {
|
||||||
|
$submission->updateOneResult($step->executionStep->name, Submission::$PROCESSING, " ");
|
||||||
|
switch ($step->executionStep->name) {
|
||||||
|
case ExecutionStep::$CLONE_REPOSITORY:
|
||||||
|
$this->lunchCloneRepositoryJob($submission, $submission->path, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$UNZIP_ZIP_FILES:
|
||||||
|
$zipFileDir = $submission->getMedia('submissions')->first()->getPath();
|
||||||
|
$this->lunchUnzipZipFilesJob($submission, $zipFileDir, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$EXAMINE_FOLDER_STRUCTURE:
|
||||||
|
$this->lunchExamineFolderStructureJob($submission, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$ADD_ENV_FILE:
|
||||||
|
$envFile = $submission->project->getMedia('project_files')->where('file_name', '.env')->first()->getPath();
|
||||||
|
$this->lunchAddEnvFileJob($submission, $envFile, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$REPLACE_PACKAGE_JSON:
|
||||||
|
$packageJson = $submission->project->getMedia('project_files')->where('file_name', 'package.json')->first()->getPath();
|
||||||
|
$this->lunchReplacePackageJsonJob($submission, $packageJson, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$COPY_TESTS_FOLDER:
|
||||||
|
$this->lunchCopyTestsFolderJob($submission, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$NPM_INSTALL:
|
||||||
|
$this->lunchNpmInstallJob($submission, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$NPM_RUN_START:
|
||||||
|
$this->lunchNpmRunStartJob($submission, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$NPM_RUN_TESTS:
|
||||||
|
$this->lunchNpmRunTestsJob($submission, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
case ExecutionStep::$DELETE_TEMP_DIRECTORY:
|
||||||
|
$this->lunchDeleteTempDirectoryJob($submission, $this->getTempDir($submission), $step);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->returnSubmissionResponse(
|
||||||
|
$isNotHistory ? 'Step ' . $step->executionStep->name . ' is ' . $submission->results->{$step->executionStep->name}->status : "History",
|
||||||
|
$submission->status,
|
||||||
|
$submission->results,
|
||||||
|
$step,
|
||||||
|
$completion_percentage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->returnSubmissionResponse(
|
||||||
|
($isNotHistory ? 'Submission is processing meanwhile there is no step to execute' : "History"),
|
||||||
|
$submission->status,
|
||||||
|
$submission->results,
|
||||||
|
$step,
|
||||||
|
$completion_percentage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission not found',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function returnSubmissionResponse($message, $status, $results, $next_step = null, $completion_percentage)
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'message' => $message,
|
||||||
|
'status' => $status,
|
||||||
|
'results' => $results,
|
||||||
|
'next_step' => $next_step,
|
||||||
|
'completion_percentage' => $completion_percentage,
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->submission_id == null) return response()->json([
|
||||||
|
'message' => 'Submission ID is required',
|
||||||
|
], 404);
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
|
||||||
|
if ($submission and $submission->status === Submission::$FAILED) {
|
||||||
|
// Create submission history
|
||||||
|
$submission->createHistory("Submission has failed, so it has been refreshed");
|
||||||
|
|
||||||
|
// if npm is installed
|
||||||
|
if ($submission->results->{ExecutionStep::$NPM_INSTALL}->status == Submission::$COMPLETED and !$this->is_dir_empty($this->getTempDir($submission))) {
|
||||||
|
$submission->restartAfterNpmInstall();
|
||||||
|
if ($submission->port != null) Process::fromShellCommandline('npx kill-port ' . $submission->port, null, null, null, 120)->run();
|
||||||
|
} else {
|
||||||
|
$commands = [];
|
||||||
|
if ($submission->port != null) {
|
||||||
|
$commands = [
|
||||||
|
['npx', 'kill-port', $submission->port],
|
||||||
|
['rm', '-rf', $this->getTempDir($submission)],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$commands = [
|
||||||
|
['rm', '-rf', $this->getTempDir($submission)],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Delete temp directory
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$process = new Process($command, null, null, null, 120);
|
||||||
|
$process->run();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info('Command ' . implode(" ", $command) . ' is successful');
|
||||||
|
} else {
|
||||||
|
Log::error('Command ' . implode(" ", $command) . ' has failed ' . $process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$submission->initializeResults();
|
||||||
|
$submission->updateStatus(Submission::$PENDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update submission status
|
||||||
|
$submission->increaseAttempts();
|
||||||
|
$submission->updatePort(null);
|
||||||
|
$submission->restartTime();
|
||||||
|
// Return response
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission has been refreshed',
|
||||||
|
'status' => $submission->status,
|
||||||
|
'results' => $submission->results,
|
||||||
|
'attempts' => $submission->attempts,
|
||||||
|
'completion_percentage' => 0,
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTempDir($submission)
|
||||||
|
{
|
||||||
|
return storage_path('app/public/nodejs/tmp/submissions/' . $submission->user_id . '/' . $submission->project->title . '/' . $submission->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function is_dir_empty($dir)
|
||||||
|
{
|
||||||
|
if (!is_readable($dir)) return true;
|
||||||
|
$handle = opendir($dir);
|
||||||
|
while (false !== ($entry = readdir($handle))) {
|
||||||
|
if ($entry != "." && $entry != "..") {
|
||||||
|
closedir($handle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function replaceCommandArraysWithValues($step_variables, $values, $step)
|
||||||
|
{
|
||||||
|
return array_reduce($step_variables, function ($commands, $variableValue) use ($values) {
|
||||||
|
return array_map(function ($command) use ($variableValue, $values) {
|
||||||
|
return $command === $variableValue ? $values[$variableValue] : $command;
|
||||||
|
}, $commands);
|
||||||
|
}, $step->executionStep->commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchCloneRepositoryJob($submission, $repoUrl, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ["{{repoUrl}}" => $repoUrl, '{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
dispatch(new CloneRepository($submission, $repoUrl, $tempDir, $commands))->onQueue(ExecutionStep::$CLONE_REPOSITORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchUnzipZipFilesJob($submission, $zipFileDir, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{zipFileDir}}' => $zipFileDir, '{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
dispatch(new UnzipZipFiles($submission, $zipFileDir, $tempDir, $commands))->onQueue(ExecutionStep::$UNZIP_ZIP_FILES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchExamineFolderStructureJob($submission, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
dispatch(new ExamineFolderStructure($submission, $tempDir, $commands))->onQueue(ExecutionStep::$EXAMINE_FOLDER_STRUCTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchAddEnvFileJob($submission, $envFile, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{envFile}}' => $envFile, '{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
dispatch(new AddEnvFile($submission, $envFile, $tempDir, $commands))->onQueue(ExecutionStep::$ADD_ENV_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchReplacePackageJsonJob($submission, $packageJson, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{packageJson}}' => $packageJson, '{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
dispatch(new ReplacePackageJson($submission, $packageJson, $tempDir, $commands))->onQueue(ExecutionStep::$REPLACE_PACKAGE_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchCopyTestsFolderJob($submission, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$testsDir = [
|
||||||
|
'testsDirApi' => $submission->project->getMedia('project_tests_api'),
|
||||||
|
'testsDirWeb' => $submission->project->getMedia('project_tests_web'),
|
||||||
|
'testsDirImage' => $submission->project->getMedia('project_tests_images'),
|
||||||
|
];
|
||||||
|
// command 1: [1]cp [2]-r [3]{{testsDir}} [4]{{tempDir}}
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{testsDir}}' => $testsDir, '{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
$commandsArray = [];
|
||||||
|
foreach ($testsDir['testsDirApi'] as $key => $value) {
|
||||||
|
$commands[2] = $value->getPath();
|
||||||
|
$commands[3] = $tempDir . '/tests/api';
|
||||||
|
array_push($commandsArray, $commands);
|
||||||
|
}
|
||||||
|
foreach ($testsDir['testsDirWeb'] as $key => $value) {
|
||||||
|
$commands[2] = $value->getPath();
|
||||||
|
$commands[3] = $tempDir . '/tests/web';
|
||||||
|
array_push($commandsArray, $commands);
|
||||||
|
}
|
||||||
|
foreach ($testsDir['testsDirImage'] as $key => $value) {
|
||||||
|
$commands[2] = $value->getPath();
|
||||||
|
$commands[3] = $tempDir . '/tests/web/images';
|
||||||
|
array_push($commandsArray, $commands);
|
||||||
|
}
|
||||||
|
dispatch(new CopyTestsFolder($submission, $testsDir, $tempDir, $commandsArray))->onQueue(ExecutionStep::$COPY_TESTS_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchNpmInstallJob($submission, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{options}}' => " "];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
dispatch(new NpmInstall($submission, $tempDir, $commands))->onQueue(ExecutionStep::$NPM_INSTALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchNpmRunStartJob($submission, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
dispatch_sync(new NpmRunStart($submission, $tempDir, $commands));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchNpmRunTestsJob($submission, $tempDir, $step)
|
||||||
|
{
|
||||||
|
$commands = [];
|
||||||
|
$tests = $submission->project->projectExecutionSteps->where('execution_step_id', $step->executionStep->id)->first()->variables;
|
||||||
|
foreach ($tests as $testCommandValue) {
|
||||||
|
$command = implode(" ", $step->executionStep->commands);
|
||||||
|
$key = explode("=", $testCommandValue)[0];
|
||||||
|
$value = explode("=", $testCommandValue)[1];
|
||||||
|
$testName = str_replace($key, $value, $command);
|
||||||
|
array_push($commands, explode(" ", $testName));
|
||||||
|
}
|
||||||
|
dispatch_sync(new NpmRunTests($submission, $tempDir, $commands));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lunchDeleteTempDirectoryJob($submission, $tempDir, $step, $commands = null)
|
||||||
|
{
|
||||||
|
if ($commands == null) {
|
||||||
|
$commands = $step->executionStep->commands;
|
||||||
|
$step_variables = $step->variables;
|
||||||
|
$values = ['{{tempDir}}' => $tempDir];
|
||||||
|
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
|
||||||
|
$commands = [$commands];
|
||||||
|
}
|
||||||
|
dispatch_sync(new DeleteTempDirectory($submission, $tempDir, $commands));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->ajax()) {
|
||||||
|
if ($request->submission_id == null) return response()->json([
|
||||||
|
'message' => 'Submission ID is required',
|
||||||
|
], 404);
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
|
||||||
|
if ($submission) {
|
||||||
|
$submission->delete();
|
||||||
|
// delete temp directory and media
|
||||||
|
if ($submission->type == Submission::$FILE) {
|
||||||
|
$submission->getMedia('submissions')->each(function ($media) {
|
||||||
|
$media->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$tempDir = $this->getTempDir($submission);
|
||||||
|
if (!$this->is_dir_empty($tempDir)) {
|
||||||
|
Process::fromShellCommandline('rm -rf ' . $tempDir)->run();
|
||||||
|
}
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission has been deleted successfully',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission not found',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restart(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->ajax()) {
|
||||||
|
if ($request->submission_id == null) return response()->json([
|
||||||
|
'message' => 'Submission ID is required',
|
||||||
|
], 404);
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
|
||||||
|
if ($submission) {
|
||||||
|
$submission->createHistory("Submission has been restarted");
|
||||||
|
|
||||||
|
if ($submission->port != null) {
|
||||||
|
$commands = [
|
||||||
|
['npx', 'kill-port', $submission->port],
|
||||||
|
['rm', '-rf', $this->getTempDir($submission)],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$commands = [
|
||||||
|
['rm', '-rf', $this->getTempDir($submission)],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Delete temp directory
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
if (!$this->is_dir_empty($this->getTempDir($submission))) {
|
||||||
|
$process = new Process($command, null, null, null, 120);
|
||||||
|
$process->run();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info('Command ' . implode(" ", $command) . ' is successful');
|
||||||
|
} else {
|
||||||
|
Log::error('Command ' . implode(" ", $command) . ' has failed ' . $process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$submission->restart();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission has been restarted successfully',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission not found',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changeSourceCode($submission_id)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = Submission::where('id', $submission_id)->where('user_id', $user->id)->first();
|
||||||
|
if ($submission) {
|
||||||
|
return view('nodejs.submissions.change_source_code', compact('submission'));
|
||||||
|
}
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'submission_id' => 'required|exists:nodejsDB.submissions,id',
|
||||||
|
'folder_path' => 'required_without:github_url',
|
||||||
|
'github_url' => 'required_without:folder_path',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
|
||||||
|
|
||||||
|
$submission->createHistory("Code has been changed");
|
||||||
|
|
||||||
|
if (!$submission->isGithubUrl()) {
|
||||||
|
$submission->getMedia('submissions')->each(function ($media) {
|
||||||
|
$media->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete temp directory if is not empty
|
||||||
|
$tempDir = $this->getTempDir($submission);
|
||||||
|
if (!$this->is_dir_empty($tempDir)) {
|
||||||
|
Process::fromShellCommandline('rm -rf ' . $tempDir)->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('folder_path')) {
|
||||||
|
$submission->type = Submission::$FILE;
|
||||||
|
$submission->path = $request->folder_path;
|
||||||
|
|
||||||
|
$temporary_file = TemporaryFile::where('folder_path', $request->folder_path)->first();
|
||||||
|
|
||||||
|
if ($temporary_file) {
|
||||||
|
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
|
||||||
|
$submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
|
||||||
|
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
|
||||||
|
rmdir(storage_path('app/' . $request->folder_path));
|
||||||
|
}
|
||||||
|
$temporary_file->delete();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$submission->type = Submission::$URL;
|
||||||
|
$submission->path = $request->github_url;
|
||||||
|
}
|
||||||
|
$submission->save();
|
||||||
|
$submission->restart();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission created successfully',
|
||||||
|
'submission' => $submission,
|
||||||
|
], 201);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Submission failed',
|
||||||
|
'error' => $th->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function downloadHistory(Request $request, $id)
|
||||||
|
{
|
||||||
|
if (!$request->type) {
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
$submission = $request->type == 'history' ? SubmissionHistory::where('id', $id)->where('user_id', $user->id)->first() : Submission::where('id', $id)->where('user_id', $user->id)->first();
|
||||||
|
|
||||||
|
if (!$submission) {
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->type == 'current' && $submission->status != Submission::$COMPLETED && $submission->status != Submission::$FAILED) {
|
||||||
|
return redirect()->route('submissions');
|
||||||
|
}
|
||||||
|
$results = json_encode($submission->results, JSON_PRETTY_PRINT);
|
||||||
|
$results_array = json_decode($results, true);
|
||||||
|
uasort($results_array, function ($a, $b) {
|
||||||
|
return $a['order'] - $b['order'];
|
||||||
|
});
|
||||||
|
$jsonResults = json_encode($results_array, JSON_PRETTY_PRINT);
|
||||||
|
$filename = 'submission_' . $submission->project->title . '_' . $user->id . '_' . $id . '_' . now()->format('Y-m-d_H-i-s') . '.json';
|
||||||
|
$headers = [
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Content-Disposition' => 'attachment; filename=' . $filename,
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->streamDownload(function () use ($submission, $user, $jsonResults) {
|
||||||
|
echo "Submission for project: " . $submission->project->title . " | User: " . $user->name . "\n";
|
||||||
|
echo "====================================================================================================\n";
|
||||||
|
echo $jsonResults;
|
||||||
|
echo "\n====================================================================================================";
|
||||||
|
}, $filename, $headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
app/Http/Controllers/NodeJS/Student/WelcomeController.php
Normal file
14
app/Http/Controllers/NodeJS/Student/WelcomeController.php
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\NodeJS\Student;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class WelcomeController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('nodejs.welcome');
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Http/Kernel.php
Normal file
71
app/Http/Kernel.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
|
||||||
|
class Kernel extends HttpKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The application's global HTTP middleware stack.
|
||||||
|
*
|
||||||
|
* These middleware are run during every request to your application.
|
||||||
|
*
|
||||||
|
* @var array<int, class-string|string>
|
||||||
|
*/
|
||||||
|
protected $middleware = [
|
||||||
|
// \App\Http\Middleware\TrustHosts::class,
|
||||||
|
\App\Http\Middleware\TrustProxies::class,
|
||||||
|
\Illuminate\Http\Middleware\HandleCors::class,
|
||||||
|
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||||
|
\App\Http\Middleware\TrimStrings::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware groups.
|
||||||
|
*
|
||||||
|
* @var array<string, array<int, class-string|string>>
|
||||||
|
*/
|
||||||
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||||
|
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's middleware aliases.
|
||||||
|
*
|
||||||
|
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
|
||||||
|
*
|
||||||
|
* @var array<string, class-string|string>
|
||||||
|
*/
|
||||||
|
protected $middlewareAliases = [
|
||||||
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
|
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
|
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||||
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
|
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||||
|
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
|
||||||
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
|
||||||
|
'teacher' => \App\Http\Middleware\TeacherMiddleware::class,
|
||||||
|
'student' => \App\Http\Middleware\StudentMiddleware::class
|
||||||
|
];
|
||||||
|
}
|
||||||
17
app/Http/Middleware/Authenticate.php
Normal file
17
app/Http/Middleware/Authenticate.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class Authenticate extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the path the user should be redirected to when they are not authenticated.
|
||||||
|
*/
|
||||||
|
protected function redirectTo(Request $request): ?string
|
||||||
|
{
|
||||||
|
return $request->expectsJson() ? null : route('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||||
|
|
||||||
|
class EncryptCookies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the cookies that should not be encrypted.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||||
|
|
||||||
|
class PreventRequestsDuringMaintenance extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be reachable while maintenance mode is enabled.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
30
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
30
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class RedirectIfAuthenticated
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||||
|
{
|
||||||
|
$guards = empty($guards) ? [null] : $guards;
|
||||||
|
|
||||||
|
foreach ($guards as $guard) {
|
||||||
|
if (Auth::guard($guard)->check()) {
|
||||||
|
return redirect(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Http/Middleware/StudentMiddleware.php
Normal file
27
app/Http/Middleware/StudentMiddleware.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class StudentMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
|
||||||
|
if ( Auth::user()->roleid !== "student" ) {
|
||||||
|
|
||||||
|
abort(403, "Unauthorized Action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Middleware/TeacherMiddleware.php
Normal file
28
app/Http/Middleware/TeacherMiddleware.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class TeacherMiddleware
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
|
||||||
|
if ( Auth::user()->roleid !== "teacher" ) {
|
||||||
|
|
||||||
|
abort(403, "Unauthorized action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Http/Middleware/TrimStrings.php
Normal file
19
app/Http/Middleware/TrimStrings.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||||
|
|
||||||
|
class TrimStrings extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the attributes that should not be trimmed.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
'current_password',
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
}
|
||||||
20
app/Http/Middleware/TrustHosts.php
Normal file
20
app/Http/Middleware/TrustHosts.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||||
|
|
||||||
|
class TrustHosts extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the host patterns that should be trusted.
|
||||||
|
*
|
||||||
|
* @return array<int, string|null>
|
||||||
|
*/
|
||||||
|
public function hosts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->allSubdomainsOfApplicationUrl(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Middleware/TrustProxies.php
Normal file
28
app/Http/Middleware/TrustProxies.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TrustProxies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The trusted proxies for this application.
|
||||||
|
*
|
||||||
|
* @var array<int, string>|string|null
|
||||||
|
*/
|
||||||
|
protected $proxies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The headers that should be used to detect proxies.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $headers =
|
||||||
|
Request::HEADER_X_FORWARDED_FOR |
|
||||||
|
Request::HEADER_X_FORWARDED_HOST |
|
||||||
|
Request::HEADER_X_FORWARDED_PORT |
|
||||||
|
Request::HEADER_X_FORWARDED_PROTO |
|
||||||
|
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||||
|
}
|
||||||
22
app/Http/Middleware/ValidateSignature.php
Normal file
22
app/Http/Middleware/ValidateSignature.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||||
|
|
||||||
|
class ValidateSignature extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the query string parameters that should be ignored.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
// 'fbclid',
|
||||||
|
// 'utm_campaign',
|
||||||
|
// 'utm_content',
|
||||||
|
// 'utm_medium',
|
||||||
|
// 'utm_source',
|
||||||
|
// 'utm_term',
|
||||||
|
];
|
||||||
|
}
|
||||||
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||||
|
|
||||||
|
class VerifyCsrfToken extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be excluded from CSRF verification.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
71
app/Jobs/NodeJS/AddEnvFile.php
Normal file
71
app/Jobs/NodeJS/AddEnvFile.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class AddEnvFile implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $envFile;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $envFile, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->envFile = $envFile;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Adding env file {$this->envFile} into {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Adding env file");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
$process = new Process($this->command);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info("Added env file {$this->envFile} into {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Added env file");
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to add env file {$this->envFile} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to add env file");
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
// failed
|
||||||
|
Log::error("Failed to add env file {$this->envFile} " . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to add env file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$ADD_ENV_FILE;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
app/Jobs/NodeJS/CloneRepository.php
Normal file
74
app/Jobs/NodeJS/CloneRepository.php
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class CloneRepository implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $repoUrl;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $repoUrl, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->repoUrl = $repoUrl;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Cloning repo {$this->repoUrl} into {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Cloning repo {$this->repoUrl}");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
$process = new Process($this->command);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
// completed
|
||||||
|
Log::info("Cloned repo {$this->repoUrl} into {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Cloned repo {$this->repoUrl}");
|
||||||
|
} else {
|
||||||
|
// failed
|
||||||
|
Log::error("Failed to clone repo {$this->repoUrl} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to clone repo {$this->repoUrl}");
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
// failed
|
||||||
|
Log::error("Failed to clone repo {$this->repoUrl} " . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to clone repo {$this->repoUrl}");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$CLONE_REPOSITORY;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
app/Jobs/NodeJS/CopyTestsFolder.php
Normal file
84
app/Jobs/NodeJS/CopyTestsFolder.php
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class CopyTestsFolder implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $testsDir;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $testsDir, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->testsDir = $testsDir;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Copying tests folder to {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Copying tests folder");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
if (is_dir($this->tempDir . '/tests')) {
|
||||||
|
Log::info("Removing old tests folder from {$this->tempDir}");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}/tests")->run();
|
||||||
|
}
|
||||||
|
mkdir($this->tempDir . '/tests', 0777, true);
|
||||||
|
mkdir($this->tempDir . '/tests/api', 0777, true);
|
||||||
|
mkdir($this->tempDir . '/tests/web', 0777, true);
|
||||||
|
mkdir($this->tempDir . '/tests/web/images', 0777, true);
|
||||||
|
foreach ($this->command as $key => $value) {
|
||||||
|
$process = new Process($value);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info("Copied tests {$value[2]} folder to {$value[3]}");
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to copying tests {$value[2]} folder to {$value[3]}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
throw new \Exception($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// completed
|
||||||
|
Log::info("Copied tests folder to {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Copied tests folder");
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error("Failed to copying tests folder to {$this->tempDir} " . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$COPY_TESTS_FOLDER;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Jobs/NodeJS/DeleteTempDirectory.php
Normal file
71
app/Jobs/NodeJS/DeleteTempDirectory.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class DeleteTempDirectory implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Deleting folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Deleting folder");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
foreach ($this->command as $key => $value) {
|
||||||
|
$process = new Process($value, null, null, null, 120);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info('Command ' . implode(" ", $value) . ' is successful');
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to delete folder {$this->tempDir} " . $process->getErrorOutput());
|
||||||
|
// $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to delete folder");
|
||||||
|
}
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
}
|
||||||
|
// completed
|
||||||
|
Log::info("Deleted folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Deleted folder");
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error("Failed to delete folder {$this->tempDir} " . $th->getMessage());
|
||||||
|
// $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to delete folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$DELETE_TEMP_DIRECTORY;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
app/Jobs/NodeJS/ExamineFolderStructure.php
Normal file
134
app/Jobs/NodeJS/ExamineFolderStructure.php
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class ExamineFolderStructure implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Examining folder structure from {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Examining folder structure");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
$process = new Process($this->command);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
// completed
|
||||||
|
$projectStructure = $submission->project->defaultFileStructure;
|
||||||
|
$defaultStructure = $projectStructure->structure;
|
||||||
|
$excludedFolders = $projectStructure->excluded;
|
||||||
|
$replacementFolders = $projectStructure->replacements;
|
||||||
|
|
||||||
|
$submissionStructure = $this->getDirectoryStructure($this->tempDir, $excludedFolders, $replacementFolders);
|
||||||
|
|
||||||
|
$diff = $this->compare_file_structures($defaultStructure, $submissionStructure);
|
||||||
|
$missingFiles = [];
|
||||||
|
foreach ($diff as $key => $value) {
|
||||||
|
if (gettype($key) == 'integer') {
|
||||||
|
if (!in_array($value, $excludedFolders)) array_push($missingFiles, $value);
|
||||||
|
} else {
|
||||||
|
if (!in_array($key, $excludedFolders)) array_push($missingFiles, [$key => $value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info("Finished examining folder structure from {$this->tempDir}");
|
||||||
|
if (empty($missingFiles)) {
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Finished examining folder structure from successfully");
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to examine folder structure from {$this->tempDir} " . json_encode($missingFiles) . " are missing");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Submitted project is missing the following files " . json_encode($missingFiles));
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to examine folder structure from {$this->tempDir} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to examine folder structure");
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error("Failed to examine folder structure from {$this->tempDir}" . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to examine folder structure");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDirectoryStructure($dirPath, $excludedFolders, $replacementFolders)
|
||||||
|
{
|
||||||
|
$structure = [];
|
||||||
|
$files = glob($dirPath . '/*');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (is_dir($file)) {
|
||||||
|
$dirName = basename($file);
|
||||||
|
if (!in_array($dirName, $excludedFolders)) {
|
||||||
|
if (isset($replacementFolders[$dirName])) {
|
||||||
|
$dirName = $replacementFolders[$dirName];
|
||||||
|
}
|
||||||
|
$structure[$dirName] = $this->getDirectoryStructure($file, $excludedFolders, $replacementFolders);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$structure[basename($file)] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function compare_file_structures($defaultStructure, $submittedStructure)
|
||||||
|
{
|
||||||
|
$diff = [];
|
||||||
|
foreach ($defaultStructure as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
if (!isset($submittedStructure[$key])) {
|
||||||
|
$diff[$key] = $value;
|
||||||
|
} else {
|
||||||
|
$new_diff = $this->compare_file_structures($value, $submittedStructure[$key]);
|
||||||
|
if (!empty($new_diff)) {
|
||||||
|
$diff[$key] = $new_diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!array_key_exists($key, $submittedStructure) || $submittedStructure[$key] !== $value) {
|
||||||
|
$diff[] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$EXAMINE_FOLDER_STRUCTURE;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Jobs/NodeJS/NpmInstall.php
Normal file
71
app/Jobs/NodeJS/NpmInstall.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class NpmInstall implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("NPM installing in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM installing");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
// check if the module is already exist within the assets folder
|
||||||
|
$process = new Process($this->command, $this->tempDir, null, null, 120);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info("NPM installed in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "NPM installed");
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to NPM install in folder {$this->tempDir} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to NPM install");
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
throw new \Exception($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error("Failed to NPM install in folder {$this->tempDir}" . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to NPM install");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$NPM_INSTALL;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
126
app/Jobs/NodeJS/NpmRunStart.php
Normal file
126
app/Jobs/NodeJS/NpmRunStart.php
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Exception\ProcessTimedOutException;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class NpmRunStart
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
$tempDir = $this->tempDir;
|
||||||
|
$command = $this->command;
|
||||||
|
|
||||||
|
|
||||||
|
Log::info("NPM run start is processing in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM run start is processing");
|
||||||
|
// Change port number in .env file
|
||||||
|
$port = $this->getAvailablePort();
|
||||||
|
if (!$port) {
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to find an available port for the project");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$submission->updatePort($port);
|
||||||
|
// Change port number in .env file
|
||||||
|
$envPath = "$tempDir/.env";
|
||||||
|
if (file_exists($envPath)) {
|
||||||
|
$envContent = file_get_contents($envPath);
|
||||||
|
$envContent = preg_replace('/PORT=\d+/', "PORT=$port", $envContent);
|
||||||
|
file_put_contents($envPath, $envContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run NPM start command
|
||||||
|
try {
|
||||||
|
$process = new Process($command, $tempDir, null, null, null);
|
||||||
|
$process->start();
|
||||||
|
$process->waitUntil(function ($type, $output) use ($port) {
|
||||||
|
return strpos($output, "Server started on port $port") !== false || strpos($output, "MongoNetworkError") !== false;
|
||||||
|
}, 60000); // Wait for 60 seconds
|
||||||
|
|
||||||
|
if (strpos($process->getOutput(), "Server started on port $port") !== false) {
|
||||||
|
Log::info("NPM run start is completed in folder {$tempDir} the application is running on port $port");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, $process->getOutput());
|
||||||
|
$process->wait();
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to NPM run start in folder {$tempDir} due to error " . $process->getOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port");
|
||||||
|
Process::fromShellCommandline("npx kill-port $port")->run();
|
||||||
|
}
|
||||||
|
} catch (ProcessTimedOutException $th) {
|
||||||
|
$process->stop();
|
||||||
|
Log::error("Failed to NPM run start in folder {$tempDir} due to timeout " . $process->getOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port due to timeout");
|
||||||
|
Process::fromShellCommandline("npx kill-port $port")->run();
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error("Failed to NPM run start in folder {$tempDir}" . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port");
|
||||||
|
Process::fromShellCommandline("npx kill-port $port")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an available port number.
|
||||||
|
*
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
private function getAvailablePort(): ?int
|
||||||
|
{
|
||||||
|
$minPort = 9000;
|
||||||
|
$maxPort = 9999;
|
||||||
|
for ($port = $minPort; $port <= $maxPort; $port++) {
|
||||||
|
$fp = @fsockopen('localhost', $port, $errno, $errstr, 1);
|
||||||
|
if (!$fp && Submission::where('port', $port)->doesntExist()) {
|
||||||
|
return $port;
|
||||||
|
} else {
|
||||||
|
if (is_resource($fp)) {
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the submission status and result of a specific step.
|
||||||
|
*
|
||||||
|
* @param Submission $submission
|
||||||
|
* @param string $status
|
||||||
|
* @param string $output
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$NPM_RUN_START;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
93
app/Jobs/NodeJS/NpmRunTests.php
Normal file
93
app/Jobs/NodeJS/NpmRunTests.php
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class NpmRunTests implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("NPM running tests in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM running tests");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
$pass_all = [];
|
||||||
|
$commands = $this->command;
|
||||||
|
foreach ($commands as $key => $command) {
|
||||||
|
$command_string = implode(" ", $command);
|
||||||
|
Log::info("Running {$command_string} in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$PROCESSING, "Running");
|
||||||
|
usleep(100000);
|
||||||
|
$process = new Process($command, $this->tempDir, null, null, 120);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
$pass_all[$key] = true;
|
||||||
|
Log::info("{$command_string} in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$COMPLETED, "Completed");
|
||||||
|
} else {
|
||||||
|
$pass_all[$key] = false;
|
||||||
|
Log::error("Failed to NPM run test {$command_string} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$FAILED, $process->getErrorOutput());
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (in_array(false, $pass_all) == false) {
|
||||||
|
Log::info("NPM ran tests in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "NPM tested");
|
||||||
|
} else {
|
||||||
|
Log::info("NPM failed to run tests in folder {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to run NPM tests");
|
||||||
|
if ($submission->port) Process::fromShellCommandline("npx kill-port $submission->port")->run();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error("Failed to NPM run tests in folder {$this->tempDir} " . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to NPM running tests");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionTestsResultsStatus($testName, Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$NPM_RUN_TESTS;
|
||||||
|
$submission->updateOneTestResult($stepName, $testName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$NPM_RUN_TESTS;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
app/Jobs/NodeJS/ReplacePackageJson.php
Normal file
74
app/Jobs/NodeJS/ReplacePackageJson.php
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class ReplacePackageJson implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $packageJson;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $packageJson, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->packageJson = $packageJson;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Replacing package.json to {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Replacing package.json");
|
||||||
|
try {
|
||||||
|
// processing
|
||||||
|
$process = new Process($this->command);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
// completed
|
||||||
|
Log::info("Replaced package.json to {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Replaced package.json");
|
||||||
|
} else {
|
||||||
|
// failed
|
||||||
|
Log::error("Failed to replace package.json to {$this->tempDir} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to replace package.json");
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
// failed
|
||||||
|
Log::error("Failed to replace package.json to {$this->tempDir} " . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to replace package.json");
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$REPLACE_PACKAGE_JSON;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
app/Jobs/NodeJS/UnzipZipFiles.php
Normal file
73
app/Jobs/NodeJS/UnzipZipFiles.php
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\NodeJS;
|
||||||
|
|
||||||
|
use App\Models\NodeJS\ExecutionStep;
|
||||||
|
use App\Models\NodeJS\Submission;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class UnzipZipFiles implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $submission;
|
||||||
|
public $zipFileDir;
|
||||||
|
public $tempDir;
|
||||||
|
public $command;
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($submission, $zipFileDir, $tempDir, $command)
|
||||||
|
{
|
||||||
|
$this->submission = $submission;
|
||||||
|
$this->zipFileDir = $zipFileDir;
|
||||||
|
$this->tempDir = $tempDir;
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$submission = $this->submission;
|
||||||
|
Log::info("Unzipping {$this->zipFileDir} into {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Unzipping submitted folder");
|
||||||
|
try {
|
||||||
|
if (!file_exists($this->tempDir)) mkdir($this->tempDir, 0777, true);
|
||||||
|
// processing
|
||||||
|
$process = new Process($this->command);
|
||||||
|
$process->start();
|
||||||
|
$process_pid = $process->getPid();
|
||||||
|
$process->wait();
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
Log::info("Unzipped {$this->zipFileDir} into {$this->tempDir}");
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Unzipped submitted folder");
|
||||||
|
} else {
|
||||||
|
Log::error("Failed to unzip {$this->zipFileDir} " . $process->getErrorOutput());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to unzip submitted folder");
|
||||||
|
Process::fromShellCommandline('kill ' . $process_pid)->run();
|
||||||
|
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
// failed
|
||||||
|
Log::error("Failed to unzip {$this->zipFileDir} " . $th->getMessage());
|
||||||
|
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed tp unzip submitted folder");
|
||||||
|
// Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
|
||||||
|
{
|
||||||
|
$stepName = ExecutionStep::$UNZIP_ZIP_FILES;
|
||||||
|
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
|
||||||
|
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Models/Android/Enrollment.php
Normal file
19
app/Models/Android/Enrollment.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Enrollment extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_enrollment";
|
||||||
|
protected $fillable = ["android_topic_id", "user_id", "status"];
|
||||||
|
|
||||||
|
|
||||||
|
public function user() : BelongsTo {
|
||||||
|
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Models/Android/Submits.php
Normal file
34
app/Models/Android/Submits.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
use App\Models\Android\Topic;
|
||||||
|
use App\Models\Android\Task;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Android\SubmitsTestCase;
|
||||||
|
|
||||||
|
class Submits extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_submits";
|
||||||
|
protected $fillable = ["user_id", "android_topic_id", "android_task_id", "duration", "upload", "comment", "validator"];
|
||||||
|
|
||||||
|
|
||||||
|
public function topic() : BelongsTo {
|
||||||
|
|
||||||
|
return $this->belongsTo(Topic::class, 'android_topic_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function task() : BelongsTo {
|
||||||
|
|
||||||
|
return $this->belongsTo(Task::class, 'android_task_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user() : BelongsTo {
|
||||||
|
|
||||||
|
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Models/Android/SubmitsFinalSubmission.php
Normal file
11
app/Models/Android/SubmitsFinalSubmission.php
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class SubmitsFinalSubmission extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_submits_submission";
|
||||||
|
protected $fillable = ["user_id", "android_topic_id", "tipe", "userfile"];
|
||||||
|
}
|
||||||
18
app/Models/Android/SubmitsTestCase.php
Normal file
18
app/Models/Android/SubmitsTestCase.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use App\Models\Android\Testcase;
|
||||||
|
|
||||||
|
class SubmitsTestCase extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_submits_testcase";
|
||||||
|
protected $fillable = ["user_id", "android_submit_id", "android_testcase_id", "status", "status_waiting"];
|
||||||
|
|
||||||
|
public function android_testcase() : BelongsTo{
|
||||||
|
|
||||||
|
return $this->belongsTo( Testcase::class, 'android_testcase_id', 'id' );
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Models/Android/Task.php
Normal file
26
app/Models/Android/Task.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Task extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_task";
|
||||||
|
protected $fillable = ['android_topic_id', 'task_no', 'task_name', 'caption', 'material', 'tipe'];
|
||||||
|
|
||||||
|
public $timestamp = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// create and update at menggunakan timestamp dari server (epoch)
|
||||||
|
public function getCreatedAtAttribute( $value ) {
|
||||||
|
|
||||||
|
return Carbon::parse($value)->timestamp;
|
||||||
|
}
|
||||||
|
public function getUpdatedAtAttribute( $value ) {
|
||||||
|
|
||||||
|
return Carbon::parse($value)->timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Models/Android/Task_waiting.php
Normal file
11
app/Models/Android/Task_waiting.php
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Task_waiting extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_task_waiting";
|
||||||
|
protected $fillable = ["user_id", "android_topic_id", "android_task_id"];
|
||||||
|
}
|
||||||
13
app/Models/Android/Testcase.php
Normal file
13
app/Models/Android/Testcase.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Testcase extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_testcase";
|
||||||
|
protected $fillable = ["task_id", "case", "score"];
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
}
|
||||||
26
app/Models/Android/Topic.php
Normal file
26
app/Models/Android/Topic.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Android;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Topic extends Model
|
||||||
|
{
|
||||||
|
protected $table = "android_topics";
|
||||||
|
protected $fillable = ['title', 'description', 'folder_path', 'picturePath', 'status'];
|
||||||
|
|
||||||
|
public $timestamp = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// create and update at menggunakan timestamp dari server (epoch)
|
||||||
|
public function getCreatedAtAttribute( $value ) {
|
||||||
|
|
||||||
|
return Carbon::parse($value)->timestamp;
|
||||||
|
}
|
||||||
|
public function getUpdatedAtAttribute( $value ) {
|
||||||
|
|
||||||
|
return Carbon::parse($value)->timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/Models/NodeJS/ExecutionStep.php
Normal file
49
app/Models/NodeJS/ExecutionStep.php
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ExecutionStep extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'commands',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'commands' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
static $CLONE_REPOSITORY = 'Clone Repository';
|
||||||
|
static $UNZIP_ZIP_FILES = 'Unzip ZIP Files';
|
||||||
|
static $REMOVE_ZIP_FILES = 'Remove ZIP Files';
|
||||||
|
static $EXAMINE_FOLDER_STRUCTURE = 'Examine Folder Structure';
|
||||||
|
static $ADD_ENV_FILE = 'Add .env File';
|
||||||
|
static $REPLACE_PACKAGE_JSON = 'Replace package.json';
|
||||||
|
static $COPY_TESTS_FOLDER = "Copy 'tests' Folder";
|
||||||
|
static $NPM_INSTALL = 'NPM Install';
|
||||||
|
static $NPM_RUN_START = 'NPM Run Start';
|
||||||
|
static $NPM_RUN_TESTS = 'NPM Run Tests';
|
||||||
|
static $DELETE_TEMP_DIRECTORY = 'Delete Temp Directory';
|
||||||
|
|
||||||
|
public function getCommandsAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCommandsAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['commands'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function projectExecutionSteps()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProjectExecutionStep::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/Models/NodeJS/Project.php
Normal file
57
app/Models/NodeJS/Project.php
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
|
|
||||||
|
class Project extends Model implements HasMedia
|
||||||
|
{
|
||||||
|
use HasFactory, InteractsWithMedia;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'tech_stack',
|
||||||
|
'github_url',
|
||||||
|
'image',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'tech_stack' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function defaultFileStructure(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(ProjectsDefaultFileStructure::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submissions(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Submission::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function projectExecutionSteps(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProjectExecutionStep::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTechStackAttribute($value): array
|
||||||
|
{
|
||||||
|
return json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTechStackAttribute($value): void
|
||||||
|
{
|
||||||
|
$this->attributes['tech_stack'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageAttribute(): string
|
||||||
|
{
|
||||||
|
return $this->getFirstMediaUrl('project_images');
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Models/NodeJS/ProjectExecutionStep.php
Normal file
44
app/Models/NodeJS/ProjectExecutionStep.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ProjectExecutionStep extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'execution_step_id',
|
||||||
|
'order',
|
||||||
|
'variables',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'variables' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getVariablesAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setVariablesAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['variables'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executionStep()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ExecutionStep::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
app/Models/NodeJS/ProjectsDefaultFileStructure.php
Normal file
61
app/Models/NodeJS/ProjectsDefaultFileStructure.php
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ProjectsDefaultFileStructure extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'structure',
|
||||||
|
'excluded',
|
||||||
|
'replacements',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'structure' => 'array',
|
||||||
|
'excluded' => 'array',
|
||||||
|
'replacements' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStructureAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStructureAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['structure'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExcludedAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExcludedAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['excluded'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReplacementsAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setReplacementsAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['replacements'] = json_encode($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
312
app/Models/NodeJS/Submission.php
Normal file
312
app/Models/NodeJS/Submission.php
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
|
|
||||||
|
class Submission extends Model implements HasMedia
|
||||||
|
{
|
||||||
|
use HasFactory, InteractsWithMedia;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
static $types = ['file', 'url'];
|
||||||
|
static $statues = ['pending', 'processing', 'completed', 'failed'];
|
||||||
|
static $FILE = 'file';
|
||||||
|
static $URL = 'url';
|
||||||
|
static $PENDING = 'pending';
|
||||||
|
static $PROCESSING = 'processing';
|
||||||
|
static $COMPLETED = 'completed';
|
||||||
|
static $FAILED = 'failed';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'project_id',
|
||||||
|
'type',
|
||||||
|
'path',
|
||||||
|
'status',
|
||||||
|
'results',
|
||||||
|
'attempts',
|
||||||
|
'port',
|
||||||
|
'start',
|
||||||
|
'end',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'results' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResultsAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResultsAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['results'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileAttribute()
|
||||||
|
{
|
||||||
|
return $this->getFirstMediaUrl('public_submissions_files');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isGithubUrl()
|
||||||
|
{
|
||||||
|
return $this->type == self::$URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function history()
|
||||||
|
{
|
||||||
|
return $this->hasMany(SubmissionHistory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExecutionSteps()
|
||||||
|
{
|
||||||
|
if ($this->isGithubUrl()) {
|
||||||
|
$steps = $this->project->projectExecutionSteps->filter(function ($step) {
|
||||||
|
return $step->executionStep->name != 'Unzip ZIP Files' && $step->executionStep->name != 'Remove ZIP Files';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$steps = $this->project->projectExecutionSteps->filter(function ($step) {
|
||||||
|
return $step->executionStep->name != 'Clone Repository';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// order the steps by their order
|
||||||
|
$steps = $steps->sortBy('order');
|
||||||
|
return $steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initializeResults()
|
||||||
|
{
|
||||||
|
$results = [];
|
||||||
|
$steps = $this->getExecutionSteps();
|
||||||
|
foreach ($steps as $step) {
|
||||||
|
if ($step->executionStep->name == ExecutionStep::$NPM_RUN_TESTS) {
|
||||||
|
$tests = $this->project->projectExecutionSteps->where('execution_step_id', $step->executionStep->id)->first()->variables;
|
||||||
|
$testResults = [];
|
||||||
|
$order = 0;
|
||||||
|
foreach ($tests as $testCommandValue) {
|
||||||
|
$order = $order + 1;
|
||||||
|
$command = implode(" ", $step->executionStep->commands);
|
||||||
|
$key = explode("=", $testCommandValue)[0];
|
||||||
|
$value = explode("=", $testCommandValue)[1];
|
||||||
|
$testName = str_replace($key, $value, $command);
|
||||||
|
$testResults[$testName] = [
|
||||||
|
'status' => self::$PENDING,
|
||||||
|
'output' => '',
|
||||||
|
'order' => $order,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$results[$step->executionStep->name] = [
|
||||||
|
'stepID' => $step->id,
|
||||||
|
'status' => self::$PENDING,
|
||||||
|
'order' => $step->order,
|
||||||
|
'output' => '',
|
||||||
|
'testResults' => $testResults,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$results[$step->executionStep->name] = [
|
||||||
|
'stepID' => $step->id,
|
||||||
|
'status' => self::$PENDING,
|
||||||
|
'order' => $step->order,
|
||||||
|
'output' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->updateResults($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increaseAttempts()
|
||||||
|
{
|
||||||
|
$this->attempts = $this->attempts + 1;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateStatus($status)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
if ($status == self::$FAILED || self::$COMPLETED) $this->end = now();
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restartTime()
|
||||||
|
{
|
||||||
|
$this->start = now();
|
||||||
|
$this->end = null;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateOneResult($step_name, $status, $output)
|
||||||
|
{
|
||||||
|
$results = $this->results;
|
||||||
|
$results->$step_name->status = $status;
|
||||||
|
$results->$step_name->output = $output;
|
||||||
|
$this->updateResults($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateOneTestResult($step_name, $test_name, $status, $output)
|
||||||
|
{
|
||||||
|
$results = $this->results;
|
||||||
|
$results->$step_name->testResults->$test_name->status = $status;
|
||||||
|
$results->$step_name->testResults->$test_name->output = $output;
|
||||||
|
$this->updateResults($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateResults($results)
|
||||||
|
{
|
||||||
|
$this->results = $results;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePort($port)
|
||||||
|
{
|
||||||
|
$this->port = $port;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentExecutionStep($step_id = null)
|
||||||
|
{
|
||||||
|
$steps = $this->getExecutionSteps();
|
||||||
|
if ($step_id) {
|
||||||
|
$current_step = $steps->first(function ($step) use ($step_id) {
|
||||||
|
return $step->id == $step_id;
|
||||||
|
});
|
||||||
|
return $current_step;
|
||||||
|
} else {
|
||||||
|
$results = $this->results;
|
||||||
|
$current_step = null;
|
||||||
|
if (!$results) {
|
||||||
|
return $current_step;
|
||||||
|
}
|
||||||
|
foreach ($steps as $step) {
|
||||||
|
if ($results->{$step->executionStep->name}?->status == self::$PROCESSING || $results->{$step->executionStep->name}?->status == self::$PENDING) {
|
||||||
|
$current_step = $step;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$current_step) {
|
||||||
|
$have_failed_steps = false;
|
||||||
|
foreach ($steps as $step) {
|
||||||
|
if ($results->{$step->executionStep->name}?->status == self::$FAILED) {
|
||||||
|
$have_failed_steps = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($have_failed_steps) {
|
||||||
|
$this->updateStatus(self::$FAILED);
|
||||||
|
} else {
|
||||||
|
$this->updateStatus(self::$COMPLETED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $current_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextExecutionStep($step_id = null)
|
||||||
|
{
|
||||||
|
if ($step_id == null) return null;
|
||||||
|
$next_step = null;
|
||||||
|
$steps = $this->getExecutionSteps();
|
||||||
|
$current_step = $this->getCurrentExecutionStep($step_id);
|
||||||
|
if ($current_step) {
|
||||||
|
$current_step_index = $steps->search(function ($step) use ($current_step) {
|
||||||
|
return $step->id == $current_step->id;
|
||||||
|
});
|
||||||
|
if ($current_step_index < $steps->count()) {
|
||||||
|
$next_step = $steps[$current_step_index + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $next_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalSteps()
|
||||||
|
{
|
||||||
|
return $this->getExecutionSteps()->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalCompletedSteps()
|
||||||
|
{
|
||||||
|
$results = $this->results;
|
||||||
|
$completed_steps = 0;
|
||||||
|
if ($results != null) {
|
||||||
|
foreach ($results as $result) {
|
||||||
|
if ($result->status == self::$COMPLETED || $result->status == self::$FAILED) {
|
||||||
|
$completed_steps++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $completed_steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restartAfterNpmInstall()
|
||||||
|
{
|
||||||
|
$step = ProjectExecutionStep::where('project_id', $this->project_id)->where('execution_step_id', ExecutionStep::where('name', ExecutionStep::$NPM_INSTALL)->first()->id)->first();
|
||||||
|
$next_step = $this->getNextExecutionStep($step->id ?? null);
|
||||||
|
// update results to pending
|
||||||
|
while ($next_step) {
|
||||||
|
if ($next_step->executionStep->name == ExecutionStep::$NPM_RUN_TESTS) {
|
||||||
|
$tests = $this->project->projectExecutionSteps->where('execution_step_id', $next_step->executionStep->id)->first()->variables;
|
||||||
|
foreach ($tests as $testCommandValue) {
|
||||||
|
$command = implode(" ", $next_step->executionStep->commands);
|
||||||
|
$key = explode("=", $testCommandValue)[0];
|
||||||
|
$value = explode("=", $testCommandValue)[1];
|
||||||
|
$testName = str_replace($key, $value, $command);
|
||||||
|
$this->updateOneTestResult($next_step->executionStep->name, $testName, Submission::$PENDING, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->updateOneResult($next_step->executionStep->name, Submission::$PENDING, " ");
|
||||||
|
$next_step = $this->getNextExecutionStep($next_step->id ?? null);
|
||||||
|
}
|
||||||
|
$this->updateStatus(Submission::$PROCESSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalAttemptsCount()
|
||||||
|
{
|
||||||
|
return $this->history->count() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restart()
|
||||||
|
{
|
||||||
|
$this->updateStatus(Submission::$PENDING);
|
||||||
|
$this->increaseAttempts();
|
||||||
|
$this->updatePort(null);
|
||||||
|
$this->restartTime();
|
||||||
|
$this->initializeResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createHistory($description)
|
||||||
|
{
|
||||||
|
// use the method attach to attach the history to the submission
|
||||||
|
$history = $this->history()->create([
|
||||||
|
'user_id' => $this->user_id,
|
||||||
|
'project_id' => $this->project_id,
|
||||||
|
'type' => $this->type,
|
||||||
|
'path' => $this->path,
|
||||||
|
'status' => $this->status,
|
||||||
|
'results' => $this->results,
|
||||||
|
'attempts' => $this->attempts,
|
||||||
|
'port' => $this->port,
|
||||||
|
'start' => $this->start,
|
||||||
|
'end' => $this->end,
|
||||||
|
'description' => $description,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $history;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
app/Models/NodeJS/SubmissionHistory.php
Normal file
110
app/Models/NodeJS/SubmissionHistory.php
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class SubmissionHistory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
static $types = ['file', 'url'];
|
||||||
|
static $statues = ['pending', 'processing', 'completed', 'failed'];
|
||||||
|
static $FILE = 'file';
|
||||||
|
static $URL = 'url';
|
||||||
|
static $PENDING = 'pending';
|
||||||
|
static $PROCESSING = 'processing';
|
||||||
|
static $COMPLETED = 'completed';
|
||||||
|
static $FAILED = 'failed';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'project_id',
|
||||||
|
'type',
|
||||||
|
'path',
|
||||||
|
'status',
|
||||||
|
'results',
|
||||||
|
'attempts',
|
||||||
|
'port',
|
||||||
|
'start',
|
||||||
|
'end',
|
||||||
|
'description'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'results' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResultsAttribute($value)
|
||||||
|
{
|
||||||
|
return json_decode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResultsAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['results'] = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileAttribute()
|
||||||
|
{
|
||||||
|
return $this->getFirstMediaUrl('public_submissions_files');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isGithubUrl()
|
||||||
|
{
|
||||||
|
return $this->type == self::$URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submission()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SubmissionStatus::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExecutionSteps()
|
||||||
|
{
|
||||||
|
if ($this->isGithubUrl()) {
|
||||||
|
$steps = $this->project->projectExecutionSteps->filter(function ($step) {
|
||||||
|
return $step->executionStep->name != 'Unzip ZIP Files' && $step->executionStep->name != 'Remove ZIP Files';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$steps = $this->project->projectExecutionSteps->filter(function ($step) {
|
||||||
|
return $step->executionStep->name != 'Clone Repository';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// order the steps by their order
|
||||||
|
$steps = $steps->sortBy('order');
|
||||||
|
return $steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalSteps()
|
||||||
|
{
|
||||||
|
return $this->getExecutionSteps()->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalCompletedSteps()
|
||||||
|
{
|
||||||
|
$results = $this->results;
|
||||||
|
$completed_steps = 0;
|
||||||
|
if ($results != null) {
|
||||||
|
foreach ($results as $result) {
|
||||||
|
if ($result->status == self::$COMPLETED) {
|
||||||
|
$completed_steps++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $completed_steps;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Models/NodeJS/TemporaryFile.php
Normal file
18
app/Models/NodeJS/TemporaryFile.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\NodeJS;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TemporaryFile extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $connection = 'nodejsDB';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'folder_path',
|
||||||
|
'file_name',
|
||||||
|
];
|
||||||
|
}
|
||||||
45
app/Models/User.php
Normal file
45
app/Models/User.php
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
use HasApiTokens, HasFactory, Notifiable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'email_verified_at' => 'datetime',
|
||||||
|
'password' => 'hashed',
|
||||||
|
];
|
||||||
|
}
|
||||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Providers/AuthServiceProvider.php
Normal file
26
app/Providers/AuthServiceProvider.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
// use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
|
class AuthServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The model to policy mappings for the application.
|
||||||
|
*
|
||||||
|
* @var array<class-string, class-string>
|
||||||
|
*/
|
||||||
|
protected $policies = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register any authentication / authorization services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Providers/BroadcastServiceProvider.php
Normal file
19
app/Providers/BroadcastServiceProvider.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class BroadcastServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
Broadcast::routes();
|
||||||
|
|
||||||
|
require base_path('routes/channels.php');
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Providers/EventServiceProvider.php
Normal file
38
app/Providers/EventServiceProvider.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Events\Registered;
|
||||||
|
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||||
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
|
||||||
|
class EventServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The event to listener mappings for the application.
|
||||||
|
*
|
||||||
|
* @var array<class-string, array<int, class-string>>
|
||||||
|
*/
|
||||||
|
protected $listen = [
|
||||||
|
Registered::class => [
|
||||||
|
SendEmailVerificationNotification::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register any events for your application.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if events and listeners should be automatically discovered.
|
||||||
|
*/
|
||||||
|
public function shouldDiscoverEvents(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Providers/RouteServiceProvider.php
Normal file
40
app/Providers/RouteServiceProvider.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
class RouteServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The path to your application's "home" route.
|
||||||
|
*
|
||||||
|
* Typically, users are redirected here after authentication.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public const HOME = '/home';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define your route model bindings, pattern filters, and other route configuration.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
RateLimiter::for('api', function (Request $request) {
|
||||||
|
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->routes(function () {
|
||||||
|
Route::middleware('api')
|
||||||
|
->prefix('api')
|
||||||
|
->group(base_path('routes/api.php'));
|
||||||
|
|
||||||
|
Route::middleware('web')
|
||||||
|
->group(base_path('routes/web.php'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/AppLayout.php
Normal file
17
app/View/Components/AppLayout.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class AppLayout extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.layouts.app');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/ApplicationLogo.php
Normal file
17
app/View/Components/ApplicationLogo.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class ApplicationLogo extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.application-logo');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/Dropdown.php
Normal file
17
app/View/Components/Dropdown.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class Dropdown extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.dropdown');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/DropdownLink.php
Normal file
17
app/View/Components/DropdownLink.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class DropdownLink extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.dropdown-link');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/FailedIcon.php
Normal file
17
app/View/Components/FailedIcon.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class FailedIcon extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.failed-icon');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/GuestLayout.php
Normal file
17
app/View/Components/GuestLayout.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class GuestLayout extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.layouts.guest');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/InputError.php
Normal file
17
app/View/Components/InputError.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class InputError extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.input-error');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/InputLabel.php
Normal file
17
app/View/Components/InputLabel.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class InputLabel extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.input-label');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/NavLink.php
Normal file
17
app/View/Components/NavLink.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class NavLink extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.nav-link');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/NotFound.php
Normal file
17
app/View/Components/NotFound.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class NotFound extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.not-found');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/PendingIcon.php
Normal file
17
app/View/Components/PendingIcon.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class PendingIcon extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.pending-icon');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/PrimaryButton.php
Normal file
17
app/View/Components/PrimaryButton.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class PrimaryButton extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.primary-button');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/RefreshIcon.php
Normal file
17
app/View/Components/RefreshIcon.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class RefreshIcon extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.refresh-icon');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/ResponsiveNavLink.php
Normal file
17
app/View/Components/ResponsiveNavLink.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class ResponsiveNavLink extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.responsive-nav-link');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/SuccessIcon.php
Normal file
17
app/View/Components/SuccessIcon.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class SuccessIcon extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.success-icon');
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/View/Components/TextInput.php
Normal file
17
app/View/Components/TextInput.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class TextInput extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('nodejs.components.text-input');
|
||||||
|
}
|
||||||
|
}
|
||||||
53
artisan
Normal file
53
artisan
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Register The Auto Loader
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Composer provides a convenient, automatically generated class loader
|
||||||
|
| for our application. We just need to utilize it! We'll require it
|
||||||
|
| into the script here so that we do not have to worry about the
|
||||||
|
| loading of any of our classes manually. It's great to relax.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Run The Artisan Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When we run the console application, the current CLI command will be
|
||||||
|
| executed in this console and the response sent back to a terminal
|
||||||
|
| or another output device for the developers. Here goes nothing!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||||
|
|
||||||
|
$status = $kernel->handle(
|
||||||
|
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||||
|
new Symfony\Component\Console\Output\ConsoleOutput
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Shutdown The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Once Artisan has finished running, we will fire off the shutdown events
|
||||||
|
| so that any final work may be done by the application before we shut
|
||||||
|
| down the process. This is the last thing to happen to the request.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$kernel->terminate($input, $status);
|
||||||
|
|
||||||
|
exit($status);
|
||||||
55
bootstrap/app.php
Normal file
55
bootstrap/app.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Create The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The first thing we will do is create a new Laravel application instance
|
||||||
|
| which serves as the "glue" for all the components of Laravel, and is
|
||||||
|
| the IoC container for the system binding all of the various parts.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$app = new Illuminate\Foundation\Application(
|
||||||
|
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Bind Important Interfaces
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, we need to bind some important interfaces into the container so
|
||||||
|
| we will be able to resolve them when needed. The kernels serve the
|
||||||
|
| incoming requests to this application from both the web and CLI.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Http\Kernel::class,
|
||||||
|
App\Http\Kernel::class
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Console\Kernel::class,
|
||||||
|
App\Console\Kernel::class
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->singleton(
|
||||||
|
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||||
|
App\Exceptions\Handler::class
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Return The Application
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This script returns the application instance. The instance is given to
|
||||||
|
| the calling script so we can separate the building of the instances
|
||||||
|
| from the actual running of the application and sending responses.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
return $app;
|
||||||
2
bootstrap/cache/.gitignore
vendored
Normal file
2
bootstrap/cache/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
69
composer.json
Normal file
69
composer.json
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"name": "laravel/laravel",
|
||||||
|
"type": "project",
|
||||||
|
"description": "The skeleton application for the Laravel framework.",
|
||||||
|
"keywords": ["laravel", "framework"],
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
|
"laravel/framework": "^10.10",
|
||||||
|
"laravel/sanctum": "^3.2",
|
||||||
|
"laravel/tinker": "^2.8",
|
||||||
|
"spatie/laravel-medialibrary": "^10.9",
|
||||||
|
"yajra/laravel-datatables-oracle": "^10.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"fakerphp/faker": "^1.9.1",
|
||||||
|
"laravel/pint": "^1.0",
|
||||||
|
"laravel/sail": "^1.18",
|
||||||
|
"mockery/mockery": "^1.4.4",
|
||||||
|
"nunomaduro/collision": "^7.0",
|
||||||
|
"phpunit/phpunit": "^10.1",
|
||||||
|
"spatie/laravel-ignition": "^2.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "app/",
|
||||||
|
"Database\\Factories\\": "database/factories/",
|
||||||
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-autoload-dump": [
|
||||||
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
|
"@php artisan package:discover --ansi"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||||
|
],
|
||||||
|
"post-root-package-install": [
|
||||||
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
|
],
|
||||||
|
"post-create-project-cmd": [
|
||||||
|
"@php artisan key:generate --ansi"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"dont-discover": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"disable-tls": true,
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": "dist",
|
||||||
|
"sort-packages": true,
|
||||||
|
"allow-plugins": {
|
||||||
|
"pestphp/pest-plugin": true,
|
||||||
|
"php-http/discovery": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
||||||
8665
composer.lock
generated
Normal file
8665
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
191
config/app.php
Normal file
191
config/app.php
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Facade;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value is the name of your application. This value is used when the
|
||||||
|
| framework needs to place the application's name in a notification or
|
||||||
|
| any other location as required by the application or its packages.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Environment
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value determines the "environment" your application is currently
|
||||||
|
| running in. This may determine how you prefer to configure various
|
||||||
|
| services the application utilizes. Set this in your ".env" file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'env' => env('APP_ENV', 'production'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Debug Mode
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When your application is in debug mode, detailed error messages with
|
||||||
|
| stack traces will be shown on every error that occurs within your
|
||||||
|
| application. If disabled, a simple generic error page is shown.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'debug' => (bool) env('APP_DEBUG', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application URL
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This URL is used by the console to properly generate URLs when using
|
||||||
|
| the Artisan command line tool. You should set this to the root of
|
||||||
|
| your application so that it is used when running Artisan tasks.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
|
||||||
|
'asset_url' => env('ASSET_URL'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Timezone
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the default timezone for your application, which
|
||||||
|
| will be used by the PHP date and date-time functions. We have gone
|
||||||
|
| ahead and set this to a sensible default for you out of the box.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'timezone' => 'UTC',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Locale Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The application locale determines the default locale that will be used
|
||||||
|
| by the translation service provider. You are free to set this value
|
||||||
|
| to any of the locales which will be supported by the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'locale' => 'en',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Fallback Locale
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The fallback locale determines the locale to use when the current one
|
||||||
|
| is not available. You may change the value to correspond to any of
|
||||||
|
| the language folders that are provided through your application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'fallback_locale' => 'en',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Faker Locale
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This locale will be used by the Faker PHP library when generating fake
|
||||||
|
| data for your database seeds. For example, this will be used to get
|
||||||
|
| localized telephone numbers, street address information and more.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'faker_locale' => 'en_US',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Encryption Key
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This key is used by the Illuminate encrypter service and should be set
|
||||||
|
| to a random, 32 character string, otherwise these encrypted strings
|
||||||
|
| will not be safe. Please do this before deploying an application!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'key' => env('APP_KEY'),
|
||||||
|
|
||||||
|
'cipher' => 'AES-256-CBC',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Maintenance Mode Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration options determine the driver used to determine and
|
||||||
|
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||||
|
| allow maintenance mode to be controlled across multiple machines.
|
||||||
|
|
|
||||||
|
| Supported drivers: "file", "cache"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'maintenance' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
// 'store' => 'redis',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Autoloaded Service Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The service providers listed here will be automatically loaded on the
|
||||||
|
| request to your application. Feel free to add your own services to
|
||||||
|
| this array to grant expanded functionality to your applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => ServiceProvider::defaultProviders()->merge([
|
||||||
|
/*
|
||||||
|
* Package Service Providers...
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Application Service Providers...
|
||||||
|
*/
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
App\Providers\AuthServiceProvider::class,
|
||||||
|
// App\Providers\BroadcastServiceProvider::class,
|
||||||
|
App\Providers\EventServiceProvider::class,
|
||||||
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
|
||||||
|
Yajra\DataTables\DataTablesServiceProvider::class,
|
||||||
|
])->toArray(),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Class Aliases
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array of class aliases will be registered when this application
|
||||||
|
| is started. However, feel free to register as many as you wish as
|
||||||
|
| the aliases are "lazy" loaded so they don't hinder performance.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'aliases' => Facade::defaultAliases()->merge([
|
||||||
|
// 'Example' => App\Facades\Example::class,
|
||||||
|
'DataTables' => Yajra\DataTables\Facades\DataTables::class,
|
||||||
|
])->toArray(),
|
||||||
|
|
||||||
|
];
|
||||||
115
config/auth.php
Normal file
115
config/auth.php
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Defaults
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default authentication "guard" and password
|
||||||
|
| reset options for your application. You may change these defaults
|
||||||
|
| as required, but they're a perfect start for most applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'defaults' => [
|
||||||
|
'guard' => 'web',
|
||||||
|
'passwords' => 'users',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, you may define every authentication guard for your application.
|
||||||
|
| Of course, a great default configuration has been defined for you
|
||||||
|
| here which uses session storage and the Eloquent user provider.
|
||||||
|
|
|
||||||
|
| All authentication drivers have a user provider. This defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| mechanisms used by this application to persist your user's data.
|
||||||
|
|
|
||||||
|
| Supported: "session"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guards' => [
|
||||||
|
'web' => [
|
||||||
|
'driver' => 'session',
|
||||||
|
'provider' => 'users',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| User Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| All authentication drivers have a user provider. This defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| mechanisms used by this application to persist your user's data.
|
||||||
|
|
|
||||||
|
| If you have multiple user tables or models you may configure multiple
|
||||||
|
| sources which represent each model / table. These sources may then
|
||||||
|
| be assigned to any extra authentication guards you have defined.
|
||||||
|
|
|
||||||
|
| Supported: "database", "eloquent"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => [
|
||||||
|
'users' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => App\Models\User::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
// 'users' => [
|
||||||
|
// 'driver' => 'database',
|
||||||
|
// 'table' => 'users',
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Resetting Passwords
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You may specify multiple password reset configurations if you have more
|
||||||
|
| than one user table or model in the application and you want to have
|
||||||
|
| separate password reset settings based on the specific user types.
|
||||||
|
|
|
||||||
|
| The expiry time is the number of minutes that each reset token will be
|
||||||
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
||||||
|
| The throttle setting is the number of seconds a user must wait before
|
||||||
|
| generating more password reset tokens. This prevents the user from
|
||||||
|
| quickly generating a very large amount of password reset tokens.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'passwords' => [
|
||||||
|
'users' => [
|
||||||
|
'provider' => 'users',
|
||||||
|
'table' => 'password_reset_tokens',
|
||||||
|
'expire' => 60,
|
||||||
|
'throttle' => 60,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Confirmation Timeout
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define the amount of seconds before a password confirmation
|
||||||
|
| times out and the user is prompted to re-enter their password via the
|
||||||
|
| confirmation screen. By default, the timeout lasts for three hours.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'password_timeout' => 10800,
|
||||||
|
|
||||||
|
];
|
||||||
71
config/broadcasting.php
Normal file
71
config/broadcasting.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Broadcaster
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default broadcaster that will be used by the
|
||||||
|
| framework when an event needs to be broadcast. You may set this to
|
||||||
|
| any of the connections defined in the "connections" array below.
|
||||||
|
|
|
||||||
|
| Supported: "pusher", "ably", "redis", "log", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('BROADCAST_DRIVER', 'null'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Broadcast Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the broadcast connections that will be used
|
||||||
|
| to broadcast events to other systems or over websockets. Samples of
|
||||||
|
| each available type of connection are provided inside this array.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'pusher' => [
|
||||||
|
'driver' => 'pusher',
|
||||||
|
'key' => env('PUSHER_APP_KEY'),
|
||||||
|
'secret' => env('PUSHER_APP_SECRET'),
|
||||||
|
'app_id' => env('PUSHER_APP_ID'),
|
||||||
|
'options' => [
|
||||||
|
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||||
|
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||||
|
'port' => env('PUSHER_PORT', 443),
|
||||||
|
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||||
|
'encrypted' => true,
|
||||||
|
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||||
|
],
|
||||||
|
'client_options' => [
|
||||||
|
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'ably' => [
|
||||||
|
'driver' => 'ably',
|
||||||
|
'key' => env('ABLY_KEY'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => 'default',
|
||||||
|
],
|
||||||
|
|
||||||
|
'log' => [
|
||||||
|
'driver' => 'log',
|
||||||
|
],
|
||||||
|
|
||||||
|
'null' => [
|
||||||
|
'driver' => 'null',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
111
config/cache.php
Normal file
111
config/cache.php
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Cache Store
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default cache connection that gets used while
|
||||||
|
| using this caching library. This connection is used when another is
|
||||||
|
| not explicitly specified when executing a given caching function.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('CACHE_DRIVER', 'file'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Stores
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the cache "stores" for your application as
|
||||||
|
| well as their drivers. You may even define multiple stores for the
|
||||||
|
| same cache driver to group types of items stored in your caches.
|
||||||
|
|
|
||||||
|
| Supported drivers: "apc", "array", "database", "file",
|
||||||
|
| "memcached", "redis", "dynamodb", "octane", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stores' => [
|
||||||
|
|
||||||
|
'apc' => [
|
||||||
|
'driver' => 'apc',
|
||||||
|
],
|
||||||
|
|
||||||
|
'array' => [
|
||||||
|
'driver' => 'array',
|
||||||
|
'serialize' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'database' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'table' => 'cache',
|
||||||
|
'connection' => null,
|
||||||
|
'lock_connection' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'file' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
'path' => storage_path('framework/cache/data'),
|
||||||
|
'lock_path' => storage_path('framework/cache/data'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'memcached' => [
|
||||||
|
'driver' => 'memcached',
|
||||||
|
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||||
|
'sasl' => [
|
||||||
|
env('MEMCACHED_USERNAME'),
|
||||||
|
env('MEMCACHED_PASSWORD'),
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||||
|
],
|
||||||
|
'servers' => [
|
||||||
|
[
|
||||||
|
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('MEMCACHED_PORT', 11211),
|
||||||
|
'weight' => 100,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => 'cache',
|
||||||
|
'lock_connection' => 'default',
|
||||||
|
],
|
||||||
|
|
||||||
|
'dynamodb' => [
|
||||||
|
'driver' => 'dynamodb',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||||
|
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'octane' => [
|
||||||
|
'driver' => 'octane',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Key Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
|
||||||
|
| stores there might be other applications using the same cache. For
|
||||||
|
| that reason, you may prefix every cache key to avoid collisions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||||
|
|
||||||
|
];
|
||||||
34
config/cors.php
Normal file
34
config/cors.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure your settings for cross-origin resource sharing
|
||||||
|
| or "CORS". This determines what cross-origin operations may execute
|
||||||
|
| in web browsers. You are free to adjust these settings as needed.
|
||||||
|
|
|
||||||
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||||||
|
|
||||||
|
'allowed_methods' => ['*'],
|
||||||
|
|
||||||
|
'allowed_origins' => ['*'],
|
||||||
|
|
||||||
|
'allowed_origins_patterns' => [],
|
||||||
|
|
||||||
|
'allowed_headers' => ['*'],
|
||||||
|
|
||||||
|
'exposed_headers' => [],
|
||||||
|
|
||||||
|
'max_age' => 0,
|
||||||
|
|
||||||
|
'supports_credentials' => false,
|
||||||
|
|
||||||
|
];
|
||||||
171
config/database.php
Normal file
171
config/database.php
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Database Connection Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which of the database connections below you wish
|
||||||
|
| to use as your default connection for all database work. Of course
|
||||||
|
| you may use many connections at once using the Database library.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('DB_CONNECTION', 'mysql'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Database Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here are each of the database connections setup for your application.
|
||||||
|
| Of course, examples of configuring each database platform that is
|
||||||
|
| supported by Laravel is shown below to make development simple.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| All database work in Laravel is done through the PHP PDO facilities
|
||||||
|
| so make sure you have the driver for your particular database of
|
||||||
|
| choice installed on your machine before you begin development.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'sqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'url' => env('DATABASE_URL'),
|
||||||
|
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||||
|
'prefix' => '',
|
||||||
|
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||||
|
],
|
||||||
|
|
||||||
|
'mysql' => [
|
||||||
|
'driver' => 'mysql',
|
||||||
|
'url' => env('DATABASE_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '3306'),
|
||||||
|
'database' => env('DB_DATABASE', 'forge'),
|
||||||
|
'username' => env('DB_USERNAME', 'forge'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => false,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'nodejsDB' => [
|
||||||
|
'driver' => 'mysql',
|
||||||
|
'url' => env('NODEJS_DATABASE_URL'),
|
||||||
|
'host' => env('NODEJS_DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('NODEJS_DB_PORT', '3306'),
|
||||||
|
'database' => env('NODEJS_DB_DATABASE', 'forge'),
|
||||||
|
'username' => env('NODEJS_DB_USERNAME', 'forge'),
|
||||||
|
'password' => env('NODEJS_DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('NODEJS_DB_SOCKET', ''),
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'pgsql' => [
|
||||||
|
'driver' => 'pgsql',
|
||||||
|
'url' => env('DATABASE_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '5432'),
|
||||||
|
'database' => env('DB_DATABASE', 'forge'),
|
||||||
|
'username' => env('DB_USERNAME', 'forge'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'charset' => 'utf8',
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'search_path' => 'public',
|
||||||
|
'sslmode' => 'prefer',
|
||||||
|
],
|
||||||
|
|
||||||
|
'sqlsrv' => [
|
||||||
|
'driver' => 'sqlsrv',
|
||||||
|
'url' => env('DATABASE_URL'),
|
||||||
|
'host' => env('DB_HOST', 'localhost'),
|
||||||
|
'port' => env('DB_PORT', '1433'),
|
||||||
|
'database' => env('DB_DATABASE', 'forge'),
|
||||||
|
'username' => env('DB_USERNAME', 'forge'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'charset' => 'utf8',
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||||
|
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Migration Repository Table
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This table keeps track of all the migrations that have already run for
|
||||||
|
| your application. Using this information, we can determine which of
|
||||||
|
| the migrations on disk haven't actually been run in the database.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'migrations' => 'migrations',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Redis Databases
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Redis is an open source, fast, and advanced key-value store that also
|
||||||
|
| provides a richer body of commands than a typical key-value system
|
||||||
|
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
|
||||||
|
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||||
|
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'default' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_DB', '0'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_CACHE_DB', '1'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
127
config/datatables.php
Normal file
127
config/datatables.php
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
* DataTables search options.
|
||||||
|
*/
|
||||||
|
'search' => [
|
||||||
|
/*
|
||||||
|
* Smart search will enclose search keyword with wildcard string "%keyword%".
|
||||||
|
* SQL: column LIKE "%keyword%"
|
||||||
|
*/
|
||||||
|
'smart' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multi-term search will explode search keyword using spaces resulting into multiple term search.
|
||||||
|
*/
|
||||||
|
'multi_term' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Case insensitive will search the keyword in lower case format.
|
||||||
|
* SQL: LOWER(column) LIKE LOWER(keyword)
|
||||||
|
*/
|
||||||
|
'case_insensitive' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wild card will add "%" in between every characters of the keyword.
|
||||||
|
* SQL: column LIKE "%k%e%y%w%o%r%d%"
|
||||||
|
*/
|
||||||
|
'use_wildcards' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perform a search which starts with the given keyword.
|
||||||
|
* SQL: column LIKE "keyword%"
|
||||||
|
*/
|
||||||
|
'starts_with' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DataTables internal index id response column name.
|
||||||
|
*/
|
||||||
|
'index_column' => 'DT_RowIndex',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of available builders for DataTables.
|
||||||
|
* This is where you can register your custom dataTables builder.
|
||||||
|
*/
|
||||||
|
'engines' => [
|
||||||
|
'eloquent' => Yajra\DataTables\EloquentDataTable::class,
|
||||||
|
'query' => Yajra\DataTables\QueryDataTable::class,
|
||||||
|
'collection' => Yajra\DataTables\CollectionDataTable::class,
|
||||||
|
'resource' => Yajra\DataTables\ApiResourceDataTable::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DataTables accepted builder to engine mapping.
|
||||||
|
* This is where you can override which engine a builder should use
|
||||||
|
* Note, only change this if you know what you are doing!
|
||||||
|
*/
|
||||||
|
'builders' => [
|
||||||
|
//Illuminate\Database\Eloquent\Relations\Relation::class => 'eloquent',
|
||||||
|
//Illuminate\Database\Eloquent\Builder::class => 'eloquent',
|
||||||
|
//Illuminate\Database\Query\Builder::class => 'query',
|
||||||
|
//Illuminate\Support\Collection::class => 'collection',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nulls last sql pattern for PostgreSQL & Oracle.
|
||||||
|
* For MySQL, use 'CASE WHEN :column IS NULL THEN 1 ELSE 0 END, :column :direction'
|
||||||
|
*/
|
||||||
|
'nulls_last_sql' => ':column :direction NULLS LAST',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* User friendly message to be displayed on user if error occurs.
|
||||||
|
* Possible values:
|
||||||
|
* null - The exception message will be used on error response.
|
||||||
|
* 'throw' - Throws a \Yajra\DataTables\Exceptions\Exception. Use your custom error handler if needed.
|
||||||
|
* 'custom message' - Any friendly message to be displayed to the user. You can also use translation key.
|
||||||
|
*/
|
||||||
|
'error' => env('DATATABLES_ERROR', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default columns definition of dataTable utility functions.
|
||||||
|
*/
|
||||||
|
'columns' => [
|
||||||
|
/*
|
||||||
|
* List of columns hidden/removed on json response.
|
||||||
|
*/
|
||||||
|
'excess' => ['rn', 'row_num'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns to be escaped. If set to *, all columns are escape.
|
||||||
|
* Note: You can set the value to empty array to disable XSS protection.
|
||||||
|
*/
|
||||||
|
'escape' => '*',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns that are allowed to display html content.
|
||||||
|
* Note: Adding columns to list will make us available to XSS attacks.
|
||||||
|
*/
|
||||||
|
'raw' => ['action'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns are forbidden from being searched/sorted.
|
||||||
|
*/
|
||||||
|
'blacklist' => ['password', 'remember_token'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns that are only allowed fo search/sort.
|
||||||
|
* If set to *, all columns are allowed.
|
||||||
|
*/
|
||||||
|
'whitelist' => '*',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JsonResponse header and options config.
|
||||||
|
*/
|
||||||
|
'json' => [
|
||||||
|
'header' => [],
|
||||||
|
'options' => 0,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default condition to determine if a parameter is a callback or not.
|
||||||
|
* Callbacks needs to start by those terms, or they will be cast to string.
|
||||||
|
*/
|
||||||
|
'callback' => ['$', '$.', 'function'],
|
||||||
|
];
|
||||||
92
config/filesystems.php
Normal file
92
config/filesystems.php
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Filesystem Disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the default filesystem disk that should be used
|
||||||
|
| by the framework. The "local" disk, as well as a variety of cloud
|
||||||
|
| based disks are available to your application. Just store away!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Filesystem Disks
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure as many filesystem "disks" as you wish, and you
|
||||||
|
| may even configure multiple disks of the same driver. Defaults have
|
||||||
|
| been set up for each driver as an example of the required values.
|
||||||
|
|
|
||||||
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'disks' => [
|
||||||
|
|
||||||
|
'local' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app'),
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'public' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/public'),
|
||||||
|
'url' => env('APP_URL') . '/storage',
|
||||||
|
'visibility' => 'public',
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'nodejs_public_projects_files' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/public/nodejs/projects'),
|
||||||
|
'url' => env('APP_URL') . 'storage/nodejs/projects',
|
||||||
|
'visibility' => 'public',
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'nodejs_public_submissions_files' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/public/nodejs/tmp/submissions_zip_files'),
|
||||||
|
'url' => env('APP_URL') . 'storage/nodejs/tmp/submissions_zip_files',
|
||||||
|
'visibility' => 'public',
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
's3' => [
|
||||||
|
'driver' => 's3',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION'),
|
||||||
|
'bucket' => env('AWS_BUCKET'),
|
||||||
|
'url' => env('AWS_URL'),
|
||||||
|
'endpoint' => env('AWS_ENDPOINT'),
|
||||||
|
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Symbolic Links
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the symbolic links that will be created when the
|
||||||
|
| `storage:link` Artisan command is executed. The array keys should be
|
||||||
|
| the locations of the links and the values should be their targets.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'links' => [
|
||||||
|
public_path('storage') => storage_path('app/public'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
52
config/hashing.php
Normal file
52
config/hashing.php
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Hash Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default hash driver that will be used to hash
|
||||||
|
| passwords for your application. By default, the bcrypt algorithm is
|
||||||
|
| used; however, you remain free to modify this option if you wish.
|
||||||
|
|
|
||||||
|
| Supported: "bcrypt", "argon", "argon2id"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'driver' => 'bcrypt',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Bcrypt Options
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the configuration options that should be used when
|
||||||
|
| passwords are hashed using the Bcrypt algorithm. This will allow you
|
||||||
|
| to control the amount of time it takes to hash the given password.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'bcrypt' => [
|
||||||
|
'rounds' => env('BCRYPT_ROUNDS', 10),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Argon Options
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the configuration options that should be used when
|
||||||
|
| passwords are hashed using the Argon algorithm. These will allow you
|
||||||
|
| to control the amount of time it takes to hash the given password.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'argon' => [
|
||||||
|
'memory' => 65536,
|
||||||
|
'threads' => 1,
|
||||||
|
'time' => 4,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
131
config/logging.php
Normal file
131
config/logging.php
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Monolog\Handler\NullHandler;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Handler\SyslogUdpHandler;
|
||||||
|
use Monolog\Processor\PsrLogMessageProcessor;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Log Channel
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option defines the default log channel that gets used when writing
|
||||||
|
| messages to the logs. The name specified in this option should match
|
||||||
|
| one of the channels defined in the "channels" configuration array.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('LOG_CHANNEL', 'stack'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Deprecations Log Channel
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the log channel that should be used to log warnings
|
||||||
|
| regarding deprecated PHP and library features. This allows you to get
|
||||||
|
| your application ready for upcoming major versions of dependencies.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'deprecations' => [
|
||||||
|
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||||
|
'trace' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Log Channels
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the log channels for your application. Out of
|
||||||
|
| the box, Laravel uses the Monolog PHP logging library. This gives
|
||||||
|
| you a variety of powerful log handlers / formatters to utilize.
|
||||||
|
|
|
||||||
|
| Available Drivers: "single", "daily", "slack", "syslog",
|
||||||
|
| "errorlog", "monolog",
|
||||||
|
| "custom", "stack"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'channels' => [
|
||||||
|
'stack' => [
|
||||||
|
'driver' => 'stack',
|
||||||
|
'channels' => ['single'],
|
||||||
|
'ignore_exceptions' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'single' => [
|
||||||
|
'driver' => 'single',
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'daily' => [
|
||||||
|
'driver' => 'daily',
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'days' => 14,
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'slack' => [
|
||||||
|
'driver' => 'slack',
|
||||||
|
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||||
|
'username' => 'Laravel Log',
|
||||||
|
'emoji' => ':boom:',
|
||||||
|
'level' => env('LOG_LEVEL', 'critical'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'papertrail' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||||
|
'handler_with' => [
|
||||||
|
'host' => env('PAPERTRAIL_URL'),
|
||||||
|
'port' => env('PAPERTRAIL_PORT'),
|
||||||
|
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
'stderr' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => StreamHandler::class,
|
||||||
|
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||||
|
'with' => [
|
||||||
|
'stream' => 'php://stderr',
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
'syslog' => [
|
||||||
|
'driver' => 'syslog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'facility' => LOG_USER,
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'errorlog' => [
|
||||||
|
'driver' => 'errorlog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'null' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'handler' => NullHandler::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'emergency' => [
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
125
config/mail.php
Normal file
125
config/mail.php
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Mailer
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default mailer that is used to send any email
|
||||||
|
| messages sent by your application. Alternative mailers may be setup
|
||||||
|
| and used as needed; however, this mailer will be used by default.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('MAIL_MAILER', 'smtp'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Mailer Configurations
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure all of the mailers used by your application plus
|
||||||
|
| their respective settings. Several examples have been configured for
|
||||||
|
| you and you are free to add your own as your application requires.
|
||||||
|
|
|
||||||
|
| Laravel supports a variety of mail "transport" drivers to be used while
|
||||||
|
| sending an e-mail. You will specify which one you are using for your
|
||||||
|
| mailers below. You are free to add additional mailers as required.
|
||||||
|
|
|
||||||
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||||
|
| "postmark", "log", "array", "failover"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'mailers' => [
|
||||||
|
'smtp' => [
|
||||||
|
'transport' => 'smtp',
|
||||||
|
'url' => env('MAIL_URL'),
|
||||||
|
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||||
|
'port' => env('MAIL_PORT', 587),
|
||||||
|
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||||
|
'username' => env('MAIL_USERNAME'),
|
||||||
|
'password' => env('MAIL_PASSWORD'),
|
||||||
|
'timeout' => null,
|
||||||
|
'local_domain' => env('MAIL_EHLO_DOMAIN'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'ses' => [
|
||||||
|
'transport' => 'ses',
|
||||||
|
],
|
||||||
|
|
||||||
|
'mailgun' => [
|
||||||
|
'transport' => 'mailgun',
|
||||||
|
// 'client' => [
|
||||||
|
// 'timeout' => 5,
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
'postmark' => [
|
||||||
|
'transport' => 'postmark',
|
||||||
|
// 'client' => [
|
||||||
|
// 'timeout' => 5,
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
'sendmail' => [
|
||||||
|
'transport' => 'sendmail',
|
||||||
|
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'log' => [
|
||||||
|
'transport' => 'log',
|
||||||
|
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'array' => [
|
||||||
|
'transport' => 'array',
|
||||||
|
],
|
||||||
|
|
||||||
|
'failover' => [
|
||||||
|
'transport' => 'failover',
|
||||||
|
'mailers' => [
|
||||||
|
'smtp',
|
||||||
|
'log',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Global "From" Address
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You may wish for all e-mails sent by your application to be sent from
|
||||||
|
| the same address. Here, you may specify a name and address that is
|
||||||
|
| used globally for all e-mails that are sent by your application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'from' => [
|
||||||
|
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||||
|
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Markdown Mail Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| If you are using Markdown based email rendering, you may configure your
|
||||||
|
| theme and component paths here, allowing you to customize the design
|
||||||
|
| of the emails. Or, you may simply stick with the Laravel defaults!
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'markdown' => [
|
||||||
|
'theme' => 'default',
|
||||||
|
|
||||||
|
'paths' => [
|
||||||
|
resource_path('views/vendor/mail'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
237
config/media-library.php
Normal file
237
config/media-library.php
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The disk on which to store added files and derived images by default. Choose
|
||||||
|
* one or more of the disks you've configured in config/filesystems.php.
|
||||||
|
*/
|
||||||
|
'disk_name' => env('MEDIA_DISK', 'public'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum file size of an item in bytes.
|
||||||
|
* Adding a larger file will result in an exception.
|
||||||
|
*/
|
||||||
|
'max_file_size' => 1024 * 1024 * 10, // 10MB
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This queue connection will be used to generate derived and responsive images.
|
||||||
|
* Leave empty to use the default queue connection.
|
||||||
|
*/
|
||||||
|
'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This queue will be used to generate derived and responsive images.
|
||||||
|
* Leave empty to use the default queue.
|
||||||
|
*/
|
||||||
|
'queue_name' => '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default all conversions will be performed on a queue.
|
||||||
|
*/
|
||||||
|
'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The fully qualified class name of the media model.
|
||||||
|
*/
|
||||||
|
'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The fully qualified class name of the model used for temporary uploads.
|
||||||
|
*
|
||||||
|
* This model is only used in Media Library Pro (https://medialibrary.pro)
|
||||||
|
*/
|
||||||
|
'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When enabled, Media Library Pro will only process temporary uploads that were uploaded
|
||||||
|
* in the same session. You can opt to disable this for stateless usage of
|
||||||
|
* the pro components.
|
||||||
|
*/
|
||||||
|
'enable_temporary_uploads_session_affinity' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When enabled, Media Library pro will generate thumbnails for uploaded file.
|
||||||
|
*/
|
||||||
|
'generate_thumbnails_for_temporary_uploads' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the class that is responsible for naming generated files.
|
||||||
|
*/
|
||||||
|
'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class that contains the strategy for determining a media file's path.
|
||||||
|
*/
|
||||||
|
'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here you can specify which path generator should be used for the given class.
|
||||||
|
*/
|
||||||
|
'custom_path_generators' => [
|
||||||
|
// Model::class => PathGenerator::class
|
||||||
|
// or
|
||||||
|
// 'model_morph_alias' => PathGenerator::class
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When urls to files get generated, this class will be called. Use the default
|
||||||
|
* if your files are stored locally above the site root or on s3.
|
||||||
|
*/
|
||||||
|
'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Moves media on updating to keep path consistent. Enable it only with a custom
|
||||||
|
* PathGenerator that uses, for example, the media UUID.
|
||||||
|
*/
|
||||||
|
'moves_media_on_update' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether to activate versioning when urls to files get generated.
|
||||||
|
* When activated, this attaches a ?v=xx query string to the URL.
|
||||||
|
*/
|
||||||
|
'version_urls' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The media library will try to optimize all converted images by removing
|
||||||
|
* metadata and applying a little bit of compression. These are
|
||||||
|
* the optimizers that will be used by default.
|
||||||
|
*/
|
||||||
|
'image_optimizers' => [
|
||||||
|
Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
|
||||||
|
'-m85', // set maximum quality to 85%
|
||||||
|
'--force', // ensure that progressive generation is always done also if a little bigger
|
||||||
|
'--strip-all', // this strips out all text information such as comments and EXIF data
|
||||||
|
'--all-progressive', // this will make sure the resulting image is a progressive one
|
||||||
|
],
|
||||||
|
Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
|
||||||
|
'--force', // required parameter for this package
|
||||||
|
],
|
||||||
|
Spatie\ImageOptimizer\Optimizers\Optipng::class => [
|
||||||
|
'-i0', // this will result in a non-interlaced, progressive scanned image
|
||||||
|
'-o2', // this set the optimization level to two (multiple IDAT compression trials)
|
||||||
|
'-quiet', // required parameter for this package
|
||||||
|
],
|
||||||
|
Spatie\ImageOptimizer\Optimizers\Svgo::class => [
|
||||||
|
'--disable=cleanupIDs', // disabling because it is known to cause troubles
|
||||||
|
],
|
||||||
|
Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
|
||||||
|
'-b', // required parameter for this package
|
||||||
|
'-O3', // this produces the slowest but best results
|
||||||
|
],
|
||||||
|
Spatie\ImageOptimizer\Optimizers\Cwebp::class => [
|
||||||
|
'-m 6', // for the slowest compression method in order to get the best compression.
|
||||||
|
'-pass 10', // for maximizing the amount of analysis pass.
|
||||||
|
'-mt', // multithreading for some speed improvements.
|
||||||
|
'-q 90', //quality factor that brings the least noticeable changes.
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These generators will be used to create an image of media files.
|
||||||
|
*/
|
||||||
|
'image_generators' => [
|
||||||
|
Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class,
|
||||||
|
Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class,
|
||||||
|
Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class,
|
||||||
|
Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class,
|
||||||
|
Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The path where to store temporary files while performing image conversions.
|
||||||
|
* If set to null, storage_path('media-library/temp') will be used.
|
||||||
|
*/
|
||||||
|
'temporary_directory_path' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The engine that should perform the image conversions.
|
||||||
|
* Should be either `gd` or `imagick`.
|
||||||
|
*/
|
||||||
|
'image_driver' => env('IMAGE_DRIVER', 'gd'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FFMPEG & FFProbe binaries paths, only used if you try to generate video
|
||||||
|
* thumbnails and have installed the php-ffmpeg/php-ffmpeg composer
|
||||||
|
* dependency.
|
||||||
|
*/
|
||||||
|
'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
|
||||||
|
'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here you can override the class names of the jobs used by this package. Make sure
|
||||||
|
* your custom jobs extend the ones provided by the package.
|
||||||
|
*/
|
||||||
|
'jobs' => [
|
||||||
|
'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class,
|
||||||
|
'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the addMediaFromUrl method you may want to replace the default downloader.
|
||||||
|
* This is particularly useful when the url of the image is behind a firewall and
|
||||||
|
* need to add additional flags, possibly using curl.
|
||||||
|
*/
|
||||||
|
'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class,
|
||||||
|
|
||||||
|
'remote' => [
|
||||||
|
/*
|
||||||
|
* Any extra headers that should be included when uploading media to
|
||||||
|
* a remote disk. Even though supported headers may vary between
|
||||||
|
* different drivers, a sensible default has been provided.
|
||||||
|
*
|
||||||
|
* Supported by S3: CacheControl, Expires, StorageClass,
|
||||||
|
* ServerSideEncryption, Metadata, ACL, ContentEncoding
|
||||||
|
*/
|
||||||
|
'extra_headers' => [
|
||||||
|
'CacheControl' => 'max-age=604800',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'responsive_images' => [
|
||||||
|
/*
|
||||||
|
* This class is responsible for calculating the target widths of the responsive
|
||||||
|
* images. By default we optimize for filesize and create variations that each are 30%
|
||||||
|
* smaller than the previous one. More info in the documentation.
|
||||||
|
*
|
||||||
|
* https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images
|
||||||
|
*/
|
||||||
|
'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default rendering media to a responsive image will add some javascript and a tiny placeholder.
|
||||||
|
* This ensures that the browser can already determine the correct layout.
|
||||||
|
*/
|
||||||
|
'use_tiny_placeholders' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class will generate the tiny placeholder used for progressive image loading. By default
|
||||||
|
* the media library will use a tiny blurred jpg image.
|
||||||
|
*/
|
||||||
|
'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When enabling this option, a route will be registered that will enable
|
||||||
|
* the Media Library Pro Vue and React components to move uploaded files
|
||||||
|
* in a S3 bucket to their right place.
|
||||||
|
*/
|
||||||
|
'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When converting Media instances to response the media library will add
|
||||||
|
* a `loading` attribute to the `img` tag. Here you can set the default
|
||||||
|
* value of that attribute.
|
||||||
|
*
|
||||||
|
* Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction.
|
||||||
|
*
|
||||||
|
* More info: https://css-tricks.com/native-lazy-loading/
|
||||||
|
*/
|
||||||
|
'default_loading_attribute_value' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You can specify a prefix for that is used for storing all media.
|
||||||
|
* If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory.
|
||||||
|
*/
|
||||||
|
'prefix' => env('MEDIA_PREFIX', ''),
|
||||||
|
];
|
||||||
109
config/queue.php
Normal file
109
config/queue.php
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Queue Connection Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Laravel's queue API supports an assortment of back-ends via a single
|
||||||
|
| API, giving you convenient access to each back-end using the same
|
||||||
|
| syntax for every one. Here you may define a default connection.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('QUEUE_CONNECTION', 'sync'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Queue Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the connection information for each server that
|
||||||
|
| is used by your application. A default configuration has been added
|
||||||
|
| for each back-end shipped with Laravel. You are free to add more.
|
||||||
|
|
|
||||||
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'sync' => [
|
||||||
|
'driver' => 'sync',
|
||||||
|
],
|
||||||
|
|
||||||
|
'database' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'table' => 'jobs',
|
||||||
|
'queue' => 'default',
|
||||||
|
'retry_after' => 90,
|
||||||
|
'after_commit' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'beanstalkd' => [
|
||||||
|
'driver' => 'beanstalkd',
|
||||||
|
'host' => 'localhost',
|
||||||
|
'queue' => 'default',
|
||||||
|
'retry_after' => 90,
|
||||||
|
'block_for' => 0,
|
||||||
|
'after_commit' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'sqs' => [
|
||||||
|
'driver' => 'sqs',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||||
|
'queue' => env('SQS_QUEUE', 'default'),
|
||||||
|
'suffix' => env('SQS_SUFFIX'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
'after_commit' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => 'default',
|
||||||
|
'queue' => env('REDIS_QUEUE', 'default'),
|
||||||
|
'retry_after' => 90,
|
||||||
|
'block_for' => null,
|
||||||
|
'after_commit' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Job Batching
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following options configure the database and table that store job
|
||||||
|
| batching information. These options can be updated to any database
|
||||||
|
| connection and table which has been defined by your application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'batching' => [
|
||||||
|
'database' => env('DB_CONNECTION', 'mysql'),
|
||||||
|
'table' => 'job_batches',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Failed Queue Jobs
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These options configure the behavior of failed queue job logging so you
|
||||||
|
| can control which database and table are used to store the jobs that
|
||||||
|
| have failed. You may change them to any database / table you wish.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'failed' => [
|
||||||
|
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||||
|
'database' => env('DB_CONNECTION', 'mysql'),
|
||||||
|
'table' => 'failed_jobs',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
67
config/sanctum.php
Normal file
67
config/sanctum.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Stateful Domains
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Requests from the following domains / hosts will receive stateful API
|
||||||
|
| authentication cookies. Typically, these should include your local
|
||||||
|
| and production domains which access your API via a frontend SPA.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||||
|
'%s%s',
|
||||||
|
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||||
|
Sanctum::currentApplicationUrlWithPort()
|
||||||
|
))),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Sanctum Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array contains the authentication guards that will be checked when
|
||||||
|
| Sanctum is trying to authenticate a request. If none of these guards
|
||||||
|
| are able to authenticate the request, Sanctum will use the bearer
|
||||||
|
| token that's present on an incoming request for authentication.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guard' => ['web'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Expiration Minutes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value controls the number of minutes until an issued token will be
|
||||||
|
| considered expired. If this value is null, personal access tokens do
|
||||||
|
| not expire. This won't tweak the lifetime of first-party sessions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'expiration' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Sanctum Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When authenticating your first-party SPA with Sanctum you may need to
|
||||||
|
| customize some of the middleware Sanctum uses while processing the
|
||||||
|
| request. You may change the middleware listed below as required.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
34
config/services.php
Normal file
34
config/services.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Third Party Services
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This file is for storing the credentials for third party services such
|
||||||
|
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||||
|
| location for this type of information, allowing packages to have
|
||||||
|
| a conventional file to locate the various service credentials.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'mailgun' => [
|
||||||
|
'domain' => env('MAILGUN_DOMAIN'),
|
||||||
|
'secret' => env('MAILGUN_SECRET'),
|
||||||
|
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||||
|
'scheme' => 'https',
|
||||||
|
],
|
||||||
|
|
||||||
|
'postmark' => [
|
||||||
|
'token' => env('POSTMARK_TOKEN'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'ses' => [
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
201
config/session.php
Normal file
201
config/session.php
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Session Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default session "driver" that will be used on
|
||||||
|
| requests. By default, we will use the lightweight native driver but
|
||||||
|
| you may specify any of the other wonderful drivers provided here.
|
||||||
|
|
|
||||||
|
| Supported: "file", "cookie", "database", "apc",
|
||||||
|
| "memcached", "redis", "dynamodb", "array"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'driver' => env('SESSION_DRIVER', 'file'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Lifetime
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the number of minutes that you wish the session
|
||||||
|
| to be allowed to remain idle before it expires. If you want them
|
||||||
|
| to immediately expire on the browser closing, set that option.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'lifetime' => env('SESSION_LIFETIME', 120),
|
||||||
|
|
||||||
|
'expire_on_close' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Encryption
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option allows you to easily specify that all of your session data
|
||||||
|
| should be encrypted before it is stored. All encryption will be run
|
||||||
|
| automatically by Laravel and you can use the Session like normal.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'encrypt' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session File Location
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the native session driver, we need a location where session
|
||||||
|
| files may be stored. A default has been set for you but a different
|
||||||
|
| location may be specified. This is only needed for file sessions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'files' => storage_path('framework/sessions'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Database Connection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the "database" or "redis" session drivers, you may specify a
|
||||||
|
| connection that should be used to manage these sessions. This should
|
||||||
|
| correspond to a connection in your database configuration options.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connection' => env('SESSION_CONNECTION'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Database Table
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the "database" session driver, you may specify the table we
|
||||||
|
| should use to manage the sessions. Of course, a sensible default is
|
||||||
|
| provided for you; however, you are free to change this as needed.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'table' => 'sessions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Cache Store
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| While using one of the framework's cache driven session backends you may
|
||||||
|
| list a cache store that should be used for these sessions. This value
|
||||||
|
| must match with one of the application's configured cache "stores".
|
||||||
|
|
|
||||||
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'store' => env('SESSION_STORE'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Sweeping Lottery
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Some session drivers must manually sweep their storage location to get
|
||||||
|
| rid of old sessions from storage. Here are the chances that it will
|
||||||
|
| happen on a given request. By default, the odds are 2 out of 100.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'lottery' => [2, 100],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Cookie Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may change the name of the cookie used to identify a session
|
||||||
|
| instance by ID. The name specified here will get used every time a
|
||||||
|
| new session cookie is created by the framework for every driver.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'cookie' => env(
|
||||||
|
'SESSION_COOKIE',
|
||||||
|
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
|
||||||
|
),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Cookie Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The session cookie path determines the path for which the cookie will
|
||||||
|
| be regarded as available. Typically, this will be the root path of
|
||||||
|
| your application but you are free to change this when necessary.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'path' => '/',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session Cookie Domain
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may change the domain of the cookie used to identify a session
|
||||||
|
| in your application. This will determine which domains the cookie is
|
||||||
|
| available to in your application. A sensible default has been set.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'domain' => env('SESSION_DOMAIN'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| HTTPS Only Cookies
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By setting this option to true, session cookies will only be sent back
|
||||||
|
| to the server if the browser has a HTTPS connection. This will keep
|
||||||
|
| the cookie from being sent to you when it can't be done securely.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| HTTP Access Only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Setting this value to true will prevent JavaScript from accessing the
|
||||||
|
| value of the cookie and the cookie will only be accessible through
|
||||||
|
| the HTTP protocol. You are free to modify this option if needed.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'http_only' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Same-Site Cookies
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option determines how your cookies behave when cross-site requests
|
||||||
|
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||||
|
| will set this value to "lax" since this is a secure default value.
|
||||||
|
|
|
||||||
|
| Supported: "lax", "strict", "none", null
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'same_site' => 'lax',
|
||||||
|
|
||||||
|
];
|
||||||
36
config/view.php
Normal file
36
config/view.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| View Storage Paths
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Most templating systems load templates from disk. Here you may specify
|
||||||
|
| an array of paths that should be checked for your views. Of course
|
||||||
|
| the usual Laravel view path has already been registered for you.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'paths' => [
|
||||||
|
resource_path('views'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Compiled View Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option determines where all the compiled Blade templates will be
|
||||||
|
| stored for your application. Typically, this is within the storage
|
||||||
|
| directory. However, as usual, you are free to change this value.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'compiled' => env(
|
||||||
|
'VIEW_COMPILED_PATH',
|
||||||
|
realpath(storage_path('framework/views'))
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
1
database/.gitignore
vendored
Normal file
1
database/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.sqlite*
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user