';
if ($submission !== null) {
@@ -131,7 +129,7 @@ public function upload(Request $request, $project_id)
$file = $request->file('folder_path');
$file_name = $file->getClientOriginalName();
- $folder_path = 'public/nodejs/tmp/submissions/' . $request->user()->id . '/' . $project_title;
+ $folder_path = 'public/tmp/submissions/' . $request->user()->id . '/' . $project_title;
$file->storeAs($folder_path, $file_name);
TemporaryFile::create([
@@ -149,7 +147,7 @@ public function submit(Request $request)
try {
$request->validate([
- 'project_id' => 'required|exists:nodejsDB.projects,id',
+ 'project_id' => 'required|exists:projects,id',
'folder_path' => 'required_without:github_url',
'github_url' => 'required_without:folder_path',
]);
@@ -171,7 +169,7 @@ public function submit(Request $request)
if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
- $submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
+ $submission->addMedia($path)->toMediaCollection('submissions', 'public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path));
}
@@ -337,6 +335,14 @@ public function process(Request $request)
break;
}
}
+
+ // Revalidate status before returning response to ensure consistency
+ if ($isNotHistory && $submission->status === Submission::$PROCESSING) {
+ // Force a fresh check of status based on current step results
+ $submission->getCurrentExecutionStep();
+ $completion_percentage = round($submission->getTotalCompletedSteps() / $submission->getTotalSteps() * 100);
+ }
+
return $this->returnSubmissionResponse(
$isNotHistory ? 'Step ' . $step->executionStep->name . ' is ' . $submission->results->{$step->executionStep->name}->status : "History",
$submission->status,
@@ -345,6 +351,33 @@ public function process(Request $request)
$completion_percentage
);
}
+
+ // If we get here, check one more time if submission should be marked as completed
+ if ($isNotHistory && $submission->status === Submission::$PROCESSING) {
+ $allStepsComplete = true;
+ $anyStepsFailed = false;
+
+ foreach ($submission->getExecutionSteps() as $execStep) {
+ $stepName = $execStep->executionStep->name;
+ if (
+ !isset($submission->results->$stepName) ||
+ $submission->results->$stepName->status === Submission::$PENDING ||
+ $submission->results->$stepName->status === Submission::$PROCESSING
+ ) {
+ $allStepsComplete = false;
+ break;
+ }
+
+ if ($submission->results->$stepName->status === Submission::$FAILED) {
+ $anyStepsFailed = true;
+ }
+ }
+
+ if ($allStepsComplete) {
+ $submission->updateStatus($anyStepsFailed ? Submission::$FAILED : Submission::$COMPLETED);
+ }
+ }
+
return $this->returnSubmissionResponse(
($isNotHistory ? 'Submission is processing meanwhile there is no step to execute' : "History"),
$submission->status,
@@ -399,7 +432,10 @@ public function refresh(Request $request)
}
// Delete temp directory
foreach ($commands as $command) {
- $process = new Process($command, null, null, null, 120);
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+ $process = new Process($command, null, $env, null, 120);
$process->run();
if ($process->isSuccessful()) {
Log::info('Command ' . implode(" ", $command) . ' is successful');
@@ -429,7 +465,7 @@ public function refresh(Request $request)
private function getTempDir($submission)
{
- return storage_path('app/public/nodejs/tmp/submissions/' . $submission->user_id . '/' . $submission->project->title . '/' . $submission->id);
+ return storage_path('app/public/tmp/submissions/' . $submission->user_id . '/' . $submission->project->title . '/' . $submission->id);
}
private function is_dir_empty($dir)
@@ -502,33 +538,51 @@ private function lunchReplacePackageJsonJob($submission, $packageJson, $tempDir,
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}}
+ $testFiles = $step->variables;
$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);
+
+ $testDirPath = $tempDir . '/tests';
+ if (!is_dir($testDirPath)) {
+ mkdir($testDirPath, 0777, true);
}
- foreach ($testsDir['testsDirWeb'] as $key => $value) {
- $commands[2] = $value->getPath();
- $commands[3] = $tempDir . '/tests/web';
- array_push($commandsArray, $commands);
+
+ foreach ($testFiles as $testFile) {
+ // Parse {{sourceFile}}=media:filename:subfolder
+ $parts = explode("=", $testFile);
+
+ if (isset($parts[1])) {
+ // media:filename:subfolder -> [media, filename, subfolder]
+ $fileConfig = explode(":", $parts[1]);
+
+ $mediaCollection = isset($fileConfig[0]) ? $fileConfig[0] : 'tests';
+ $fileName = isset($fileConfig[1]) ? $fileConfig[1] : '';
+ $subFolder = isset($fileConfig[2]) ? $fileConfig[2] : '';
+
+ $mediaItem = $submission->project->getMedia('project_tests' . ($mediaCollection != 'tests' ? "_$mediaCollection" : ""))
+ ->where('file_name', $fileName)
+ ->first();
+
+ if ($mediaItem) {
+ $destDir = $testDirPath;
+ if (!empty($subFolder)) {
+ $destDir .= '/' . $subFolder;
+ if (!is_dir($destDir)) {
+ mkdir($destDir, 0777, true);
+ }
+ }
+
+ $copyCommand = $commands;
+ $copyCommand[2] = $mediaItem->getPath();
+ $copyCommand[3] = $destDir . '/' . basename($fileName);
+ $commandsArray[] = $copyCommand;
+ } else {
+ Log::warning("Test file not found: {$fileName} in collection project_tests_{$mediaCollection}");
+ }
+ }
}
- 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);
+
+ dispatch(new CopyTestsFolder($submission, null, $tempDir, $commandsArray))->onQueue(ExecutionStep::$COPY_TESTS_FOLDER);
}
private function lunchNpmInstallJob($submission, $tempDir, $step)
@@ -543,7 +597,7 @@ private function lunchNpmInstallJob($submission, $tempDir, $step)
private function lunchNpmRunStartJob($submission, $tempDir, $step)
{
$commands = $step->executionStep->commands;
- dispatch_sync(new NpmRunStart($submission, $tempDir, $commands));
+ dispatch(new NpmRunStart($submission, $tempDir, $commands));
}
private function lunchNpmRunTestsJob($submission, $tempDir, $step)
@@ -653,7 +707,7 @@ 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 view('submissions.change_source_code', compact('submission'));
}
return redirect()->route('submissions');
}
@@ -662,7 +716,7 @@ public function update(Request $request)
{
try {
$request->validate([
- 'submission_id' => 'required|exists:nodejsDB.submissions,id',
+ 'submission_id' => 'required|exists:submissions,id',
'folder_path' => 'required_without:github_url',
'github_url' => 'required_without:folder_path',
]);
@@ -693,7 +747,7 @@ public function update(Request $request)
if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
- $submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
+ $submission->addMedia($path)->toMediaCollection('submissions', 'public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path));
}
diff --git a/app/Jobs/NodeJS/CloneRepository.php b/app/Jobs/NodeJS/CloneRepository.php
index a2d7d67..3ee5848 100644
--- a/app/Jobs/NodeJS/CloneRepository.php
+++ b/app/Jobs/NodeJS/CloneRepository.php
@@ -12,6 +12,7 @@
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
+use Illuminate\Support\Facades\File;
class CloneRepository implements ShouldQueue
{
@@ -41,8 +42,10 @@ public function handle(): void
Log::info("Cloning repo {$this->repoUrl} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Cloning repo {$this->repoUrl}");
try {
+ $this->prepareTempDirectory();
+
// processing
- $process = new Process($this->command);
+ $process = new Process($this->command, null, null, null, 500);
$process->start();
$process_pid = $process->getPid();
$process->wait();
@@ -71,4 +74,15 @@ private function updateSubmissionStatus(Submission $submission, string $status,
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
+
+ private function prepareTempDirectory(): void
+ {
+ if (File::exists($this->tempDir)) {
+ File::deleteDirectory($this->tempDir);
+ }
+
+ File::ensureDirectoryExists(dirname($this->tempDir), 0755, true);
+
+ Log::info("Created temp directory {$this->tempDir}");
+ }
}
diff --git a/app/Jobs/NodeJS/CopyTestsFolder.php b/app/Jobs/NodeJS/CopyTestsFolder.php
index ac0f422..a2c8c5c 100644
--- a/app/Jobs/NodeJS/CopyTestsFolder.php
+++ b/app/Jobs/NodeJS/CopyTestsFolder.php
@@ -41,36 +41,36 @@ public function handle(): void
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();
+ // Ensure tests directory exists
+ if (!is_dir($this->tempDir . '/tests')) {
+ mkdir($this->tempDir . '/tests', 0777, true);
}
- 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);
+
+ // Execute all commands to copy test files
+ foreach ($this->command as $command) {
+ $process = new Process($command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
+
if ($process->isSuccessful()) {
- Log::info("Copied tests {$value[2]} folder to {$value[3]}");
+ Log::info("Copied test file {$command[2]} to {$command[3]}");
} else {
- Log::error("Failed to copying tests {$value[2]} folder to {$value[3]}");
- $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
+ Log::error("Failed to copy test file {$command[2]} to {$command[3]}");
+ Log::error("Error: " . $process->getErrorOutput());
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copy test files: " . $process->getErrorOutput());
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");
+ Log::info("Copied all test files to {$this->tempDir}");
+ $this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Copied test files successfully");
} 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");
+ Log::error("Failed to copy test files to {$this->tempDir}: " . $th->getMessage());
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copy test files: " . $th->getMessage());
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
diff --git a/app/Jobs/NodeJS/NpmInstall.php b/app/Jobs/NodeJS/NpmInstall.php
index 463a0a0..8187d49 100644
--- a/app/Jobs/NodeJS/NpmInstall.php
+++ b/app/Jobs/NodeJS/NpmInstall.php
@@ -41,7 +41,10 @@ public function handle(): void
try {
// processing
// check if the module is already exist within the assets folder
- $process = new Process($this->command, $this->tempDir, null, null, 120);
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+ $process = new Process($this->command, $this->tempDir, $env, null, 120);
$process->start();
$process_pid = $process->getPid();
$process->wait();
diff --git a/app/Jobs/NodeJS/NpmRunStart.php b/app/Jobs/NodeJS/NpmRunStart.php
index efa09b8..1f62453 100644
--- a/app/Jobs/NodeJS/NpmRunStart.php
+++ b/app/Jobs/NodeJS/NpmRunStart.php
@@ -36,12 +36,14 @@ public function __construct($submission, $tempDir, $command)
*/
public function handle(): void
{
+ set_time_limit(60);
+
$submission = $this->submission;
$tempDir = $this->tempDir;
$command = $this->command;
- Log::info("NPM run start is processing in folder {$this->tempDir}");
+ Log::info("NPM run will run on folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM run start is processing");
// Change port number in .env file
$port = $this->getAvailablePort();
@@ -49,6 +51,7 @@ public function handle(): void
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to find an available port for the project");
return;
}
+ Log::info("NPM run start is processing on port $port");
$submission->updatePort($port);
// Change port number in .env file
$envPath = "$tempDir/.env";
@@ -60,21 +63,23 @@ public function handle(): void
// Run NPM start command
try {
- $process = new Process($command, $tempDir, null, null, null);
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+ $process = new Process($command, $tempDir, $env, null, null);
$process->start();
$process->waitUntil(function ($type, $output) use ($port) {
- return strpos($output, "Server started on port $port") !== false || strpos($output, "MongoNetworkError") !== false;
+ return strpos($output, "$port") !== false || strpos($output, "MongoNetworkError") !== false;
}, 60000); // Wait for 60 seconds
- if (strpos($process->getOutput(), "Server started on port $port") !== false) {
+ if (strpos($process->getOutput(), "$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();
}
+ 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());
diff --git a/app/Jobs/NodeJS/NpmRunTests.php b/app/Jobs/NodeJS/NpmRunTests.php
index ae32964..0d6d34e 100644
--- a/app/Jobs/NodeJS/NpmRunTests.php
+++ b/app/Jobs/NodeJS/NpmRunTests.php
@@ -47,19 +47,69 @@ public function handle(): void
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();
+ $env = [
+ 'PATH' => config('app.process_path') . ':' . getenv('PATH'),
+ ];
+
+ $process = new Process($command, $this->tempDir, $env, null, 120);
+ $process->setTty(false);
+ $process->setPty(false);
+ $output = '';
+ $errorOutput = '';
+
+ $process->run(function ($type, $buffer) use (&$output, &$errorOutput) {
+ if (Process::OUT === $type) {
+ $output .= $buffer;
+ } else {
+ $errorOutput .= $buffer;
+ }
+ });
+
+ $fullOutput = trim($output . "\n" . $errorOutput);
+
+ $passedTests = null;
+ $totalTests = null;
+ $failedTests = null;
+
+ // Handle format: "Tests: X failed, Y passed, Z total"
+ if (preg_match('/Tests:\s+(\d+)\s+failed,\s+(\d+)\s+passed,\s+(\d+)\s+total/', $fullOutput, $matches)) {
+ $failedTests = (int)$matches[1];
+ $passedTests = (int)$matches[2];
+ $totalTests = (int)$matches[3];
+ Log::info("Extracted test metrics: $failedTests failed, $passedTests passed, $totalTests total");
+ }
+ // Handle format: "Tests: X passed, Y total"
+ else if (preg_match('/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+total/', $fullOutput, $matches)) {
+ $passedTests = (int)$matches[1];
+ $totalTests = (int)$matches[2];
+ $failedTests = $totalTests - $passedTests;
+ Log::info("Extracted test metrics: $passedTests passed, $totalTests total");
+ }
+
if ($process->isSuccessful()) {
$pass_all[$key] = true;
- Log::info("{$command_string} in folder {$this->tempDir}");
- $this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$COMPLETED, "Completed");
+ Log::info("{$command_string} completed in folder {$this->tempDir}");
+ $this->updateSubmissionTestsResultsStatus(
+ $command_string,
+ $submission,
+ Submission::$COMPLETED,
+ $fullOutput,
+ $passedTests,
+ $totalTests,
+ $failedTests
+ );
} 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();
+ Log::error("Failed to NPM run test {$command_string}");
+ $this->updateSubmissionTestsResultsStatus(
+ $command_string,
+ $submission,
+ Submission::$FAILED,
+ $fullOutput,
+ $passedTests,
+ $totalTests,
+ $failedTests
+ );
}
}
if (in_array(false, $pass_all) == false) {
@@ -77,10 +127,17 @@ public function handle(): void
}
}
- private function updateSubmissionTestsResultsStatus($testName, Submission $submission, string $status, string $output): void
- {
+ private function updateSubmissionTestsResultsStatus(
+ $testName,
+ Submission $submission,
+ string $status,
+ string $output,
+ ?int $passedTests = null,
+ ?int $totalTests = null,
+ ?int $failedTests = null
+ ): void {
$stepName = ExecutionStep::$NPM_RUN_TESTS;
- $submission->updateOneTestResult($stepName, $testName, $status, $output);
+ $submission->updateOneTestResult($stepName, $testName, $status, $output, $passedTests, $totalTests, $failedTests);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
diff --git a/app/Jobs/NodeJS/UnzipZipFiles.php b/app/Jobs/NodeJS/UnzipZipFiles.php
index 8d9f5f6..71d8e56 100644
--- a/app/Jobs/NodeJS/UnzipZipFiles.php
+++ b/app/Jobs/NodeJS/UnzipZipFiles.php
@@ -31,19 +31,26 @@ public function __construct($submission, $zipFileDir, $tempDir, $command)
$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);
+ $this->prepareTempDirectory();
+
+ $process = new Process(
+ $this->command,
+ null,
+ $this->getEnvironment(),
+ null,
+ 300
+ );
$process->start();
$process_pid = $process->getPid();
$process->wait();
@@ -51,23 +58,55 @@ public function handle(): void
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();
+ $error = "Failed to unzip: " . $process->getErrorOutput() . "\n" . $process->getOutput();
+ Log::error($error);
+ $this->cleanup();
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, $error);
}
} 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();
+ $error = "Failed to unzip {$this->zipFileDir} " . $th->getMessage();
+ Log::error($error);
+ $this->cleanup();
+ $this->updateSubmissionStatus($submission, Submission::$FAILED, $error);
+ Process::fromShellCommandline('kill ' . $process_pid)->run();
+ Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
+ }
+ }
+
+ protected function prepareTempDirectory(): void
+ {
+ File::ensureDirectoryExists($this->tempDir, 0755);
+
+ File::cleanDirectory($this->tempDir);
+ }
+
+ protected function getEnvironment(): array
+ {
+ return [
+ 'PATH' => '/usr/local/bin:/usr/bin:/bin:' . getenv('PATH'),
+ 'HOME' => getenv('HOME') ?: '/tmp',
+ ];
+ }
+
+ protected function cleanup(): void
+ {
+ try {
+ if (File::exists($this->tempDir)) {
+ File::deleteDirectory($this->tempDir);
+ }
+ } catch (\Throwable $th) {
+ Log::error("Cleanup failed: " . $th->getMessage());
}
}
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);
+ if ($status !== Submission::$PROCESSING) {
+ $submission->updateOneResult($stepName, $status, $output);
+ }
+ if ($status !== Submission::$COMPLETED) {
+ $submission->updateStatus($status);
+ }
}
}
diff --git a/app/Models/NodeJS/ExecutionStep.php b/app/Models/NodeJS/ExecutionStep.php
index 5992825..a427fcf 100644
--- a/app/Models/NodeJS/ExecutionStep.php
+++ b/app/Models/NodeJS/ExecutionStep.php
@@ -9,8 +9,6 @@ class ExecutionStep extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'name',
'commands',
diff --git a/app/Models/NodeJS/Project.php b/app/Models/NodeJS/Project.php
index f7b7947..2d61cf1 100644
--- a/app/Models/NodeJS/Project.php
+++ b/app/Models/NodeJS/Project.php
@@ -11,8 +11,6 @@ class Project extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'title',
'description',
diff --git a/app/Models/NodeJS/ProjectExecutionStep.php b/app/Models/NodeJS/ProjectExecutionStep.php
index a107f95..1681832 100644
--- a/app/Models/NodeJS/ProjectExecutionStep.php
+++ b/app/Models/NodeJS/ProjectExecutionStep.php
@@ -9,8 +9,6 @@ class ProjectExecutionStep extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'project_id',
'execution_step_id',
diff --git a/app/Models/NodeJS/ProjectsDefaultFileStructure.php b/app/Models/NodeJS/ProjectsDefaultFileStructure.php
index a8336c3..25aed60 100644
--- a/app/Models/NodeJS/ProjectsDefaultFileStructure.php
+++ b/app/Models/NodeJS/ProjectsDefaultFileStructure.php
@@ -3,14 +3,12 @@
namespace App\Models\NodeJS;
use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Model;;
class ProjectsDefaultFileStructure extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'project_id',
'structure',
diff --git a/app/Models/NodeJS/Submission.php b/app/Models/NodeJS/Submission.php
index b3a21de..e233452 100644
--- a/app/Models/NodeJS/Submission.php
+++ b/app/Models/NodeJS/Submission.php
@@ -10,9 +10,6 @@
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';
@@ -158,11 +155,25 @@ public function updateOneResult($step_name, $status, $output)
$this->updateResults($results);
}
- public function updateOneTestResult($step_name, $test_name, $status, $output)
- {
+ public function updateOneTestResult(
+ $step_name,
+ $test_name,
+ $status,
+ $output,
+ ?int $passedTests = null,
+ ?int $totalTests = null,
+ ?int $failedTests = null
+ ) {
$results = $this->results;
$results->$step_name->testResults->$test_name->status = $status;
$results->$step_name->testResults->$test_name->output = $output;
+
+ if ($passedTests !== null && $totalTests !== null) {
+ $results->$step_name->testResults->$test_name->passedTests = $passedTests;
+ $results->$step_name->testResults->$test_name->totalTests = $totalTests;
+ $results->$step_name->testResults->$test_name->failedTests = $failedTests;
+ }
+
$this->updateResults($results);
}
@@ -192,8 +203,12 @@ public function getCurrentExecutionStep($step_id = 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) {
+ if (
+ $results->{$step->executionStep->name}?->status == self::$PROCESSING ||
+ $results->{$step->executionStep->name}?->status == self::$PENDING
+ ) {
$current_step = $step;
break;
}
@@ -201,16 +216,29 @@ public function getCurrentExecutionStep($step_id = null)
if (!$current_step) {
$have_failed_steps = false;
+ $all_completed = true;
+
foreach ($steps as $step) {
- if ($results->{$step->executionStep->name}?->status == self::$FAILED) {
- $have_failed_steps = true;
+ if (
+ !isset($results->{$step->executionStep->name}) ||
+ $results->{$step->executionStep->name}?->status == self::$PENDING ||
+ $results->{$step->executionStep->name}?->status == self::$PROCESSING
+ ) {
+ $all_completed = false;
break;
}
+
+ if ($results->{$step->executionStep->name}?->status == self::$FAILED) {
+ $have_failed_steps = true;
+ }
}
- if ($have_failed_steps) {
- $this->updateStatus(self::$FAILED);
- } else {
- $this->updateStatus(self::$COMPLETED);
+
+ if ($all_completed) {
+ if ($have_failed_steps) {
+ $this->updateStatus(self::$FAILED);
+ } else {
+ $this->updateStatus(self::$COMPLETED);
+ }
}
}
diff --git a/app/Models/NodeJS/SubmissionHistory.php b/app/Models/NodeJS/SubmissionHistory.php
index abe2b92..f6298f6 100644
--- a/app/Models/NodeJS/SubmissionHistory.php
+++ b/app/Models/NodeJS/SubmissionHistory.php
@@ -8,9 +8,6 @@
class SubmissionHistory extends Model
{
use HasFactory;
-
- protected $connection = 'nodejsDB';
-
static $types = ['file', 'url'];
static $statues = ['pending', 'processing', 'completed', 'failed'];
static $FILE = 'file';
diff --git a/app/Models/NodeJS/TemporaryFile.php b/app/Models/NodeJS/TemporaryFile.php
index 22f0ab3..42d737a 100644
--- a/app/Models/NodeJS/TemporaryFile.php
+++ b/app/Models/NodeJS/TemporaryFile.php
@@ -9,8 +9,6 @@ class TemporaryFile extends Model
{
use HasFactory;
- protected $connection = 'nodejsDB';
-
protected $fillable = [
'folder_path',
'file_name',
diff --git a/database/seeders/NodeJS_Seeder.php b/database/seeders/NodeJS_Seeder.php
index cfa595f..82c9439 100644
--- a/database/seeders/NodeJS_Seeder.php
+++ b/database/seeders/NodeJS_Seeder.php
@@ -4,7 +4,6 @@
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
-use App\Models\User;
use Illuminate\Support\Facades\Hash;
use App\Models\NodeJS\Project;
use App\Models\NodeJS\ProjectsDefaultFileStructure;
@@ -18,344 +17,16 @@ class NodeJS_Seeder extends Seeder
*/
public function run(): void
{
- // user
- User::create([
- 'name' => 'Omar',
- 'email' => 'omar.yem1111@gmail.com',
- 'password' => Hash::make('123456789'),
- 'created_at' => now(),
- 'updated_at' => now(),
- ]);
-
- // projects
- Project::insert([[
- 'title' => 'api-experiment',
- 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
- 'tech_stack' => json_encode([
- 'framework' => 'ExpressJS',
- 'language' => 'NodeJS',
- 'database' => 'MongoDB',
- 'testing' => 'Jest, Supertest, Puppeteer',
- ]),
- 'github_url' => 'https://github.com/Omar630603/api-experiment',
- 'image' => 'image',
- 'created_at' => now(),
- 'updated_at' => now(),
- ], [
- 'title' => 'auth-experiment',
- 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
- 'tech_stack' => json_encode([
- 'framework' => 'ExpressJS',
- 'language' => 'NodeJS',
- 'database' => 'MongoDB',
- 'testing' => 'Jest, Supertest, Puppeteer',
- ]),
- 'github_url' => 'https://github.com/Omar630603/auth-experiment',
- 'image' => 'image',
- 'created_at' => now(),
- 'updated_at' => now(),
- ]]);
-
-
- $project_api_experiment = Project::where('title', 'api-experiment')->first();
- $project_auth_experiment = Project::where('title', 'auth-experiment')->first();
-
- // images
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/images/api-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/images/auth-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
-//
-// // files
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
-//
-// // tests
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/create-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/no-products-found-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/not-found-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/product-details-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/products-table-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/update-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-password-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page-after-register.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page-with-error.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/profile-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/register-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
-//
-// // guides
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
-//
-// // supplements
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/initial_data.json'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
-//
-// // zips
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-//
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-// $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
-
- $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
- $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
-
- // project default file structure
- ProjectsDefaultFileStructure::insert([
- [
- 'project_id' => $api_experiment_project_id,
- 'structure' => json_encode([
- 'controllers' => [
- 'api' => [
- 'product.controller.js' => '',
- ],
- 'web' => [
- 'product.controller.js' => '',
- ],
- ],
- 'models' => [
- 'product.model.js' => '',
- ],
- 'node_modules' => '',
- 'routes' => [
- 'api' => [
- 'product.routes.js' => '',
- ],
- 'web' => [
- 'product.routes.js' => '',
- ],
- ],
- 'tests' => [
- 'api' => [
- 'testA01.test.js' => '',
- 'testA02.test.js' => '',
- 'testA03.test.js' => '',
- 'testA04.test.js' => '',
- 'testA05.test.js' => '',
- ],
- 'web' => [
- 'images' => [
- 'create-product-page.png' => '',
- 'error-notFound-page.png' => '',
- 'index-page.png' => '',
- 'no-products-found-page.png' => '',
- 'not-found-product-page.png' => '',
- 'product-details-page.png' => '',
- 'products-table-page.png' => '',
- 'update-product-page.png' => '',
- ],
- 'testA01.test.js' => '',
- 'testA02.test.js' => '',
- 'testA03.test.js' => '',
- 'testA04.test.js' => '',
- 'testA05.test.js' => '',
- ],
- ],
- 'web' => [
- 'layouts' => [
- 'main.ejs' => '',
- ],
- 'styles' => [
- 'main.css' => '',
- ],
- 'views' => [
- 'products' => [
- 'create.ejs' => '',
- 'details.ejs' => '',
- 'index.ejs' => '',
- 'update.ejs' => '',
- ],
- 'error.ejs' => '',
- 'index.ejs' => '',
- ],
- ],
- '.env' => '',
- '.env.example' => '',
- '.gitignore' => '',
- 'app.js' => '',
- 'initial_data.json' => '',
- 'package-lock.json' => '',
- 'package.json' => '',
- 'README' => '',
- 'server.js' => '',
- ]),
- 'excluded' => json_encode([
- 'node_modules',
- 'tests',
- '.env',
- '.env.example',
- '.gitignore',
- 'package-lock.json',
- 'initial_data.json',
- 'README',
- ]),
- 'replacements' => json_encode([
- '.env',
- 'tests',
- 'package.json'
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'structure' => json_encode([
- 'controllers' => [
- 'api' => [
- 'auth.controller.js' => '',
- ],
- 'web' => [
- 'auth.controller.js' => '',
- ],
- ],
- 'helpers' => [
- 'errorhandler.helper.js' => '',
- 'jsonwebtoken.helper.js' => '',
- ],
- 'models' => [
- 'user.model.js' => '',
- ],
- 'node_modules' => '',
- 'routes' => [
- 'api' => [
- 'auth.routes.js' => '',
- ],
- 'web' => [
- 'auth.routes.js' => '',
- ],
- ],
- 'services' => [
- 'auth.service.js' => '',
- ],
- 'tests' => [
- 'api' => [
- 'testB01.test.js' => '',
- 'testB02.test.js' => '',
- 'testB03.test.js' => '',
- 'testB04.test.js' => '',
- 'testB05.test.js' => '',
- ],
- 'web' => [
- 'images' => [
- 'edit-page.png' => '',
- 'edit-password-page.png' => '',
- 'error-notFound-page.png' => '',
- 'index-page.png' => '',
- 'index-page-after-register.png' => '',
- 'login-page.png' => '',
- 'login-page-with-error.png' => '',
- 'profile-page.png' => '',
- 'register-page.png' => '',
- ],
- 'testB01.test.js' => '',
- 'testB02.test.js' => '',
- 'testB03.test.js' => '',
- 'testB04.test.js' => '',
- 'testB05.test.js' => '',
- ],
- ],
- 'web' => [
- 'layouts' => [
- 'main.ejs' => '',
- ],
- 'styles' => [
- 'main.css' => '',
- ],
- 'views' => [
- 'auth' => [
- 'edit.ejs' => '',
- 'login.ejs' => '',
- 'profile.ejs' => '',
- 'register.ejs' => '',
- ],
- 'error.ejs' => '',
- 'index.ejs' => '',
- ],
- ],
- '.env' => '',
- '.env.example' => '',
- '.gitignore' => '',
- 'app.js' => '',
- 'package-lock.json' => '',
- 'package.json' => '',
- 'README' => '',
- 'server.js' => '',
- ]),
- 'excluded' => json_encode([
- 'node_modules',
- 'tests',
- '.env',
- '.env.example',
- '.gitignore',
- 'package-lock.json',
- 'README',
- ]),
- 'replacements' => json_encode([
- '.env',
- 'tests',
- 'package.json'
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ]
-
- ]);
-
- // execution steps
+ // Seed Execution Steps
ExecutionStep::insert([
[
'name' => 'Clone Repository',
'commands' => json_encode([
- 'git', 'clone', '{{repoUrl}}', '{{tempDir}}',
+ 'git',
+ 'clone',
+ '--depth=1',
+ '{{repoUrl}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -363,7 +34,11 @@ public function run(): void
[
'name' => 'Unzip ZIP Files',
'commands' => json_encode([
- 'unzip', '{{zipFileDir}}', '-d', '{{tempDir}}',
+ 'unzip',
+ '-o',
+ '{{zipFileDir}}',
+ '-d',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -371,7 +46,9 @@ public function run(): void
[
'name' => 'Remove ZIP Files',
'commands' => json_encode([
- 'rm', '-rf', '{{zipFileDir}}',
+ 'rm',
+ '-rf',
+ '{{zipFileDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -379,7 +56,8 @@ public function run(): void
[
'name' => 'Examine Folder Structure',
'commands' => json_encode([
- 'ls', '{{tempDir}}',
+ 'ls',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -387,7 +65,10 @@ public function run(): void
[
'name' => 'Add .env File',
'commands' => json_encode([
- 'cp', '-r', '{{envFile}}', '{{tempDir}}',
+ 'cp',
+ '-r',
+ '{{envFile}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -395,7 +76,10 @@ public function run(): void
[
'name' => 'Replace package.json',
'commands' => json_encode([
- 'cp', '-r', '{{packageJson}}', '{{tempDir}}',
+ 'cp',
+ '-r',
+ '{{packageJson}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -403,7 +87,10 @@ public function run(): void
[
'name' => "Copy 'tests' Folder",
'commands' => json_encode([
- 'cp', '-r', '{{testsDir}}', '{{tempDir}}',
+ 'cp',
+ '-r',
+ '{{sourceFile}}',
+ '{{destinationFile}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -411,7 +98,9 @@ public function run(): void
[
'name' => 'NPM Install',
'commands' => json_encode([
- 'npm', 'install', '{{options}}',
+ 'npm',
+ 'install',
+ '{{options}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -419,7 +108,9 @@ public function run(): void
[
'name' => 'NPM Run Start',
'commands' => json_encode([
- 'npm', 'run', 'start',
+ 'npm',
+ 'run',
+ 'start',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -427,7 +118,9 @@ public function run(): void
[
'name' => 'NPM Run Tests',
'commands' => json_encode([
- 'npm', 'run', '{{testFile}}',
+ 'npx',
+ 'jest',
+ '{{testFile}}',
]),
'created_at' => now(),
'updated_at' => now(),
@@ -435,51 +128,121 @@ public function run(): void
[
'name' => 'Delete Temp Directory',
'commands' => json_encode([
- 'rm', '-rf', '{{tempDir}}',
+ 'rm',
+ '-rf',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
]
]);
+
+ // Seed the project
+ Project::insert([
+ [
+ 'title' => 'asynchronous-programming',
+ 'description' => 'Proyek ini adalah pengembangan backend untuk sistem reservasi restoran berbasis Node.js yang dikembangkan secara bertahap untuk mempelajari konsep asynchronous programming dan error handling.
+ Mahasiswa akan memulai dengan setup project, kemudian mengimplementasikan berbagai pola asynchronous seperti callback, Promise, dan Async/Await untuk mengelola menu restoran, reservasi meja, dan pesanan.
+ Selain itu, proyek ini juga mencakup error handling yang efektif menggunakan middleware untuk menangani kesalahan selama operasi database, validasi input, dan proses asynchronous lainnya',
+ 'tech_stack' => json_encode([
+ 'framework' => 'ExpressJS',
+ 'language' => 'NodeJS',
+ 'database' => 'MongoDB',
+ ]),
+ 'github_url' => 'https://github.com/Omar630603/auth-experiment',
+ 'image' => 'image',
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]
+ ]);
- // execution step projects
- $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
- $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
+ $project_asynchronous_programming = Project::where('title', 'asynchronous-programming')->first();
- $clone_repo_execution_step_id = ExecutionStep::where('name', ExecutionStep::$CLONE_REPOSITORY)->first()->id;
- $unzip_zip_files_execution_step_id = ExecutionStep::where('name', ExecutionStep::$UNZIP_ZIP_FILES)->first()->id;
- $checking_folder_structure_execution_step_id = ExecutionStep::where('name', ExecutionStep::$EXAMINE_FOLDER_STRUCTURE)->first()->id;
- $add_env_file_execution_step_id = ExecutionStep::where('name', ExecutionStep::$ADD_ENV_FILE)->first()->id;
- $replace_package_json_execution_step_id = ExecutionStep::where('name', ExecutionStep::$REPLACE_PACKAGE_JSON)->first()->id;
- $copy_tests_folder_step_id = ExecutionStep::where('name', ExecutionStep::$COPY_TESTS_FOLDER)->first()->id;
- $npm_install_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_INSTALL)->first()->id;
- $npm_run_start_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_START)->first()->id;
- $npm_run_tests_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_TESTS)->first()->id;
- $delete_temp_directory_execution_step_id = ExecutionStep::where('name', ExecutionStep::$DELETE_TEMP_DIRECTORY)->first()->id;
+ //images
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/images/asynchronous-programming.png'))->preservingOriginal()->toMediaCollection('project_images', 'nodejs_public_projects_files')->preserve;
+
+ //files
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/files/.env'))->preservingOriginal()->toMediaCollection('project_files', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/files/package.json'))->preservingOriginal()->toMediaCollection('project_files', 'nodejs_public_projects_files');
+
+ //guides
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP01.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP02.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP03.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP04.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/guides/Guide ASP05.pdf'))->preservingOriginal()->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+
+ //supplements
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/supplements/.env.example'))->preservingOriginal()->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/supplements/.gitignore'))->preservingOriginal()->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+
+ //zips
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/zips/guides.zip'))->preservingOriginal()->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/zips/supplements.zip'))->preservingOriginal()->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+
+ //tests
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul1-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul2-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul3-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul4-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/unit/modul5-unit.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul1-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul2-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul3-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul4-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+ $project_asynchronous_programming->addMedia(storage_path('projects/asynchronous-programming/tests/integration/modul5-integration.test.js'))->preservingOriginal()->toMediaCollection('project_tests', 'nodejs_public_projects_files');
+
+ // Get the project ID for associating execution steps
+ $async_experiment_project_id = Project::where('title', 'asynchronous-programming')->first()->id;
+
+ // Get execution step IDs
+ $clone_repo_execution_step_id = ExecutionStep::where('name', 'Clone Repository')->first()->id;
+ $unzip_zip_files_execution_step_id = ExecutionStep::where('name', 'Unzip ZIP Files')->first()->id;
+ $checking_folder_structure_execution_step_id = ExecutionStep::where('name', 'Examine Folder Structure')->first()->id;
+ $add_env_file_execution_step_id = ExecutionStep::where('name', 'Add .env File')->first()->id;
+ $replace_package_json_execution_step_id = ExecutionStep::where('name', 'Replace package.json')->first()->id;
+ $copy_tests_folder_step_id = ExecutionStep::where('name', "Copy 'tests' Folder")->first()->id;
+ $npm_install_execution_step_id = ExecutionStep::where('name', 'NPM Install')->first()->id;
+ $npm_run_start_execution_step_id = ExecutionStep::where('name', 'NPM Run Start')->first()->id;
+ $npm_run_tests_execution_step_id = ExecutionStep::where('name', 'NPM Run Tests')->first()->id;
+ $delete_temp_directory_execution_step_id = ExecutionStep::where('name', 'Delete Temp Directory')->first()->id;
+
+ $clone_repo_execution_step_id = ExecutionStep::where('name', 'Clone Repository')->first()->id;
+ $unzip_zip_files_execution_step_id = ExecutionStep::where('name', 'Unzip ZIP Files')->first()->id;
+ $checking_folder_structure_execution_step_id = ExecutionStep::where('name', 'Examine Folder Structure')->first()->id;
+ $add_env_file_execution_step_id = ExecutionStep::where('name', 'Add .env File')->first()->id;
+ $replace_package_json_execution_step_id = ExecutionStep::where('name', 'Replace package.json')->first()->id;
+ $copy_tests_folder_step_id = ExecutionStep::where('name', "Copy 'tests' Folder")->first()->id;
+ $npm_install_execution_step_id = ExecutionStep::where('name', 'NPM Install')->first()->id;
+ $npm_run_start_execution_step_id = ExecutionStep::where('name', 'NPM Run Start')->first()->id;
+ $npm_run_tests_execution_step_id = ExecutionStep::where('name', 'NPM Run Tests')->first()->id;
+ $delete_temp_directory_execution_step_id = ExecutionStep::where('name', 'Delete Temp Directory')->first()->id;
ProjectExecutionStep::insert([
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $clone_repo_execution_step_id,
'order' => 1,
'variables' => json_encode([
- '{{repoUrl}}', '{{tempDir}}',
+ '{{repoUrl}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $unzip_zip_files_execution_step_id,
'order' => 2,
'variables' => json_encode([
- '{{zipFileDir}}', '{{tempDir}}'
+ '{{zipFileDir}}',
+ '{{tempDir}}'
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $checking_folder_structure_execution_step_id,
'order' => 3,
'variables' => json_encode([
@@ -489,37 +252,52 @@ public function run(): void
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $add_env_file_execution_step_id,
'order' => 4,
'variables' => json_encode([
- '{{envFile}}', '{{tempDir}}',
+ '{{envFile}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $replace_package_json_execution_step_id,
'order' => 5,
'variables' => json_encode([
- '{{packageJson}}', '{{tempDir}}',
+ '{{packageJson}}',
+ '{{tempDir}}',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $copy_tests_folder_step_id,
'order' => 6,
'variables' => json_encode([
- '{{testsDir}}', '{{tempDir}}',
+ // Format: {{sourceFile}}=media:filename:subfolder
+ // Contoh:
+ // '{{sourceFile}}=api:testfile.js:api' - places file in tests/api folder
+ // '{{sourceFile}}=web:homepage.test.js:web/integration' - places file in tests/web/integration folder
+ '{{sourceFile}}=tests:modul1-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul2-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul3-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul4-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul5-unit.test.js:unit',
+ '{{sourceFile}}=tests:modul1-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul2-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul3-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul4-integration.test.js:integration',
+ '{{sourceFile}}=tests:modul5-integration.test.js:integration',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $npm_install_execution_step_id,
'order' => 7,
'variables' => json_encode([
@@ -529,7 +307,7 @@ public function run(): void
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $npm_run_start_execution_step_id,
'order' => 8,
'variables' => null,
@@ -537,26 +315,26 @@ public function run(): void
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $npm_run_tests_execution_step_id,
'order' => 9,
'variables' => json_encode([
- '{{testFile}}=api-testA01',
- '{{testFile}}=web-testA01',
- '{{testFile}}=api-testA02',
- '{{testFile}}=web-testA02',
- '{{testFile}}=api-testA03',
- '{{testFile}}=web-testA03',
- '{{testFile}}=api-testA04',
- '{{testFile}}=web-testA04',
- '{{testFile}}=api-testA05',
- '{{testFile}}=web-testA05',
+ '{{testFile}}=modul1-unit.test.js',
+ '{{testFile}}=modul2-unit.test.js',
+ '{{testFile}}=modul3-unit.test.js',
+ '{{testFile}}=modul4-unit.test.js',
+ '{{testFile}}=modul5-unit.test.js',
+ '{{testFile}}=modul1-integration.test.js',
+ '{{testFile}}=modul2-integration.test.js',
+ '{{testFile}}=modul3-integration.test.js',
+ '{{testFile}}=modul4-integration.test.js',
+ '{{testFile}}=modul5-integration.test.js',
]),
'created_at' => now(),
'updated_at' => now(),
],
[
- 'project_id' => $api_experiment_project_id,
+ 'project_id' => $async_experiment_project_id,
'execution_step_id' => $delete_temp_directory_execution_step_id,
'order' => 10,
'variables' => json_encode([
@@ -565,113 +343,727 @@ public function run(): void
'created_at' => now(),
'updated_at' => now(),
],
+ ]);
+
+ $async_experiment_project_id = Project::where('title', 'asynchronous-programming')->first()->id;
+
+ ProjectsDefaultFileStructure::insert([
[
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $clone_repo_execution_step_id,
- 'order' => 1,
- 'variables' => json_encode([
- '{{repoUrl}}', '{{tempDir}}',
+ 'project_id' => $async_experiment_project_id,
+ 'structure' => json_encode([
+ 'src' => [
+ 'config' => [
+ 'database.js' => '',
+ ],
+ 'controllers' => [
+ 'mejaController.js' => '',
+ 'menuController.js' => '',
+ 'orderController.js' => '',
+ ],
+ 'models' => [
+ 'mejaModel.js' => '',
+ 'menuModel.js' => '',
+ 'orderModel.js' => '',
+ ],
+ 'middleware' => [
+ 'errorHandler.js' => '',
+ ],
+ 'routes' => [
+ 'mejaRoutes.js' => '',
+ 'menuRoutes.js' => '',
+ 'orderRoutes.js' => '',
+ ],
+ ],
+ 'node_modules' => '',
+ 'tests' => [],
+ '.env' => '',
+ '.env.example' => '',
+ '.gitignore' => '',
+ 'app.js' => '',
+ 'package-lock.json' => '',
+ 'package.json' => '',
+ 'README' => '',
+ 'server.js' => '',
]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $unzip_zip_files_execution_step_id,
- 'order' => 2,
- 'variables' => json_encode([
- '{{zipFileDir}}', '{{tempDir}}'
+ 'excluded' => json_encode([
+ 'node_modules',
+ 'tests',
+ '.env',
+ '.env.example',
+ '.gitignore',
+ 'package-lock.json',
+ 'README',
]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $checking_folder_structure_execution_step_id,
- 'order' => 3,
- 'variables' => json_encode([
- '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $add_env_file_execution_step_id,
- 'order' => 4,
- 'variables' => json_encode([
- '{{envFile}}', '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $replace_package_json_execution_step_id,
- 'order' => 5,
- 'variables' => json_encode([
- '{{packageJson}}', '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $copy_tests_folder_step_id,
- 'order' => 6,
- 'variables' => json_encode([
- '{{testsDir}}', '{{tempDir}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $npm_install_execution_step_id,
- 'order' => 7,
- 'variables' => json_encode([
- '{{options}}',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $npm_run_start_execution_step_id,
- 'order' => 8,
- 'variables' => null,
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $npm_run_tests_execution_step_id,
- 'order' => 9,
- 'variables' => json_encode([
- '{{testFile}}=api-testB01',
- '{{testFile}}=web-testB01',
- '{{testFile}}=api-testB02',
- '{{testFile}}=web-testB02',
- '{{testFile}}=api-testB03',
- '{{testFile}}=web-testB03',
- '{{testFile}}=api-testB04',
- '{{testFile}}=web-testB04',
- '{{testFile}}=api-testB05',
- '{{testFile}}=web-testB05',
- ]),
- 'created_at' => now(),
- 'updated_at' => now(),
- ],
- [
- 'project_id' => $auth_experiment_project_id,
- 'execution_step_id' => $delete_temp_directory_execution_step_id,
- 'order' => 10,
- 'variables' => json_encode([
- '{{tempDir}}',
+ 'replacements' => json_encode([
+ '.env',
+ 'tests',
+ 'package.json'
]),
'created_at' => now(),
'updated_at' => now(),
],
]);
}
+
+ /**
+ * Seed project default file structure
+ */
+// public function run(): void
+// {
+// // user
+// User::create([
+// 'name' => 'Omar',
+// 'email' => 'omar.yem1111@gmail.com',
+// 'password' => Hash::make('123456789'),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]);
+
+// // projects
+// Project::insert([[
+// 'title' => 'api-experiment',
+// 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
+// 'tech_stack' => json_encode([
+// 'framework' => 'ExpressJS',
+// 'language' => 'NodeJS',
+// 'database' => 'MongoDB',
+// 'testing' => 'Jest, Supertest, Puppeteer',
+// ]),
+// 'github_url' => 'https://github.com/Omar630603/api-experiment',
+// 'image' => 'image',
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ], [
+// 'title' => 'auth-experiment',
+// 'description' => 'This is an API and web project using NodeJS, ExpressJS, and MongoDB. The goal of this project is to try testing API endpoints and Web pages using Jest, Supertest, and Puppeteer.',
+// 'tech_stack' => json_encode([
+// 'framework' => 'ExpressJS',
+// 'language' => 'NodeJS',
+// 'database' => 'MongoDB',
+// 'testing' => 'Jest, Supertest, Puppeteer',
+// ]),
+// 'github_url' => 'https://github.com/Omar630603/auth-experiment',
+// 'image' => 'image',
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]]);
+
+
+// $project_api_experiment = Project::where('title', 'api-experiment')->first();
+// $project_auth_experiment = Project::where('title', 'auth-experiment')->first();
+
+// // images
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/images/api-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/images/auth-experiment.png'))->toMediaCollection('project_images', 'nodejs_public_projects_files');
+// //
+// // // files
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/.env'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/files/package.json'))->toMediaCollection('project_files', 'nodejs_public_projects_files');
+// //
+// // // tests
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/api/testA05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/testA05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/create-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/no-products-found-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/not-found-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/product-details-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/products-table-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/tests/web/images/update-product-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB01.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB02.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB03.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB04.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/api/testB05.test.js'))->toMediaCollection('project_tests_api', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB01.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB02.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB03.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB04.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/testB05.test.js'))->toMediaCollection('project_tests_web', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/edit-password-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/error-notFound-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/index-page-after-register.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/login-page-with-error.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/profile-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/tests/web/images/register-page.png'))->toMediaCollection('project_tests_images', 'nodejs_public_projects_files');
+// //
+// // // guides
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/guides/Guide A05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B01.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B02.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B03.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B04.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/guides/Guide B05.pdf'))->toMediaCollection('project_guides', 'nodejs_public_projects_files');
+// //
+// // // supplements
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/initial_data.json'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.env.example'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/.gitignore'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.css'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/supplements/main.ejs'))->toMediaCollection('project_supplements', 'nodejs_public_projects_files');
+// //
+// // // zips
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_api_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/api-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// //
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/guides.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/supplements.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+// // $project_auth_experiment->addMedia(storage_path('app/public/assets/nodejs/projects/auth-experiment/zips/tests.zip'))->toMediaCollection('project_zips', 'nodejs_public_projects_files');
+
+// $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
+// $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
+
+// // project default file structure
+// ProjectsDefaultFileStructure::insert([
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'structure' => json_encode([
+// 'controllers' => [
+// 'api' => [
+// 'product.controller.js' => '',
+// ],
+// 'web' => [
+// 'product.controller.js' => '',
+// ],
+// ],
+// 'models' => [
+// 'product.model.js' => '',
+// ],
+// 'node_modules' => '',
+// 'routes' => [
+// 'api' => [
+// 'product.routes.js' => '',
+// ],
+// 'web' => [
+// 'product.routes.js' => '',
+// ],
+// ],
+// 'tests' => [
+// 'api' => [
+// 'testA01.test.js' => '',
+// 'testA02.test.js' => '',
+// 'testA03.test.js' => '',
+// 'testA04.test.js' => '',
+// 'testA05.test.js' => '',
+// ],
+// 'web' => [
+// 'images' => [
+// 'create-product-page.png' => '',
+// 'error-notFound-page.png' => '',
+// 'index-page.png' => '',
+// 'no-products-found-page.png' => '',
+// 'not-found-product-page.png' => '',
+// 'product-details-page.png' => '',
+// 'products-table-page.png' => '',
+// 'update-product-page.png' => '',
+// ],
+// 'testA01.test.js' => '',
+// 'testA02.test.js' => '',
+// 'testA03.test.js' => '',
+// 'testA04.test.js' => '',
+// 'testA05.test.js' => '',
+// ],
+// ],
+// 'web' => [
+// 'layouts' => [
+// 'main.ejs' => '',
+// ],
+// 'styles' => [
+// 'main.css' => '',
+// ],
+// 'views' => [
+// 'products' => [
+// 'create.ejs' => '',
+// 'details.ejs' => '',
+// 'index.ejs' => '',
+// 'update.ejs' => '',
+// ],
+// 'error.ejs' => '',
+// 'index.ejs' => '',
+// ],
+// ],
+// '.env' => '',
+// '.env.example' => '',
+// '.gitignore' => '',
+// 'app.js' => '',
+// 'initial_data.json' => '',
+// 'package-lock.json' => '',
+// 'package.json' => '',
+// 'README' => '',
+// 'server.js' => '',
+// ]),
+// 'excluded' => json_encode([
+// 'node_modules',
+// 'tests',
+// '.env',
+// '.env.example',
+// '.gitignore',
+// 'package-lock.json',
+// 'initial_data.json',
+// 'README',
+// ]),
+// 'replacements' => json_encode([
+// '.env',
+// 'tests',
+// 'package.json'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'structure' => json_encode([
+// 'controllers' => [
+// 'api' => [
+// 'auth.controller.js' => '',
+// ],
+// 'web' => [
+// 'auth.controller.js' => '',
+// ],
+// ],
+// 'helpers' => [
+// 'errorhandler.helper.js' => '',
+// 'jsonwebtoken.helper.js' => '',
+// ],
+// 'models' => [
+// 'user.model.js' => '',
+// ],
+// 'node_modules' => '',
+// 'routes' => [
+// 'api' => [
+// 'auth.routes.js' => '',
+// ],
+// 'web' => [
+// 'auth.routes.js' => '',
+// ],
+// ],
+// 'services' => [
+// 'auth.service.js' => '',
+// ],
+// 'tests' => [
+// 'api' => [
+// 'testB01.test.js' => '',
+// 'testB02.test.js' => '',
+// 'testB03.test.js' => '',
+// 'testB04.test.js' => '',
+// 'testB05.test.js' => '',
+// ],
+// 'web' => [
+// 'images' => [
+// 'edit-page.png' => '',
+// 'edit-password-page.png' => '',
+// 'error-notFound-page.png' => '',
+// 'index-page.png' => '',
+// 'index-page-after-register.png' => '',
+// 'login-page.png' => '',
+// 'login-page-with-error.png' => '',
+// 'profile-page.png' => '',
+// 'register-page.png' => '',
+// ],
+// 'testB01.test.js' => '',
+// 'testB02.test.js' => '',
+// 'testB03.test.js' => '',
+// 'testB04.test.js' => '',
+// 'testB05.test.js' => '',
+// ],
+// ],
+// 'web' => [
+// 'layouts' => [
+// 'main.ejs' => '',
+// ],
+// 'styles' => [
+// 'main.css' => '',
+// ],
+// 'views' => [
+// 'auth' => [
+// 'edit.ejs' => '',
+// 'login.ejs' => '',
+// 'profile.ejs' => '',
+// 'register.ejs' => '',
+// ],
+// 'error.ejs' => '',
+// 'index.ejs' => '',
+// ],
+// ],
+// '.env' => '',
+// '.env.example' => '',
+// '.gitignore' => '',
+// 'app.js' => '',
+// 'package-lock.json' => '',
+// 'package.json' => '',
+// 'README' => '',
+// 'server.js' => '',
+// ]),
+// 'excluded' => json_encode([
+// 'node_modules',
+// 'tests',
+// '.env',
+// '.env.example',
+// '.gitignore',
+// 'package-lock.json',
+// 'README',
+// ]),
+// 'replacements' => json_encode([
+// '.env',
+// 'tests',
+// 'package.json'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]
+
+// ]);
+
+// // execution steps
+// ExecutionStep::insert([
+// [
+// 'name' => 'Clone Repository',
+// 'commands' => json_encode([
+// 'git', 'clone', '{{repoUrl}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Unzip ZIP Files',
+// 'commands' => json_encode([
+// 'unzip', '{{zipFileDir}}', '-d', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Remove ZIP Files',
+// 'commands' => json_encode([
+// 'rm', '-rf', '{{zipFileDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Examine Folder Structure',
+// 'commands' => json_encode([
+// 'ls', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Add .env File',
+// 'commands' => json_encode([
+// 'cp', '-r', '{{envFile}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Replace package.json',
+// 'commands' => json_encode([
+// 'cp', '-r', '{{packageJson}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => "Copy 'tests' Folder",
+// 'commands' => json_encode([
+// 'cp', '-r', '{{testsDir}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'NPM Install',
+// 'commands' => json_encode([
+// 'npm', 'install', '{{options}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'NPM Run Start',
+// 'commands' => json_encode([
+// 'npm', 'run', 'start',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'NPM Run Tests',
+// 'commands' => json_encode([
+// 'npm', 'run', '{{testFile}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'name' => 'Delete Temp Directory',
+// 'commands' => json_encode([
+// 'rm', '-rf', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ]
+// ]);
+
+// // execution step projects
+// $api_experiment_project_id = Project::where('title', 'api-experiment')->first()->id;
+// $auth_experiment_project_id = Project::where('title', 'auth-experiment')->first()->id;
+
+// $clone_repo_execution_step_id = ExecutionStep::where('name', ExecutionStep::$CLONE_REPOSITORY)->first()->id;
+// $unzip_zip_files_execution_step_id = ExecutionStep::where('name', ExecutionStep::$UNZIP_ZIP_FILES)->first()->id;
+// $checking_folder_structure_execution_step_id = ExecutionStep::where('name', ExecutionStep::$EXAMINE_FOLDER_STRUCTURE)->first()->id;
+// $add_env_file_execution_step_id = ExecutionStep::where('name', ExecutionStep::$ADD_ENV_FILE)->first()->id;
+// $replace_package_json_execution_step_id = ExecutionStep::where('name', ExecutionStep::$REPLACE_PACKAGE_JSON)->first()->id;
+// $copy_tests_folder_step_id = ExecutionStep::where('name', ExecutionStep::$COPY_TESTS_FOLDER)->first()->id;
+// $npm_install_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_INSTALL)->first()->id;
+// $npm_run_start_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_START)->first()->id;
+// $npm_run_tests_execution_step_id = ExecutionStep::where('name', ExecutionStep::$NPM_RUN_TESTS)->first()->id;
+// $delete_temp_directory_execution_step_id = ExecutionStep::where('name', ExecutionStep::$DELETE_TEMP_DIRECTORY)->first()->id;
+
+// ProjectExecutionStep::insert([
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $clone_repo_execution_step_id,
+// 'order' => 1,
+// 'variables' => json_encode([
+// '{{repoUrl}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $unzip_zip_files_execution_step_id,
+// 'order' => 2,
+// 'variables' => json_encode([
+// '{{zipFileDir}}', '{{tempDir}}'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $checking_folder_structure_execution_step_id,
+// 'order' => 3,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $add_env_file_execution_step_id,
+// 'order' => 4,
+// 'variables' => json_encode([
+// '{{envFile}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $replace_package_json_execution_step_id,
+// 'order' => 5,
+// 'variables' => json_encode([
+// '{{packageJson}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $copy_tests_folder_step_id,
+// 'order' => 6,
+// 'variables' => json_encode([
+// '{{testsDir}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $npm_install_execution_step_id,
+// 'order' => 7,
+// 'variables' => json_encode([
+// '{{options}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $npm_run_start_execution_step_id,
+// 'order' => 8,
+// 'variables' => null,
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $npm_run_tests_execution_step_id,
+// 'order' => 9,
+// 'variables' => json_encode([
+// '{{testFile}}=api-testA01',
+// '{{testFile}}=web-testA01',
+// '{{testFile}}=api-testA02',
+// '{{testFile}}=web-testA02',
+// '{{testFile}}=api-testA03',
+// '{{testFile}}=web-testA03',
+// '{{testFile}}=api-testA04',
+// '{{testFile}}=web-testA04',
+// '{{testFile}}=api-testA05',
+// '{{testFile}}=web-testA05',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $api_experiment_project_id,
+// 'execution_step_id' => $delete_temp_directory_execution_step_id,
+// 'order' => 10,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $clone_repo_execution_step_id,
+// 'order' => 1,
+// 'variables' => json_encode([
+// '{{repoUrl}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $unzip_zip_files_execution_step_id,
+// 'order' => 2,
+// 'variables' => json_encode([
+// '{{zipFileDir}}', '{{tempDir}}'
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $checking_folder_structure_execution_step_id,
+// 'order' => 3,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $add_env_file_execution_step_id,
+// 'order' => 4,
+// 'variables' => json_encode([
+// '{{envFile}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $replace_package_json_execution_step_id,
+// 'order' => 5,
+// 'variables' => json_encode([
+// '{{packageJson}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $copy_tests_folder_step_id,
+// 'order' => 6,
+// 'variables' => json_encode([
+// '{{testsDir}}', '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $npm_install_execution_step_id,
+// 'order' => 7,
+// 'variables' => json_encode([
+// '{{options}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $npm_run_start_execution_step_id,
+// 'order' => 8,
+// 'variables' => null,
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $npm_run_tests_execution_step_id,
+// 'order' => 9,
+// 'variables' => json_encode([
+// '{{testFile}}=api-testB01',
+// '{{testFile}}=web-testB01',
+// '{{testFile}}=api-testB02',
+// '{{testFile}}=web-testB02',
+// '{{testFile}}=api-testB03',
+// '{{testFile}}=web-testB03',
+// '{{testFile}}=api-testB04',
+// '{{testFile}}=web-testB04',
+// '{{testFile}}=api-testB05',
+// '{{testFile}}=web-testB05',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// [
+// 'project_id' => $auth_experiment_project_id,
+// 'execution_step_id' => $delete_temp_directory_execution_step_id,
+// 'order' => 10,
+// 'variables' => json_encode([
+// '{{tempDir}}',
+// ]),
+// 'created_at' => now(),
+// 'updated_at' => now(),
+// ],
+// ]);
+// }
}
diff --git a/package-lock.json b/package-lock.json
index 7f66dc3..bb0c951 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "iCLOP-V2",
+ "name": "iCLOP_V3",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/files/package.json b/public/assets/nodejs/projects/asycnchronous-programming/files/package.json
new file mode 100644
index 0000000..ae27766
--- /dev/null
+++ b/public/assets/nodejs/projects/asycnchronous-programming/files/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "restaurant-reservation",
+ "version": "1.0.0",
+ "description": "1. **Buat folder proyek** \r ```sh\r npm init -y",
+ "main": "app.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "nodemon server.js",
+ "praktikum1": "cross-env NODE_ENV=test jest -i test/praktikum1.test.js --testTimeout=20000",
+ "praktikum2": "cross-env NODE_ENV=test jest -i --verbose test/praktikum2Unit.test.js test/praktikum2Integration.test.js --testTimeout=20000",
+ "praktikum3": "cross-env NODE_ENV=test jest -i --verbose test/praktikum3Unit.test.js test/praktikum3Integration.test.js --testTimeout=20000"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "mongodb": "^6.13.1",
+ "mongoose": "^8.10.1"
+ },
+ "devDependencies": {
+ "cross-env": "^7.0.3",
+ "fs-extra": "^11.3.0",
+ "jest": "^29.7.0",
+ "nodemon": "^3.1.9",
+ "supertest": "^7.0.0"
+ }
+}
diff --git a/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP01.pdf b/public/assets/nodejs/projects/asycnchronous-programming/guides/Guide ASP01.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..64171e4ab436856598533b49b72ae64e43d67f70
GIT binary patch
literal 4072637
zcmdqJ1z40#_Xi9HAtf!nfPmz}(jp+;jr4-V0!w#?fGCZ0hqQEesibr_C@I}7@GbcG
zJo-Mq@vrN
@a
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