Amal_Udjir/app/Http/Controllers/NodeJS/Student/SubmissionController.php

758 lines
37 KiB
PHP

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