add project

This commit is contained in:
abiyasa05 2025-03-22 15:18:38 +07:00
commit fca26f144f
3706 changed files with 1220132 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# Mengabaikan folder vendor Laravel
/vendor/
# Mengabaikan file konfigurasi environment
.env
# Mengabaikan folder storage di Laravel
/storage/
# Mengabaikan file cache dan logs di Laravel
/*.log
/*.cache
# Mengabaikan folder dan file terkait Python
venv/
__pycache__/
*.pyc
# Mengabaikan file sistem yang tidak diinginkan (MacOS, Windows)
.DS_Store
Thumbs.db
# Mengabaikan file editor seperti VSCode, Sublime, dll
.vscode/
.idea/

View File

@ -0,0 +1,442 @@
# from fastapi import FastAPI, HTTPException
# from fastapi.middleware.cors import CORSMiddleware
# import httpx
# import logging
# import traceback
# import random # Untuk memilih cerita secara acak
# app = FastAPI()
# OLLAMA_URL = "http://192.168.60.92:11434/api/generate"
# # Konfigurasi Logging
# logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
# # Data ringkasan cerita
# summaries = {
# "Malin Kundang": "Malin Kundang adalah seorang anak dari keluarga miskin yang menjadi kaya raya namun menolak mengakui ibunya, hingga akhirnya dikutuk menjadi batu.",
# "Bawang Merah Bawang Putih": "Bawang Putih adalah gadis baik hati yang diperlakukan buruk oleh ibu dan saudara tirinya, tetapi kebaikannya membuahkan hasil berkat ikan ajaib.",
# "Sangkuriang": "Sangkuriang jatuh cinta pada ibunya, Dayang Sumbi, dan diberi tugas mustahil untuk membangun perahu dalam satu malam. Ia gagal dan akhirnya marah, menendang perahu hingga menjadi Gunung Tangkuban Perahu.",
# "Si Kancil": "Si Kancil dengan kecerdikannya berhasil menipu buaya untuk menyeberangi sungai dengan aman."
# }
# @app.get("/generate/")
# async def generate_text():
# try:
# # Pilih cerita secara acak
# selected_story = random.choice(list(summaries.keys()))
# story_summary = summaries[selected_story]
# # Buat prompt untuk Ollama
# prompt = f"""Buatlah tiga soal literasi dan jawabannya berdasarkan ringkasan cerita berikut:
# Cerita:
# {story_summary}
# Format output:
# Soal 1: [Tulis soal pertama di sini]
# Jawaban: [Tulis jawaban pertama di sini]
# Soal 2: [Tulis soal kedua di sini]
# Jawaban: [Tulis jawaban kedua di sini]
# Soal 3: [Tulis soal ketiga di sini]
# Jawaban: [Tulis jawaban ketiga di sini]
# """
# payload = {
# "model": "llama3.1:latest",
# "prompt": prompt,
# "stream": False
# }
# logging.info(f"Sending request to Ollama: {payload}")
# # Kirim request ke Ollama dengan timeout
# async with httpx.AsyncClient(timeout=30) as client:
# response = await client.post(OLLAMA_URL, json=payload)
# # Log response dari Ollama
# logging.info(f"Response status code: {response.status_code}")
# logging.info(f"Response content: {response.text}")
# # Raise error jika status code bukan 2xx
# response.raise_for_status()
# # Coba parse JSON response
# try:
# result = response.json()
# logging.info(f"Parsed response: {result}")
# return {
# "selected_story": selected_story,
# "generated_questions": result
# }
# except Exception as e:
# logging.error(f"Failed to parse JSON response: {response.text}")
# raise HTTPException(status_code=500, detail="Invalid response format from Ollama API")
# except httpx.HTTPStatusError as e:
# logging.error(f"HTTP error from Ollama API: {e.response.text}")
# raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
# except Exception as e:
# error_trace = traceback.format_exc() # Dapatkan traceback lengkap
# logging.error(f"Unexpected error: {error_trace}") # Cetak error di log
# raise HTTPException(status_code=500, detail="Internal Server Error")
# # Endpoint untuk mengecek koneksi ke Ollama
# @app.get("/test-connection/")
# async def test_connection():
# try:
# async with httpx.AsyncClient(timeout=10) as client:
# response = await client.get("http://192.168.60.92:11434")
# return {"status": response.status_code, "content": response.text}
# except Exception as e:
# logging.error(f"Connection test failed: {str(e)}")
# return {"error": str(e)}
# app = FastAPI()
# OLLAMA_URL = "http://192.168.60.92:11434/api/generate"
# logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
# class GenerateRequest(BaseModel):
# content: str # Menerima teks bebas, bukan content_id
# @app.post("/generate/")
# async def generate_text(request: GenerateRequest):
# try:
# if not request.content.strip():
# raise HTTPException(status_code=400, detail="Content cannot be empty")
# prompt = f"""Buatlah tiga soal literasi dan jawabannya berdasarkan teks berikut:
# {request.content}
# Format output:
# Soal 1: [Tulis soal pertama di sini]
# Jawaban: [Tulis jawaban pertama di sini]
# Jawaban: [Tulis jawaban kedua di sini]
# Soal 2: [Tulis soal kedua di sini]
# Jawaban: [Tulis jawaban pertama di sini]
# Jawaban: [Tulis jawaban kedua di sini]
# Soal 3: [Tulis soal ketiga di sini]
# Jawaban: [Tulis jawaban pertama di sini]
# Jawaban: [Tulis jawaban kedua di sini]
# """
# payload = {
# "model": "llama3.1:latest",
# "prompt": prompt,
# "stream": False
# }
# async with httpx.AsyncClient(timeout=30) as client:
# response = await client.post(OLLAMA_URL, json=payload)
# response.raise_for_status()
# result = response.json()
# return result
# except Exception as e:
# logging.error(f"Error: {traceback.format_exc()}")
# raise HTTPException(status_code=500, detail="Internal Server Error")
# from fastapi import FastAPI, HTTPException
# from pydantic import BaseModel
# import httpx
# import logging
# import traceback
# app = FastAPI()
# OLLAMA_URL = "http://192.168.60.92:11434/api/generate"
# class GenerateRequest(BaseModel):
# content: str = "Buatlah soal literasi berdasarkan teks anak-anak tentang lingkungan."
# @app.post("/generate/")
# async def generate_text(request: GenerateRequest):
# # Prompt default jika kosong
# prompt = f"""Buatlah tiga soal literasi berdasarkan teks berikut:
# {request.content}
# Format output:
# Soal 1: [Tulis soal pertama di sini]
# Jawaban: [Tulis jawaban pertama di sini]
# Soal 2: [Tulis soal kedua di sini]
# Jawaban: [Tulis jawaban kedua di sini]
# Soal 3: [Tulis soal ketiga di sini]
# Jawaban: [Tulis jawaban ketiga di sini]
# """
# payload = {
# "model": "llama3.1:latest",
# "prompt": prompt,
# "stream": False
# }
# async with httpx.AsyncClient() as client:
# response = await client.post(OLLAMA_URL, json=payload)
# response.raise_for_status()
# return response.json()
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import httpx
import logging
import random
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Ubah sesuai kebutuhan jika ada pembatasan domain
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
OLLAMA_URL = "http://localhost:11434/api/generate"
# Konfigurasi Logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
# Data ringkasan cerita, pantun, dan puisi (bisa ditambah lagi)
data_sources = {
"cerita": {
"Malin Kundang": "Malin Kundang adalah seorang anak dari keluarga miskin yang menjadi kaya raya namun menolak mengakui ibunya, hingga akhirnya dikutuk menjadi batu.",
"Bawang Merah Bawang Putih": "Bawang Putih adalah gadis baik hati yang diperlakukan buruk oleh ibu dan saudara tirinya, tetapi kebaikannya membuahkan hasil berkat ikan ajaib.",
"Sangkuriang": "Sangkuriang jatuh cinta pada ibunya, Dayang Sumbi, dan diberi tugas mustahil untuk membangun perahu dalam satu malam. Ia gagal dan akhirnya marah, menendang perahu hingga menjadi Gunung Tangkuban Perahu.",
"Si Kancil": "Si Kancil dengan kecerdikannya berhasil menipu buaya untuk menyeberangi sungai dengan aman.",
"Timun Mas": "Seorang ibu tua mendapatkan anak dari biji timun emas. Namun, anak itu harus melarikan diri dari raksasa jahat yang ingin memakannya."
},
"pantun": {
"Pantun Nasihat": "Jalan-jalan ke kota Blitar,\nJangan lupa membeli roti.\nRajin belajar sejak pintar,\nAgar sukses di kemudian hari.",
"Pantun Jenaka": "Ke pasar beli ikan teri,\nIkan habis tinggal kepala.\nJangan suka mencuri,\nNanti ketahuan malah celaka."
},
"puisi": {
"Puisi Alam": "Langit biru membentang luas,\nBurung-burung terbang bebas.\nAngin sepoi menyapu dedaunan,\nAlam indah penuh kedamaian.",
"Puisi Persahabatan": "Sahabat sejati selalu ada,\nDalam suka dan dalam duka.\nBersama kita jalani hari,\nMengukir cerita tak terlupa."
}
}
@app.post("/generate/")
async def generate_text():
try:
# Pilih 3 cerita, 1 pantun, dan 1 puisi secara acak
selected_stories = random.sample(list(data_sources["cerita"].keys()), 3)
selected_pantun = random.choice(list(data_sources["pantun"].keys()))
selected_puisi = random.choice(list(data_sources["puisi"].keys()))
# Buat format prompt dengan cerita, pantun, dan puisi
story_prompts = "\n\n".join([
f"**{story}**\n\n{data_sources['cerita'][story]}\n\nBerdasarkan cerita ini, buatlah **3 soal literasi** dalam format pilihan ganda."
for story in selected_stories
])
pantun_prompt = f"**{selected_pantun}**\n\n{data_sources['pantun'][selected_pantun]}\n\nBerdasarkan pantun ini, buatlah **1 soal literasi** dalam format pilihan ganda."
puisi_prompt = f"**{selected_puisi}**\n\n{data_sources['puisi'][selected_puisi]}\n\nBerdasarkan puisi ini, buatlah **1 soal literasi** dalam format pilihan ganda."
# Gabungkan semua prompt
full_prompt = f"""
Buatlah soal berdasarkan teks berikut ini:
{story_prompts}
{pantun_prompt}
{puisi_prompt}
Pastikan soal yang dibuat beragam, berbobot untuk siswa SD, dan tidak hanya berasal dari satu jenis teks.
"""
payload = {
"model": "llama3.1:latest",
"prompt": full_prompt,
"stream": False
}
logging.info(f"Mengirim permintaan ke Ollama: {payload}")
# Kirim request ke Ollama
async with httpx.AsyncClient(timeout=60) as client:
response = await client.post(OLLAMA_URL, json=payload)
# Log response dari Ollama
logging.info(f"Response status code: {response.status_code}")
logging.info(f"Response content: {response.text}")
# Raise error jika status code bukan 2xx
response.raise_for_status()
# Ambil hasil respon dan parsing JSON
result = response.json()
# Pastikan hasil tidak kosong
generated_text = result.get("response", "").strip()
if not generated_text:
raise HTTPException(status_code=500, detail="Ollama tidak menghasilkan pertanyaan")
return {
"selected_stories": selected_stories,
"selected_pantun": selected_pantun,
"selected_puisi": selected_puisi,
"generated_questions": generated_text
}
except httpx.HTTPStatusError as e:
logging.error(f"HTTP error dari Ollama API: {e.response.text}")
raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
except Exception as e:
logging.error(f"Terjadi kesalahan: {str(e)}")
raise HTTPException(status_code=500, detail="Terjadi kesalahan internal")
# from fastapi import FastAPI, HTTPException
# from fastapi.middleware.cors import CORSMiddleware
# import httpx
# import logging
# import random
# import re
# import traceback
# app = FastAPI()
# app.add_middleware(
# CORSMiddleware,
# allow_origins=["*"],
# allow_credentials=True,
# allow_methods=["*"],
# allow_headers=["*"],
# )
# OLLAMA_URL = "http://192.168.60.92:11434/api/generate"
# # Konfigurasi Logging
# logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
# # Data ringkasan cerita, pantun, dan puisi
# data_sources = {
# "cerita": {
# "Malin Kundang": "Malin Kundang adalah seorang anak dari keluarga miskin yang menjadi kaya raya namun menolak mengakui ibunya, hingga akhirnya dikutuk menjadi batu.",
# "Bawang Merah Bawang Putih": "Bawang Putih adalah gadis baik hati yang diperlakukan buruk oleh ibu dan saudara tirinya, tetapi kebaikannya membuahkan hasil berkat ikan ajaib.",
# "Sangkuriang": "Sangkuriang jatuh cinta pada ibunya, Dayang Sumbi, dan diberi tugas mustahil untuk membangun perahu dalam satu malam. Ia gagal dan akhirnya marah, menendang perahu hingga menjadi Gunung Tangkuban Perahu.",
# "Si Kancil": "Si Kancil dengan kecerdikannya berhasil menipu buaya untuk menyeberangi sungai dengan aman.",
# "Timun Mas": "Seorang ibu tua mendapatkan anak dari biji timun emas. Namun, anak itu harus melarikan diri dari raksasa jahat yang ingin memakannya."
# },
# "pantun": {
# "Pantun Nasihat": "Jalan-jalan ke kota Blitar,\nJangan lupa membeli roti.\nRajin belajar sejak pintar,\nAgar sukses di kemudian hari.",
# "Pantun Jenaka": "Ke pasar beli ikan teri,\nIkan habis tinggal kepala.\nJangan suka mencuri,\nNanti ketahuan malah celaka."
# },
# "puisi": {
# "Puisi Alam": "Langit biru membentang luas,\nBurung-burung terbang bebas.\nAngin sepoi menyapu dedaunan,\nAlam indah penuh kedamaian.",
# "Puisi Persahabatan": "Sahabat sejati selalu ada,\nDalam suka dan dalam duka.\nBersama kita jalani hari,\nMengukir cerita tak terlupa."
# }
# }
# @app.post("/generate/")
# async def generate_text():
# try:
# # Pilih 3 cerita, 1 pantun, dan 1 puisi secara acak
# selected_stories = random.sample(list(data_sources["cerita"].keys()), 3)
# selected_pantun = random.choice(list(data_sources["pantun"].keys()))
# selected_puisi = random.choice(list(data_sources["puisi"].keys()))
# # Buat format prompt dengan cerita, pantun, dan puisi
# story_prompts = "\n\n".join([
# f"**{story}**\n\n{data_sources['cerita'][story]}\n\nBerdasarkan cerita ini, buatlah **3 soal literasi** dalam format pilihan ganda."
# for story in selected_stories
# ])
# pantun_prompt = f"**{selected_pantun}**\n\n{data_sources['pantun'][selected_pantun]}\n\nBerdasarkan pantun ini, buatlah **1 soal literasi** dalam format pilihan ganda."
# puisi_prompt = f"**{selected_puisi}**\n\n{data_sources['puisi'][selected_puisi]}\n\nBerdasarkan puisi ini, buatlah **1 soal literasi** dalam format pilihan ganda."
# # Gabungkan semua prompt
# full_prompt = f"""
# Buatlah soal berdasarkan teks berikut ini:
# {story_prompts}
# {pantun_prompt}
# {puisi_prompt}
# Format setiap soal:
# ---
# **Pertanyaan**
# A. Pilihan 1
# B. Pilihan 2
# C. Pilihan 3
# D. Pilihan 4
# Jawaban: (A/B/C/D)
# ---
# """
# payload = {
# "model": "llama3.1:latest",
# "prompt": full_prompt,
# "stream": False
# }
# logging.info(f"Mengirim permintaan ke Ollama: {payload}")
# # Kirim request ke Ollama
# async with httpx.AsyncClient(timeout=60) as client:
# response = await client.post(OLLAMA_URL, json=payload)
# logging.info(f"Response status code: {response.status_code}")
# logging.info(f"Response content: {response.text}")
# response.raise_for_status()
# result = response.json()
# generated_text = result.get("response", "").strip()
# if not generated_text:
# raise HTTPException(status_code=500, detail="Ollama tidak menghasilkan pertanyaan")
# # Parsing hasil teks menjadi daftar soal
# questions = []
# raw_questions = re.split(r'\n\s*\n', generated_text)
# for raw in raw_questions:
# lines = raw.strip().split("\n")
# if len(lines) >= 6:
# question_text = lines[0].strip()
# options = [f"({opt[0]}) {opt[3:].strip()}" for opt in lines[1:5] if len(opt) > 3]
# # Ambil jawaban dengan regex
# answer_match = re.search(r'Jawaban:\s*\(?([A-D])\)?', raw)
# correct_answer = f"({answer_match.group(1)})" if answer_match else "Tidak ditemukan"
# questions.append({
# "question": question_text,
# "options": options,
# "correct_answer": correct_answer
# })
# if not questions:
# raise HTTPException(status_code=500, detail="Parsing soal gagal, format tidak sesuai.")
# return {
# "selected_stories": selected_stories,
# "selected_pantun": selected_pantun,
# "selected_puisi": selected_puisi,
# "generated_questions": questions
# }
# except httpx.HTTPStatusError as e:
# logging.error(f"HTTP error dari Ollama API: {e.response.text}")
# raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
# except Exception as e:
# logging.error(f"Terjadi kesalahan: {str(e)}")
# logging.error(traceback.format_exc()) # Cetak stack trace lengkap
# raise HTTPException(status_code=500, detail="Terjadi kesalahan internal")

View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

View File

@ -0,0 +1,65 @@
APP_NAME=iCLOP
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
NODEJS_DB_HOST=127.0.0.1
NODEJS_DB_PORT=3306
NODEJS_DB_DATABASE=iclop_nodejs
NODEJS_DB_USERNAME=root
NODEJS_DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

19
Penilaian Literasi/iClOP-V2/.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode

View File

@ -0,0 +1,6 @@
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} !^public
RewriteRule ^(.*)$ public/$1 [L]
</IfModule>

View File

@ -0,0 +1,66 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Cubet Techno Labs](https://cubettech.com)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[Many](https://www.many.co.uk)**
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
- **[DevSquad](https://devsquad.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[OP.GG](https://op.gg)**
- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
- **[Lendio](https://lendio.com)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -0,0 +1,27 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@ -0,0 +1,844 @@
<?php
namespace App\Http\Controllers\Android;
use App\Models\Android\Topic;
use App\Models\User;
use App\Models\Android\Task;
use App\Models\Android\Task_waiting;
use App\Models\Android\Enrollment;
use App\Models\Android\SubmitstestCase;
use App\Models\Android\SubmitsFinalSubmission;
use App\Models\Android\Submits;
use App\Models\Android\Testcase;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use DB;
class AndroidController extends Controller
{
/**
- LECTURER
*/
public function lecturer_material(){
$dt_keseluruhan = array();
$topic = Topic::all();
$notifikasi = $this->notify_validator();
$total_mhs = 0;
if ( $topic->count() > 0 ) {
foreach ( $topic AS $isi ) {
// count data task
$isi->total = Task::where('android_topic_id', $isi->id)->count();
$total_mhs_bytopik = Enrollment::where('android_topic_id', $isi->id)->whereNotIn('status', ['cancel'])->get()->count();
$isi->enroll = $total_mhs_bytopik;
array_push( $dt_keseluruhan, $isi );
$total_mhs = $total_mhs + $total_mhs_bytopik;
}
}
return view('android.teacher.material', compact('dt_keseluruhan', 'total_mhs', 'notifikasi'));
}
public function lecturer_overview( $id ){
$topic = Topic::findOrFail( $id );
$task = Task::where('android_topic_id', $id)->get();
$dt_enrollment = array();
$enrollment = Enrollment::where('android_topic_id', $id)->get();
foreach ( $enrollment As $isi ) {
// informasi validation
$total_request = 0;
$total_validate = 0;
$user_id = $isi->user->id;
// ambil informasi data testcase
$where = array(
'user_id' => $user_id,
'android_topic_id' => $id
);
$submit = Submits::where($where)->get();
// - - - - - - - -
$NA = 0;
$total_submit = $submit->count();
// - - - - - - - -
if ( $submit->count() > 0 ) {
$total_NA = 0;
foreach ( $submit AS $isi_s ) {
// ambil data testcase
$SubmitstestCase = SubmitstestCase::where("android_submit_id", $isi_s->id)->get();
$isi->testcase = $SubmitstestCase;
// auto
$where_passed = [
'android_submit_id' => $isi_s->id,
'status_validate' => "passed"
];
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
// ambil informasi data task untuk memanggil bobot
$info_testcase = Testcase::where("task_id", $isi_s->android_task_id)->get();
$bobot = 0;
foreach ( $info_testcase AS $isiit ) {
$bobot += $isiit->score;
}
$skor = 0;
foreach ( $test_passed AS $isitp ) {
$skor += $isitp->score;
}
$nilai = 0;
if ( $skor > 0 && $bobot > 0 ) {
$nilai = $skor / $bobot * 100;
}
$total_NA += $nilai;
// autograding
// $passed = 0;
// foreach ( $SubmitstestCase AS $det ) {
// if ( $det->status_validate == "passed" ) {
// $passed++;
// }
// }
// $total_NA += ($passed / $SubmitstestCase->count() * 100);
}
// echo $total_submit;
$NA = $total_NA / $total_submit;
$total_request = $submit->count();
foreach ( $submit AS $det ) {
if ( $det->validator == "validated" ) $total_validate++;
}
}
$progress = 0;
$total_task = Task::where("android_topic_id", $isi->android_topic_id)->count();
$status = $isi->status;
// check progress
if ( $status != "cancel") {
$task_waiting = Task_waiting::where( $where );
$total_task_waiting = $task_waiting->count();
if ( $total_task_waiting > 0 ) {
$info = Task::find( $task_waiting->first()->android_task_id );
$progress = $total_task_waiting / $total_task * 100;
}
}
$isi->total_request = $total_request;
$isi->total_validate = $total_validate;
$isi->NA = $NA;
$isi->progress = $progress;
array_push( $dt_enrollment, $isi );
}
$data = array(
'task' => $task,
'topic' => $topic,
'enrollment' => $dt_enrollment
);
// print_r( $Enrollment->user->name );
return view('android.teacher.overview', $data);
}
public function lecturer_waiting() {
// ambil data
$dt_keseluruhan = array();
$topic = Topic::all();
$notifikasi = $this->notify_validator();
$total_mhs = 0;
if ( $topic->count() > 0 ) {
foreach ( $topic AS $isi ) {
// count data task
$isi->total = Task::where('android_topic_id', $isi->id)->count();
$total_mhs_bytopik = Enrollment::where('android_topic_id', $isi->id)->whereNotIn('status', ['cancel'])->get()->count();
$isi->enroll = $total_mhs_bytopik;
array_push( $dt_keseluruhan, $isi );
$total_mhs = $total_mhs + $total_mhs_bytopik;
}
}
/* Cek */
$submits = Submits::where('validator', 'process')->get();
$dt_need_validator = array();
if ( $submits->count() > 0 ) {
// group by
$dt_submit_topic = array();
foreach ( $submits AS $index => $isi ) {
if ( $index > 0 ) {
$find = in_array($isi->android_topic_id, array_column($dt_submit_topic, 'android_topic_id'));
if ( $find == false ) {
array_push( $dt_submit_topic, [
'android_topic_id' => $isi->android_topic_id,
]);
}
continue;
} else {
array_push( $dt_submit_topic, [
'android_topic_id' => $isi->android_topic_id,
]);
}
}
// akumulasi mahasiswa
foreach ( $dt_submit_topic AS $isi ) {
$where = array(
'android_topic_id' => $isi,
'validator' => "process"
);
$all_mhs = Submits::where($where)->get();
$jumlah = $all_mhs->count();
$dt_all_mahasiswa_by_topic = array();
$title = "";
if ( $jumlah > 0 ) {
foreach ( $all_mhs AS $mhs ) {
$title = $mhs->topic->title;
array_push( $dt_all_mahasiswa_by_topic, $mhs );
}
}
// push to validator
array_push( $dt_need_validator, [
'android_topic_id' => $isi,
'title' => $title,
'jumlah' => $jumlah,
'all_mhs' => $dt_all_mahasiswa_by_topic
]);
}
}
return view('android.teacher.validator_waiting', compact('notifikasi', 'total_mhs', 'dt_need_validator'));
}
// detail task
public function lecturer_waiting_preview( $id_submit ) {
$submit = Submits::findOrFail( $id_submit );
// $testcase = SubmitstestCase::where("android_submit_id", $id_submit)->->get();
$testcase = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where("android_submit_id", $id_submit)->select("android_submits_testcase.*", "case", "score")->get();
// echo json_encode($testcase);
return view('android.teacher.validator_detail', compact('submit', 'testcase'));
}
// do validate
public function lecturer_do_validate( $id_testcase, $android_submit_id ) {
// update all status waiting to failed (default)
$where = array(
'android_submit_id' => $android_submit_id,
'status_validate' => "waiting"
);
SubmitstestCase::where($where)->update(['status_validate' => "failed"]);
$submittestcase = SubmitstestCase::findOrFail( $id_testcase );
$submit = Submits::findOrFail( $android_submit_id );
$submit->validator = "validated";
$submit->save();
if ( $submittestcase->status_validate == "failed" ) {
$submittestcase->status_validate = "passed";
} else if ( $submittestcase->status_validate == "passed" ) {
$submittestcase->status_validate = "failed";
}
$submittestcase->save();
// echo $;
// return "oke";
}
public function lecturer_load_point_testcase( $android_submit_id ) {
$where = [
'android_submit_id' => $android_submit_id,
'status_validate' => "passed"
];
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where)->get();
// ambil data submit berdasarkan id
$submit = Submits::findOrFail( $android_submit_id );
$task = Task::findOrFail( $submit->android_task_id );
$info_testcase = Testcase::where("task_id", $task->id)->get();
$bobot = 0;
foreach ( $info_testcase AS $isi ) {
$bobot += $isi->score;
}
$skor = 0;
foreach ( $test_passed AS $isi ) {
$skor += $isi->score;
}
// hitung bobot per testcase
$nilai = 0;
if ( $skor > 0 && $bobot > 0 ) {
$nilai = $skor / $bobot * 100;
}
echo json_encode(["point" => number_format($nilai, 2), "bobot" => $bobot, 'skor' => $test_passed->count()]);
}
// overview student
public function lecturer_overview_student( $topic_id, $user_id ) {
$enrollment = Enrollment::where('android_topic_id', $topic_id)->first();
$user = User::findOrFail( $user_id );
$where = array(
'android_topic_id' => $topic_id,
'user_id' => $user_id
);
$submit = Submits::where( $where )->get();
$dt_keseluruhan = array();
$NA = 0;
if ( $submit->count() > 0 ) {
$total_NA = 0;
$total_submit = $submit->count();
foreach ( $submit AS $isi ) {
// ambil data info nilai yang passed
$where_passed = [
'android_submit_id' => $isi->id,
'status_validate' => "passed"
];
// $test_passed = SubmitstestCase::where($where_passed)->get();
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
// ambil informasi data task untuk memanggil bobot
$info_testcase = Testcase::where("task_id", $isi->android_task_id)->get();
$bobot = 0;
// $dt = [];
foreach ( $info_testcase AS $isiit ) {
$bobot += $isiit->score;
}
$skor = 0;
foreach ( $test_passed AS $isitp ) {
$skor += $isitp->score;
}
// hitung bobot per testcase
$nilai = 0;
if ( $skor > 0 && $bobot > 0 ) {
$nilai = $skor / $bobot * 100;
}
// ambil data testcase
$isi->testcase = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where('android_submit_id', $isi->id)->get();
$isi->nilai = $nilai;
$isi->bobot = $bobot;
// echo $nilai.' = '.($nilai / $bobot * 100);
// echo "<br>";
// autograding
$total_NA += $nilai;
// echo $nilai;
// echo "<hr>";
array_push($dt_keseluruhan, $isi);
}
// echo $total_NA;
// echo " / ";
// echo $total_submit;
// echo " = ";
$NA = $total_NA / $total_submit;
// echo $total_NA;
}
// submission
$submission = SubmitsFinalSubmission::where( $where );
// echo $NA;
return view('android.teacher.overview_student', compact('enrollment', 'dt_keseluruhan', 'NA', 'user', 'submission'));
}
public function lecturer_confirm_student( $topic_id, $user_id, $enroll_id ){
$enrollment = Enrollment::findOrFail( $enroll_id );
$enrollment->status = "complete";
$enrollment->save();
return redirect("teacher/android23/overview-student/$topic_id/$user_id");
}
// notifikasi validator
public function notify_validator() {
$dt_notify = array();
$total = 0;
// get submit topic
$submitTopic = Submits::where('validator', 'process')->groupBy('android_topic_id')->get();
foreach ( $submitTopic AS $isi ) {
$topic_id = $isi->android_topic_id;
$hitung = Submits::where('android_topic_id', $topic_id)->where('validator', 'process')->get()->count();
$total += $hitung;
$isi->waiting = $hitung;
array_push( $dt_notify, $isi );
}
return [
'total' => $total,
'notify'=> $dt_notify
];
}
//
public function index() {
$dt_keseluruhan = array();
$topic = Topic::all();
if ( $topic->count() > 0 ) {
foreach ( $topic AS $isi ) {
// count data task
$isi->total = Task::where('android_topic_id', $isi->id)->count();
array_push( $dt_keseluruhan, $isi );
}
}
return view('android.admin.topic.index', compact('dt_keseluruhan'));
}
// tambah topik
function add(Request $request) {
$nama_file = "";
$directory_upload = "android23/profile";
// cek direktori
$direktori = 'android23/document/'.$request->folder_path;
if ( !is_dir( $direktori ) ){
mkdir( $direktori );
}
if ( $request->hasFile('picturePath') ) {
$file = $request->file('picturePath');
$nama_file = $file->getClientOriginalName();
$file->move($directory_upload, $nama_file);
}
$data = array(
'title' => $request->title,
'description' => $request->description,
'folder_path' => $request->folder_path,
'picturePath' => $nama_file,
'status' => $request->status
);
Topic::insert($data);
return redirect('android23/topic');
}
// update topik
function update( Request $request, $id ) {
$topik = Topic::where("id", $id)->first();
$nama_file = $topik->picturePath;
$directory_upload = "android23/profile";
if ( $request->hasFile('picturePath') ) {
$file = $request->file('picturePath');
$nama_file = $file->getClientOriginalName();
$file->move($directory_upload, $nama_file);
// delete old pic
if ( !empty( $topik->picturePath ) ) {
unlink( $directory_upload .'/'. $topik->picturePath );
}
}
$data = array(
'title' => $request->title,
'description' => $request->description,
'folder_path' => $request->folder_path,
'picturePath' => $nama_file,
'status' => $request->status
);
Topic::where('id', $id)->update($data);
return redirect('android23/topic');
}
// delete
function delete( $id ) {
Topic::where("id", $id)->delete();
return redirect('android23/topic');
}
// learning task
function learning_view( $id ) {
$topic = Topic::where('id', $id)->first();
$dt_task = Task::where('android_topic_id', $id)->get();
// sum bobot keseluruhan
// $total = Task::where('android_topic_id', $id)->sum('bobot');
$total = 0;
$task = array();
foreach ( $dt_task AS $isi ) {
// ambil data testcase
$testcase = Testcase::where("task_id", $isi->id)->get();
$isi->testcase = $testcase;
array_push( $task, $isi );
}
// print_r( $total );
return view('android.admin.topic.learning', compact('topic', 'task', 'total'));
}
// tambah learnng
function learning_add( Request $request, $id, $tipe ) {
$topic = Topic::where('id', $id)->first();
$nama_file = "";
$directory_upload = "android23/document/". $topic->folder_path;
if ( $request->hasFile('material') ) {
$file = $request->file('material');
$nama_file = $file->getClientOriginalName();
$file->move($directory_upload, $nama_file);
}
// insert data task
$data = array(
'android_topic_id' => $id,
'task_no' => $request->task_no,
'task_name' => $request->title,
'caption' => $request->caption,
'material' => $nama_file,
'tipe' => $tipe,
// 'testcase' => implode(',', $tags)
);
$id_task = Task::create($data)->id;
// cek apakah mengisi data
$dt_testcase = array();
if ( $request->has('tags') && $tipe == "submission" ){
// convert tags to obj
$data_tags = json_decode($request->tags);
foreach ( $data_tags AS $val ){
array_push( $dt_testcase, [
'task_id' => $id_task,
'case' => $val->value,
'score' => 0
]);
}
// print_r( $dt_testcase );
// insert data testcase
Testcase::insert( $dt_testcase );
}
return redirect('android23/topic/learning/'. $id);
}
function learning_update( Request $request, $id_topic, $id_task ) {
$topik = Topic::where("id", $id_topic)->first();
$task = Task::where("id", $id_task)->first();
$nama_file = $task->material;
$directory_upload = "android23/document/$topik->folder_path";
if ( $request->hasFile('material') ) {
$file = $request->file('material');
$nama_file = $file->getClientOriginalName();
$file->move($directory_upload, $nama_file);
// delete old pic
if ( !empty( $task->material ) ) {
unlink( $directory_upload .'/'. $task->material );
}
}
$data = array(
'task_no' => $request->task_no,
'task_name' => $request->title,
'caption' => $request->caption,
'material' => $nama_file,
);
Task::where('id', $id_task)->update($data);
return redirect('android23/topic/learning/'. $id_topic);
}
function learning_remove( $id_topic, $id_task ) {
// remove data testcase
Testcase::where('task_id', $id_task)->delete();
Task::where('id', $id_task)->delete();
return redirect('android23/topic/learning/'. $id_topic);
}
/* Testcase */
public function learning_update_testcase( Request $request ) {
// task id
$task_id = $request->task_id;
$dt_testcase_baru = array();
foreach ( $request->case AS $index => $isi ) {
array_push( $dt_testcase_baru, [
'task_id' => $task_id,
'case' => $isi,
'score' => $request->score[$index]
]);
}
// hapus data testcase lama
Testcase::where('task_id', $task_id)->delete();
Testcase::insert( $dt_testcase_baru );
return redirect('android23/topic/learning/'. $request->topic_id);
}
public function learning_add_testcase( Request $request, $topic_id, $task_id ) {
$dt_testcase = array();
if ( $request->has('tags') ){
// convert tags to obj
$data_tags = json_decode($request->tags);
foreach ( $data_tags AS $val ){
array_push( $dt_testcase, [
'task_id' => $task_id,
'case' => $val->value,
'score' => 0
]);
}
// insert data testcase
Testcase::insert( $dt_testcase );
}
return redirect('android23/topic/learning/'. $topic_id);
}
public function learning_reset_testcase( $topic_id, $task_id ) {
Testcase::where('task_id', $task_id)->delete();
return redirect('android23/topic/learning/'. $topic_id);
}
public function learning_remove_testcase( $topic_id, $testcase_id ){
$testcase = Testcase::find( $testcase_id );
$testcase->delete();
return redirect('android23/topic/learning/'. $topic_id);
}
}

View File

@ -0,0 +1,670 @@
<?php
namespace App\Http\Controllers\Android;
use App\Models\Android\Enrollment;
use App\Models\Android\Submits;
use App\Models\Android\SubmitsTestCase;
use App\Models\Android\SubmitsFinalSubmission;
use App\Models\Android\Task;
use App\Models\Android\Task_waiting;
use App\Models\Android\Topic;
use App\Models\Android\Testcase;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class AndroidMaterialController extends Controller
{
public function index() {
$topic = DB::table("android_topics");
$data_keseluruhan = array();
$id = Auth::user()->id;
$recent_task = array();
if ( $topic->count() > 0 ) {
foreach ( $topic->get() AS $isi ){
// init
$status = "enroll";
$progress = 0;
$grade = "-";
$total_task = Task::where("android_topic_id", $isi->id)->count();
$recent = "-";
$akses = "-";
$where = array(
'android_topic_id' => $isi->id,
'user_id' => $id
);
// check
$enrollment = DB::table("android_enrollment")->where($where);
if ( $enrollment->count() != 0 ) {
$dt_enroll = $enrollment->first();
$status = $dt_enroll->status;
$akses = $enrollment->first()->created_at;
}
// check progress
if ( $status != "cancel" ) {
$task_waiting = Task_waiting::where( $where );
$total_task_waiting = $task_waiting->count();
if ( $total_task_waiting > 0 ) {
$info = Task::find( $task_waiting->first()->android_task_id );
$progress = $total_task_waiting / $total_task * 100;
$recent = $info->task_name;
}
}
$NA = 0;
if ( $status == "complete" ) {
$where_enroll = array(
'user_id' => Auth::user()->id,
'android_topic_id' => $isi->id
);
$enrollment = Enrollment::where($where_enroll)->first();
$dt_all_submit = array();
$where = array(
'user_id' => Auth::user()->id, // 1577
'android_submits.android_topic_id' => $isi->id // 11
);
$submits = Submits::select('android_submits.id','android_task.task_name', 'android_submits.created_at', 'android_submits.duration')
->join('android_task', 'android_task.id', '=', 'android_submits.android_task_id')->where($where)->get();
//mulai sini autograding
$estimate = 0;
$total_submit = $submits->count();
if ( $submits->count() > 0 ) {
$total_test = 0;
foreach ( $submits AS $isi_sb ) {
$submit_id = $isi_sb->id;
$where_passed = [
'android_submit_id' => $isi_sb->id,
'status_validate' => "passed"
];
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
// ambil informasi data task untuk memanggil bobot
$info_testcase = Testcase::where("task_id", $isi_sb->android_task_id)->get();
$bobot = 0;
foreach ( $info_testcase AS $isiit ) {
$bobot += $isiit->score;
}
$skor = 0;
foreach ( $test_passed AS $isitp ) {
$skor += $isitp->score;
}
$nilai = 0;
if ( $skor > 0 && $bobot > 0 ) {
$nilai = $skor / $bobot * 100;
// echo "masuk";
}
// ----
// $dt_submit_testcase = array();
// $testcase = SubmitsTestCase::where("android_submit_id", $submit_id)->get();
// // autograding
// $passed = 0;
// foreach ( $testcase AS $det ) {
// if ( $det->status_validate == "passed" ) {
// $passed++;
// }
// }
// $persentage = ($passed / $testcase->count() * 100);
$total_test += $nilai;
$estimate += $isi_sb->duration;
// echo $nilai.'<br>';
}
$NA = $total_test / $total_submit;
// echo $total_test.' - '. $total_submit;
// echo $NA;
}
}
$isi->status_waiting = $status;
$isi->progress = $progress;
$isi->grade = $grade;
$isi->total_task = $total_task;
$isi->akses_materi = $akses;
$isi->recent = $recent;
$isi->NA = $NA;
// recent task
$where = array(
'android_task.android_topic_id' => $isi->id,
'user_id' => $id
);
$recent_task = Task_waiting::select("task_name", "title")
->join("android_task", "android_task.id", "=", "android_task_waiting.android_task_id")
->join("android_topics", "android_topics.id", "=", "android_task_waiting.android_topic_id")
->where( $where )->orderBy('android_task.created_at', 'desc')->get()->unique('android_topic_id');
array_push( $data_keseluruhan, $isi );
}
}
return view('android.student.material.index', compact('data_keseluruhan', 'recent_task'));
}
// upload
public function upload( Request $request ) {
$request->validate([
'file' => 'required'
]);
$fileName = time().'-'.request()->file->getClientOriginalExtension();
request()->file->move(public_path('files'), $fileName);
return request()->json([
'success' => "You have successfully upload file"
]);
}
public function task( Request $request, $topic_id ) {
// ambil data informasi topik
$topic = Topic::findOrFail( $topic_id );
$where = array(
'user_id' => Auth::user()->id,
'android_task_id' => $request->id
);
$submit_byId = Submits::where($where);
$data = [];
// cek memiliki id atau tidak
if ($request->filled('id')) {
$id_task_recommend = $request->id;
// ambil informasi data task berdasarkan id
$task = Task::findOrFail($id_task_recommend);
$testcase = Testcase::where("task_id", $id_task_recommend)->get();
// $task = Task::where('id', $id_task_recommend);
$submit_information = array();
$submit_testcase = array();
if ( $submit_byId->count() > 0 ) {
$submits = $submit_byId->first();
$submitTestCase = SubmitsTestCase::where("android_submit_id", $submits->id)->get();
$submit_information = $submits;
$submit_testcase = $submitTestCase;
// echo json_encode($submits->id);
}
// cek apakah sudah mengakses materi ?
// next material
$next = Task::where('id', '>', $task->id);
if ( $next->count() > 0 ) {
$url = '/android23/task/'.$topic_id.'?id='. $next->min('id');
} else {
$url = '/android23/task/'.$topic_id.'?id='. $id_task_recommend.'&type=final';
}
$dt_task = array();
$tasks = Task::where("android_topic_id", $topic_id)->orderBy('task_no', 'ASC')->get();
foreach ( $tasks AS $isi_tsk ) {
$where = [
'user_id' => Auth::user()->id,
'android_topic_id' => $topic_id,
'android_task_id' => $isi_tsk->id,
];
$task_waiting = Task_waiting::where($where)->get();
if ( $task_waiting->count() == 0 ) {
$isi_tsk->status_akses = false;
} else {
$isi_tsk->status_akses = true;
}
array_push( $dt_task, $isi_tsk );
}
$data = array(
'topic_id' => $topic_id,
'id' => $id_task_recommend,
'task' => $task,
'testcase' => $testcase,
'topic' => $topic,
'all_task' => $dt_task,
'submit_information' => $submit_information,
'submit_testcase' => $submit_testcase,
'request' => $request,
'taskwaiting' => $task_waiting->count(),
'urlku' => $url
);
// echo $url;
// cek apabila request sudah final
if ( $request->type == "final" ) {
$where_enroll = array(
'user_id' => Auth::user()->id,
'android_topic_id' => $topic_id
);
$enrollment = Enrollment::where($where_enroll)->first();
$dt_all_submit = array();
$where = array(
'user_id' => Auth::user()->id, // 1577
'android_submits.android_topic_id' => $topic_id // 11
);
$submits = Submits::select('android_task_id','android_submits.id','android_task.task_name', 'android_submits.created_at', 'android_submits.duration')
->join('android_task', 'android_task.id', '=', 'android_submits.android_task_id')->where($where)->get();
//mulai sini autograding
$NA = 0;
$estimate = 0;
$total_submit = $submits->count();
if ( $submits->count() > 0 ) {
$total_test = 0;
foreach ( $submits AS $isi ) {
$submit_id = $isi->id;
$dt_submit_testcase = array();
$testcase = SubmitsTestCase::where("android_submit_id", $submit_id)->get();
// autograding
$where_passed = [
'android_submit_id' => $isi->id,
'status_validate' => "passed"
];
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
// ambil informasi data task untuk memanggil bobot
$info_testcase = Testcase::where("task_id", $isi->android_task_id)->get();
$bobot = 0;
foreach ( $info_testcase AS $isiit ) {
$bobot += $isiit->score;
}
$skor = 0;
foreach ( $test_passed AS $isitp ) {
$skor += $isitp->score;
}
$nilai = 0;
if ( $skor > 0 && $bobot > 0 ) {
$nilai = $skor / $bobot * 100;
}
$total_test += $nilai;
// echo $isi->android_task_id.'<br>';
// autograding (old)
// $passed = 0;
// foreach ( $testcase AS $det ) {
// if ( $det->status_validate == "passed" ) {
// $passed++;
// }
// }
// $persentage = ($passed / $testcase->count() * 100);
// $total_test += $persentage;
// total_test = total_NA + persentage
// total_test = 0 + 57
// total_test = 57
// total_test = total_test + persentnage
// total test = 57 + 75
$estimate += $isi->duration;
array_push( $dt_all_submit, [
'info' => $isi,
'persentage' => $nilai,
'testcase' => $testcase
]);
}
$NA = $total_test / $total_submit;
}
$add = array(
'submits' => $dt_all_submit,
'NA' => $NA,
'submit_submission' => SubmitsFinalSubmission::all(),
'enrollment' => $enrollment,
'estimate' => $estimate
);
// merge
$data = array_merge($data, $add);
}
// return view
} else {
// last recomend
$id_task_recommend = "";
$where = [
'user_id' => Auth::user()->id,
'android_topic_id' => $topic_id,
];
$recommend = Task_waiting::where( $where )->orderBy('created_at', 'DESC');
if ( $recommend->count() == 0 ) {
// memberikan id awal
// ambil materi awal task berdasarkan topic id
$dt_task = Task::where('android_topic_id', $topic_id)->first();
$id_task_recommend = $dt_task->id;
} else {
$dt_task = $recommend->first();
$id_task_recommend = $dt_task->android_task_id;
}
// echo "test";
// redirect . ..
}
// access materi update
$cektaskwaiting = array(
'user_id' => Auth::user()->id,
'android_topic_id' => $topic_id,
'android_task_id' => $id_task_recommend
);
// cek
$cekTask = Task_waiting::where( $cektaskwaiting )->get();
if ( $cekTask->count() == 0 ) {
Task_waiting::create( $cektaskwaiting );
}
// print_r( $data );
if ( $request->filled('id') ) {
return view('android.student/material/task', $data);
} else {
return redirect('android23/task/'. $topic_id.'?id='. $id_task_recommend);
}
}
//validation result
public function validation() {
// informasi login sebagai
$id = Auth::user()->id;
$kondisi = ["user_id" => $id, 'status' => "complete"];
$enrollment = Enrollment::where($kondisi)->get();
$dt_keseluruhan = array();
if ( $enrollment->count() > 0 ) {
foreach ( $enrollment AS $isi ) {
// id topik
$topic_id = $isi->android_topic_id;
$topic = Topic::find( $topic_id );
$where_task = ["android_topic_id" => $topic_id, 'user_id' => $id];
// $task = Task::where( $where_task )->get();
// ambil data submits
$submit = Submits::where($where_task)->get();
$isi->submit = $submit;
$isi->topic = $topic;
// $isi->task = $task;
array_push( $dt_keseluruhan, $isi );
}
}
// echo "oke";
return view('android.student.validation.index', compact('dt_keseluruhan'));
}
public function validation_detail( $topic_id ) {
$enrollment = Enrollment::where( "android_topic_id", $topic_id )->first();
$dt_all_submit = array();
$where = array(
'user_id' => Auth::user()->id, // 1577
'android_submits.android_topic_id' => $topic_id // 11
);
$submits = Submits::select('android_task_id','android_submits.id','android_task.task_name', 'android_submits.created_at', 'android_submits.duration')
->join('android_task', 'android_task.id', '=', 'android_submits.android_task_id')->where($where)->get();
//mulai sini autograding
$NA = 0;
$estimate = 0;
$total_submit = $submits->count();
if ( $submits->count() > 0 ) {
$total_test = 0;
foreach ( $submits AS $isi ) {
$submit_id = $isi->id;
$dt_submit_testcase = array();
$testcase = SubmitsTestCase::where("android_submit_id", $submit_id)->get();
// autograding
$where_passed = [
'android_submit_id' => $isi->id,
'status_validate' => "passed"
];
$test_passed = SubmitstestCase::join("android_testcase", "android_testcase.id", "=", "android_submits_testcase.android_testcase_id")->where($where_passed)->get();
// ambil informasi data task untuk memanggil bobot
$info_testcase = Testcase::where("task_id", $isi->android_task_id)->get();
$bobot = 0;
foreach ( $info_testcase AS $isiit ) {
$bobot += $isiit->score;
}
$skor = 0;
foreach ( $test_passed AS $isitp ) {
$skor += $isitp->score;
}
$nilai = 0;
if ( $skor > 0 && $bobot > 0 ) {
$nilai = $skor / $bobot * 100;
}
$total_test += $nilai;
// echo $isi->android_task_id.'<br>';
// autograding (old)
// $passed = 0;
// foreach ( $testcase AS $det ) {
// if ( $det->status_validate == "passed" ) {
// $passed++;
// }
// }
// $persentage = ($passed / $testcase->count() * 100);
// $total_test += $persentage;
// total_test = total_NA + persentage
// total_test = 0 + 57
// total_test = 57
// total_test = total_test + persentnage
// total test = 57 + 75
$estimate += $isi->duration;
array_push( $dt_all_submit, [
'info' => $isi,
'persentage' => $nilai,
'testcase' => $testcase
]);
}
$NA = $total_test / $total_submit;
}
$dt_keseluruhan = array(
'submits' => $dt_all_submit,
'NA' => $NA,
'submit_submission' => SubmitsFinalSubmission::all(),
'enrollment' => $enrollment,
'estimate' => $estimate
);
return view('android.student.validation.detail', compact('dt_keseluruhan'));
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\Android23;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AssignController extends Controller
{
//
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Android;
use App\Models\Android\Enrollment;
use App\Models\Android\Topic;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class EnrollController extends Controller
{
public function enroll( Request $request, $topic_id = null ){
// cek
$topik = Topic::findOrFail($topic_id);
$where = ['android_topic_id' => $topic_id, 'user_id' => Auth::user()->id];
$enroll_status = Enrollment::where($where)->count();
if ( $enroll_status == 0 ) {
$data = array(
'android_topic_id' => $topic_id,
'user_id' => Auth::user()->id,
'status' => "process"
);
Enrollment::create( $data );
return redirect()->route('material');
} else {
// sebelumnya sudah pernah melakukan enroll
return redirect()->route('material')->withErrors('pesan', 'Invalid Enrollment Request');
}
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace App\Http\Controllers\Android;
use App\Models\Android\Submits;
use App\Models\Android\SubmitsTestCase;
use App\Models\Android\Enrollment;
use App\Models\Android\SubmitsFinalSubmission;
use App\Models\Android\Topic;
use App\Models\Android\Task;
use App\Models\Android\Testcase;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class SubmissionController extends Controller
{
public function proses_tambah( Request $request ) {
$request->validate([
'userfile' => 'required|image',
'duration' => 'required',
'comment' => 'required|string'
]);
$file = $request->file('userfile');
$extension = $file->getClientOriginalExtension();
// upload path
$upload_path = "android23/submission";
$fileName = uniqid().'-'.strtotime("now").'.'.$extension;
$file->move($upload_path, $fileName);
$task_id = $request->android_task_id;
$topic_id = $request->android_topic_id;
$data = array(
'user_id' => Auth::user()->id,
'android_topic_id' => $topic_id,
'android_task_id' => $task_id,
'duration' => $request->duration,
'upload' => $fileName,
'comment' => $request->comment,
'validator' => "process"
);
$submit = Submits::create( $data );
$dt_task = Task::findOrFail( $task_id );
$dt_testcasefromtask = Testcase::where("task_id", $task_id)->get();
// listdata test case
$data_submit = array();
if ( $request->has('task') ) {
foreach ( $dt_testcasefromtask AS $isi ) {
$status = false;
foreach ( $request->task AS $ts ){
if ( $ts == $isi->id ) {
$status = true;
break;
}
}
$label = "failed";
if ( $status ) {
$label = "passed";
}
array_push($data_submit, [
'user_id' => Auth::user()->id,
'android_submit_id' => $submit->id,
'android_testcase_id'=> $isi->id,
'status'=> $label,
'status_validate' => 'waiting',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
}
// print_r( $data_submit );
SubmitsTestCase::insert($data_submit);
}
return redirect('android23/task/'.$topic_id.'?id='.$task_id);
}
// submit final submission
public function submit_final_submission( Request $request, $topic_id ){
if ( !empty( $request->type ) ) {
// validation
if ( $request->type == "github" ) {
$rules = array(
'link' => 'required|string'
);
} else {
$rules = array(
'userfile' => 'required|file|mimes:zip|max:3072'
);
}
// check rules
$request->validate($rules);
if ( $request->type == "zip" ) {
// file upload
$file = $request->file('userfile');
// upload path
$upload_path = "android23/final-submission";
$userfile = uniqid().'-'.strtotime("now").'.zip';
$file->move($upload_path, $userfile);
} else {
// github
$userfile = $request->link;
}
$data = array(
'user_id' => Auth::user()->id,
'android_topic_id' => $topic_id,
'tipe' => $request->type,
'userfile' => $userfile
);
SubmitsFinalSubmission::create( $data );
$dataEnrollUpdate = array(
'status' => "review"
);
Enrollment::where(['user_id' => $data['user_id'], 'android_topic_id' => $data['android_topic_id']])->update( $dataEnrollUpdate );
// redirect
return redirect('android23/task/'.$topic_id.'?id='.$request->task_id.'&type=final');
}
}
public function overview( $id ) {
$data = array(
'topic_id' => $id,
'topic' => Topic::findOrFail( $id ),
'all_task' => Task::orderBy('task_no', 'ASC')->get(),
);
return view('student.android23.material.overview', $data);
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Session;
class AuthController extends Controller
{
public function proses(Request $request)
{
$request->validate([
'email' => 'required',
'password' => 'required',
]);
$credential = request(['email', 'password']);
if (Auth::attempt($credential)) {
if (Auth::user()->role == "admin") {
return redirect('welcome');
} else if (Auth::user()->role == "teacher") {
return redirect('dashboard_teacher');
} else {
// student
session(['key' => Auth::user()->name]);
session(['email' => Auth::user()->email]);
return redirect('dashboard-student');
}
} else {
echo "okee err";
}
}
public function signup(Request $request)
{
$data = $request->validate([
'name' => 'required',
'email' => 'required',
'password' => 'required|confirmed',
'role' => 'required',
]);
$data['password'] = bcrypt($data['password']);
User::create($data);
return redirect('/');
}
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
public function redirect()
{
return Socialite::driver(driver: 'google')->redirect();
}
public function googleCallback()
{
$user = Socialite::driver('google')->user();
$userDatabase = User::where('google_id', $user->getId())->first();
$token = $user->token;
session(['google_token' => $token]);
if (!$userDatabase) {
$data = [
'google_id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'role' => 'student',
];
$existingUser = User::where('email', $data['email'])->first();
if ($existingUser) {
$existingUser->update($data);
$userDatabase = $existingUser;
} else {
$userDatabase = User::create($data);
}
auth()->login($userDatabase);
session()->regenerate();
return redirect()->route('dashboard-student');
} else {
auth()->login($userDatabase);
session()->regenerate();
return redirect()->route('dashboard-student');
}
}
protected function createDatabase($dbUsername, $dbPassword) {
try {
$connection = DB::connection('mysql')->getPdo();
DB::statement("CREATE USER '{$dbUsername}'@'%' IDENTIFIED BY '';"); // Empty password
DB::statement("GRANT USAGE ON *.* TO '{$dbUsername}'@'%';");
DB::statement("ALTER USER '{$dbUsername}'@'%' REQUIRE NONE WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;");
// Create a database
DB::statement("CREATE DATABASE IF NOT EXISTS `{$dbUsername}`;");
DB::statement("GRANT ALL PRIVILEGES ON `{$dbUsername}`.* TO '{$dbUsername}'@'%';");
// Apply the changes immediately
DB::statement("FLUSH PRIVILEGES;");
} catch (\Exception $e) {
// Handle exceptions such as permission issues or SQL errors
Log::error($e->getMessage());
// Optionally, return an error message to the user
}
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DataController extends Controller
{
public function index()
{
$cards = [
[
'image' => './images/cards/Android.png',
'title' => 'Android programming with Java and Kotlin',
'topics' => '18 learning topics',
],
[
'image' => './images/cards/Flutter.png',
'title' => 'Mobile programming with Flutter',
'topics' => '18 learning topics',
],
[
'image' => './images/cards/Node.js.png',
'title' => 'Web application with Node.JS',
'topics' => '18 learning topics',
],
];
$cardsData = [
[
'image' => './images/cards/computer.png',
'title' => 'Fully Computer-Assisted Learning Platform',
'description' => 'Digital educational platform that utilizes artificial intelligence and machine learning to provide a comprehensive and interactive learning experience entirely driven by computer technology.',
],
[
'image' => './images/cards/eos-icons_machine-learning.png',
'title' => 'Intelligence Guidance',
'description' => 'System or technology that provides automated support and guidance to learners, assisting them in their learning journey through intelligent algorithms and machine learning.',
],
[
'image' => './images/cards/Grading.png',
'title' => 'Auto Grading',
'description' => 'Technology that automatically evaluates and scores assignments, exams, or assessments, eliminating the need for manual grading by instructors and providing efficient and consistent feedback to students.',
],
];
$cardsData2 = [
[
'image' => './images/cards/Intelligence.png',
'title' => 'Intelligence Learning Guidance',
'description' => 'Intelligence Learning Guidance utilizes AI and smart algorithms to provide personalized support, adapting to learners needs and optimizing their educational outcomes.',
],
[
'image' => './images/cards/coding.png',
'title' => 'Practical Coding Approach',
'description' => 'Focuses on teaching coding skills through real-world examples, projects, and problem-solving scenarios, enabling learners to develop practical coding proficiency and problem-solving abilities.',
],
[
'image' => './images/cards/Machine.png',
'title' => 'Online Virtual Machine',
'description' => 'Virtual computing environment accessible over the internet, enabling users to run applications, perform tasks, and store data without requiring physical hardware.',
],
];
return view('welcome', compact('cards', 'cardsData', 'cardsData2'));
}
}

View File

@ -0,0 +1,188 @@
<?php
namespace App\Http\Controllers\Flutter;
use App\Http\Controllers\Controller;
use App\Models\NodeJS\Project;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use App\Models\Flutter\Topic;
use App\Models\FlutterTestResult;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Symfony\Component\Process\Process;
use Illuminate\Support\Facades\Session;
class FlutterController extends Controller
{
public function index()
{
$actual = "";
$topics = Topic::all();
$topicsCount = count($topics);
return view('flutter.student.material.index', [
'result_up' => $actual,
'topics' => $topics,
'topicsCount' => $topicsCount,
]);
}
function flutter_material_detail()
{
$flutterid = isset($_GET['flutterid']) ? (int)$_GET['flutterid'] : '';
$start = isset($_GET['start']) ? (int)$_GET['start'] : '';
$output = isset($_GET['output']) ? $_GET['output'] : '';
// dd($start);
$results = DB::select("select * from flutter_topics_detail where id_topics = $flutterid and id ='$start' ");
foreach ($results as $r) {
if ($flutterid == $r->id_topics) {
if (empty($r->file_name)) {
$contain = $r->description;
$pdf_reader = 0;
} else {
$contain = $r->file_name;
$pdf_reader = 1;
}
$html_start = $this->html_persyaratan($contain, $pdf_reader);
} else {
$html_start = "";
}
}
$listTask = DB::select("select aa.*, us.name from flutter_user_submits aa join users us on aa.userid = us.id where flutter_id = $flutterid and flutter_id_topic = $start ");
$idUser = Auth::user()->id;
$roleTeacher = DB::select("select role from users where id = $idUser");
$topics = Topic::all();
$detail = Topic::findorfail($flutterid);
$topicsCount = count($topics);
$detailCount = ($topicsCount / $topicsCount) * 10;
$flutterTestResults = FlutterTestResult::where('user_id', $idUser)->where('flutterid', $flutterid)->first();
return view('flutter.student.material.topics_detail', [
'row' => $detail,
'topics' => $topics,
'flutterid' => $flutterid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader,
'topicsCount' => $topicsCount,
'detailCount' => $detailCount,
'output' => $output,
'flag' => $results[0] ? $results[0]->flag : 0,
'listTask' => $listTask,
'role' => $roleTeacher[0] ? $roleTeacher[0]->role : '',
'flutterTestResults' => $flutterTestResults
]);
}
public function sendUrl(Request $request)
{
$fileURL = $request->input('url');
// dd($fileURL);
$response = Http::asForm()->timeout(200000000000)->post('http://localhost:8080/submit', [
'url' => $fileURL
]);
if ($response) {
$data = $response->json();
// Return the data as JSON
return response()->json($data);
} else {
return response()->json(['message' => 'An errorcukihile submitting the request.'], 500);
}
}
function html_start()
{
$html = "<div style='text-align:center;font-size:18px'><em>Modul kelas Belajar Pengembangan Aplikasi Android Intermediate dalam bentuk cetak (buku) maupun elektronik sudah didaftarkan ke Dirjen HKI, Kemenkumham RI. Segala bentuk penggandaan dan atau komersialisasi, sebagian atau seluruh bagian, baik cetak maupun elektronik terhadap modul kelas <em>Belajar Pengembangan Aplikasi Android Intermediate</em> tanpa izin formal tertulis kepada pemilik hak cipta akan diproses melalui jalur hukum.</em></div>";
return $html;
}
function html_persyaratan($desc, $pdf_reader)
{
$html = $desc;
return $html;
}
function send_task()
{
$flutterid = isset($_GET['flutterid']) ? $_GET['flutterid'] : '';
$task = isset($_GET['task']) ? $_GET['task'] : '';
$output = isset($_GET['output']) ? $_GET['output'] : '';
$task_db = DB::table('flutter_task')
->where('id_topics', $flutterid)
->where('id', $task)
->first();
if ($task_db->id == $task) {
$html_start = $this->html_task();
} else {
$html_start = "";
}
$pdf_reader = 0;
$topics = Topic::all();
$detail = Topic::findorfail($flutterid);
$topicsCount = count($topics);
$persen = ($topicsCount / $topicsCount) * 10;
session(['params' => $persen]);
return view('flutter.student.material.topics_detail', [
'row' => $detail,
'topics' => $topics,
'flutterid' => $flutterid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader,
'detailCount' => $persen,
'output' => $output,
]);
}
public function store(Request $request)
{
// dd(gettype($request->success_tests));
$user_id = Auth::user()->id;
// Validasi request jika diperlukan
$request->validate([
'success_tests' => 'required',
'failed_tests' => 'required',
'score' => 'required|numeric',
]);
// Simpan data ke dalam database menggunakan model
$testResult = FlutterTestResult::create([
'user_id' => $user_id,
'success_tests' => $request->success_tests,
'failed_tests' => $request->failed_tests,
'score' => $request->score,
'flutterid' => $request->flutterid,
]);
// Jika ingin memberikan respons atau melakukan sesuatu setelah penyimpanan
return response()->json(['message' => 'Data berhasil disimpan.', 'data' => $testResult]);
}
// function html_task(){
// return view('php.student.task.form_submission_task',[]);
// }
// function php_admin(){
// return view('php.admin.material.upload_materi',[]);
// }
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class LiteracyAnswerController extends Controller
{
//
}

View File

@ -0,0 +1,115 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use App\Models\Literacy\LiteracyAssessment;
use App\Models\Literacy\LiteracyQuestion;
use App\Models\Literacy\LiteracyAnswer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LiteracyAssessmentController extends Controller
{
public function __construct()
{
// Middleware memastikan hanya student yang bisa mengakses
$this->middleware(function ($request, $next) {
if (Auth::user()->role !== 'student') {
abort(403, 'Unauthorized action.');
}
return $next($request);
});
}
/**
* Menampilkan daftar asesmen yang sedang berjalan atau sudah selesai untuk student yang login.
*/
public function index()
{
$assessments = LiteracyAssessment::where('user_id', Auth::id())->get();
return view('literacy.student.assessments.index', compact('assessments'));
}
/**
* Memulai asesmen dan mengubah status menjadi "in_progress".
*/
public function start($id)
{
$assessment = LiteracyAssessment::where('id', $id)
->where('user_id', Auth::id()) // Pastikan hanya asesmen milik student tersebut
->firstOrFail();
$assessment->update(['status' => 'in_progress']);
return redirect()->route('literacy_assessments_show', $id);
}
/**
* Menampilkan halaman asesmen dengan pertanyaan yang diurutkan.
*/
public function show($id)
{
$assessment = LiteracyAssessment::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
// Ambil semua pertanyaan, urutkan pilihan ganda dulu baru essay
$questions = LiteracyQuestion::orderByRaw("FIELD(type, 'multiple_choice', 'essay')")->get();
return view('literacy.student.assessments.assessment', compact('assessment', 'questions'));
}
/**
* Melanjutkan asesmen yang belum selesai.
*/
public function continue($id)
{
$assessment = LiteracyAssessment::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
return redirect()->route('literacy_assessments_show', $id);
}
/**
* Menyimpan jawaban siswa ke database secara otomatis saat memilih atau mengisi jawaban.
*/
public function storeAnswer(Request $request, $assessmentId)
{
$request->validate([
'question_id' => 'required|exists:literacy_questions,id',
'answer' => 'nullable|string',
]);
$assessment = LiteracyAssessment::where('id', $assessmentId)
->where('user_id', Auth::id())
->firstOrFail();
// Simpan atau update jawaban siswa
LiteracyAnswer::updateOrCreate(
[
'assessment_id' => $assessment->id,
'user_id' => Auth::id(),
'question_id' => $request->question_id,
],
[
'answer' => $request->answer,
]
);
// Cek apakah semua pertanyaan telah dijawab
$totalQuestions = LiteracyQuestion::count();
$answeredQuestions = LiteracyAnswer::where('assessment_id', $assessment->id)
->whereNotNull('answer')
->count();
// Jika semua pertanyaan sudah dijawab, ubah status asesmen menjadi "completed"
if ($answeredQuestions >= $totalQuestions) {
$assessment->update(['status' => 'completed']);
}
return response()->json(['success' => true]);
}
}

View File

@ -0,0 +1,495 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use App\Models\NodeJS\Project;
use App\Models\Literacy\LiteracyTopic;
use App\Models\Literacy\LiteracyTopic_detail;
use App\Models\Literacy\LiteracyUserEnroll;
use App\Models\Literacy\LiteracyMaterial;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process;
use Illuminate\Support\Facades\Session;
class LiteracyController extends Controller{
function materials()
{
$materials = LiteracyMaterial::all();
return view('literacy.student.materials.index', [
'materials' => $materials,
]);
}
public function submit_score_baru(Request $request)
{
$userId = Auth::id(); // Get the authenticated user's ID
$score = $request->input('score');
$topicsId = $request->input('topics_id');
// Insert the new score into user_submissions using the Query Builder
DB::table('react_user_submits')->insert([
'user_id' => $userId,
'score' => $score,
'topics_id' => $topicsId,
'flag' => $score > 50 ? true : false,
'created_at' => now(),
'updated_at' => now()
]);
// Retrieve or create an entry in user_rank for specific user and topic
$currentRank = DB::table('react_student_rank')
->where('id_user', $userId)
->where('topics_id', $topicsId)
->first();
if (!$currentRank) {
// If no entry exists, create one
DB::table('react_student_rank')->insert([
'id_user' => $userId,
'best_score' => $score,
'topics_id' => $topicsId,
'created_at' => now(),
'updated_at' => now()
]);
} else if ($score > $currentRank->best_score) {
// Update the best_score if the new score is higher
DB::table('react_student_rank')
->where('id_user', $userId)
->where('topics_id', $topicsId)
->update([
'best_score' => $score,
'updated_at' => now()
]);
}
if ($score > 50 ) {
$exists = DB::table('react_student_enroll')
->where('id_users', Auth::user()->id)
->where('php_topics_detail_id', $topicsId)
->exists();
if (!$exists) {
// Record does not exist, so insert a new one
$flags = $results[0]->flag ?? 0;
if ( $flags == 0) {
DB::table('react_student_enroll')->insert([
'id_users' => Auth::user()->id,
'php_topics_detail_id' => $topicsId,
'created_at' => now()
]);
}
}
}
return response()->json(['message' => 'Score submitted successfully']);
}
public function index(){
$actual = "";
$topics = LiteracyTopic::all();
$topics_detail = LiteracyTopic_detail::all();
$topicsCount = count($topics);
$topicsDetailCount = count($topics_detail);
$idUser = Auth::user()->id;
$roleTeacher = DB::select("select role from users where id = $idUser");
// Retrieve all completed topics based on role
if ($roleTeacher[0]->role == "student") {
$completedTopics = DB::table('react_student_enroll')
->join('users', 'react_student_enroll.id_users', '=', 'users.id')
->join('react_topics_detail', 'react_student_enroll.php_topics_detail_id', '=', 'react_topics_detail.id')
->select('react_student_enroll.*', 'users.name as user_name', 'react_topics_detail.*')
->where('react_student_enroll.id_users', $idUser)
->get();
} else if ($roleTeacher[0]->role == "teacher") {
$completedTopics = DB::table('react_student_enroll')
->join('users', 'react_student_enroll.id_users', '=', 'users.id')
->join('react_topics_detail', 'react_student_enroll.php_topics_detail_id', '=', 'react_topics_detail.id')
->select('react_student_enroll.*', 'users.name as user_name', 'react_topics_detail.*')
->get();
} else {
; // Return an empty collection if role is neither student nor teacher
}
// Calculate progress for each user
$progress = [];
foreach ($completedTopics as $enrollment) {
$userId = $enrollment->id_users;
if (!isset($progress[$userId])) {
$userCompletedCount = DB::table('react_student_enroll')
->where('id_users', $userId)
->count();
$progress[$userId] = [
'name' => $enrollment->user_name,
'percent' => round(($userCompletedCount / $topicsDetailCount) * 100, 2)
];
}
}
return view('literacy.student.material.index',[
'result_up' => $actual,
'topics' => $topics_detail,
'topicsCount' => $topicsDetailCount,
'completedTopics' => $completedTopics,
'role' => $roleTeacher[0] ? $roleTeacher[0]->role : '',
'progress' => $progress
]);
}
function php_material_detail(){
$phpid = isset($_GET['phpid']) ? (int)$_GET['phpid'] : '';
$start = isset($_GET['start']) ? (int)$_GET['start'] : '';
$output = isset($_GET['output']) ? $_GET['output'] : '';
$results = DB::select("select * from react_topics_detail where id_topics =$start and id ='$phpid' ");
foreach($results as $r){
if( $start == $r->id_topics){
if(empty($r->file_name)){
$contain = $r->description;
$pdf_reader = 0;
}else{
$contain = $r->file_name;
$pdf_reader = 1;
}
$html_start = $this->html_persyaratan($contain,$pdf_reader);
}else{
$html_start = "";
}
}
// dd($html_start);
// $listTask = DB::select("select aa.*, us.name from react_user_submits aa join users us on aa.userid = us.id where php_id = $phpid and php_id_topic = $start ");
$idUser = Auth::user()->id;
$roleTeacher = DB::select("select role from users where id = $idUser");
$topics = LiteracyTopic::all();
$detail = LiteracyTopic::findorfail($start);
$topicsCount = count($topics);
$detailCount = ($topicsCount/$topicsCount)*10;
$topics_detail = LiteracyTopic_detail::all();
// Check if the record already exists
$exists = DB::table('react_student_enroll')
->where('id_users', Auth::user()->id)
->where('php_topics_detail_id', $phpid)
->exists();
if (!$exists) {
// Record does not exist, so insert a new one
$flags = $results[0]->flag ?? 0;
if ( $flags == 0) {
DB::table('react_student_enroll')->insert([
'id_users' => Auth::user()->id,
'php_topics_detail_id' => $phpid,
'created_at' => now()
]);
}
}
$completedTopics = LiteracyUserEnroll::where('id_users', Auth::user()->id)->distinct('php_topics_detail_id')->count();
$progress = ( $completedTopics/ count($topics_detail) ) * 100;
return view('literacy.student.material.topics_detail',[
'row' => $detail,
'topics' => $topics,
'phpid' => $phpid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader ,
'topicsCount'=> $topicsCount ,
'detailCount'=> $detailCount ,
'output' => $output,
'flag' => $results[0] ? $results[0]->flag : 0,
'listTask' => [],
'role' => $roleTeacher[0] ? $roleTeacher[0]->role : '',
'progress' => round($progress, 0)
]);
}
function html_start(){
$html ="<div style='text-align:center;font-size:18px'><em>Modul kelas Belajar Pengembangan Aplikasi Android Intermediate dalam bentuk cetak (buku) maupun elektronik sudah didaftarkan ke Dirjen HKI, Kemenkumham RI. Segala bentuk penggandaan dan atau komersialisasi, sebagian atau seluruh bagian, baik cetak maupun elektronik terhadap modul kelas <em>Belajar Pengembangan Aplikasi Android Intermediate</em> tanpa izin formal tertulis kepada pemilik hak cipta akan diproses melalui jalur hukum.</em></div>";
return $html;
}
function html_persyaratan($desc,$pdf_reader){
$html = $desc;
return $html;
}
public function upload(Request $request)
{
if ($request->hasFile('upload')) {
$originName = $request->file('upload')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('upload')->getClientOriginalExtension();
$fileName = $fileName . '_' . time() . '.' . $extension;
$request->file('upload')->move(public_path('media'), $fileName);
$url = asset('media/' . $fileName);
return response()->json(['fileName' => $fileName, 'uploaded' => 1, 'url' => $url]);
}
}
function send_task(){
$phpid = isset($_GET['phpid']) ? $_GET['phpid'] : '';
$task = isset($_GET['task']) ? $_GET['task'] : '';
$output = isset($_GET['output']) ? $_GET['output'] : '';
$task_db = DB::table('php_task')
->where('id_topics', $phpid)
->where('id', $task)
->first();
if($task_db->id == $task){
$html_start = $this->html_task();
}else{
$html_start = "";
}
$pdf_reader = 0;
$topics = Topic::all();
$detail = Topic::findorfail($phpid);
$topicsCount = count($topics);
$persen = ($topicsCount/$topicsCount)*10;
session(['params' => $persen]);
return view('php.student.material.topics_detail',[
'row' => $detail,
'topics' => $topics,
'phpid' => $phpid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader ,
'detailCount'=> $persen,
'output' => $output,
]);
}
function html_task(){
return view('php.student.task.form_submission_task',[]);
}
function php_admin(){
return view('php.admin.material.upload_materi',[]);
}
function task_submission(Request $request){
$phpid = (int)$request->get('phpid');
$start = (int)$request->get('start');
$this->validate($request, [
'file' => 'required',
]);
// menyimpan data file yang diupload ke variabel $file
$file = $request->file('file');
$file_name = Auth::user()->name.'_'.$file->getClientOriginalName();
Storage::disk('public')->makeDirectory('private/'.Auth::user()->name);
Storage::disk('public')->put('/private/'.Auth::user()->name.'/'.$file_name,File::get($file));
$userName = Auth::user()->name;
Session::put('user_name', $userName);
$user_name = Session::get('user_name');
$name = Session::put('ori_file_name', $file_name);
$path = storage_path("app/private/{$userName}/{$file_name}");
Session::put('path', $path);
$val = session('key');
// DB::select("TRUNCATE TABLE php_user_submits");
// DB::insert("insert into php_user_submits(userid) values ('$val')");
$phpunitExecutable = base_path('vendor/bin/phpunit');
Storage::disk('local')->put('/private/testingunit/testingunit.php',File::get($file));
if($start == 43){
$unitTest = base_path('tests/CreateDatabase.php');
}else if($start == 42){
$unitTest = base_path('tests/CheckConnection.php');
}else if($start == 44){
$unitTest = base_path('tests/CreateTable.php');
}else if($start == 45){
$unitTest = base_path('tests/CreateTableGuru.php');
}else if($start == 46){
$unitTest = base_path('tests/CheckInsert.php');
}else if($start == 47){
$unitTest = base_path('tests/CheckInsertGuru.php');
}else if($start == 48){
$unitTest = base_path('tests/CheckInsertHtml.php');
}else if($start == 49){
$unitTest = base_path('tests/CheckInsertHtmlGuru.php');
}else if($start == 50){
$unitTest = base_path('tests/CheckSelectHtml.php');
}else if($start == 51){
$unitTest = base_path('tests/CheckSelectHtmlGuru.php');
}else if($start == 52){
$unitTest = base_path('tests/CheckUpdateHtml.php');
}else if($start == 53){
$unitTest = base_path('tests/CheckUpdateHtmlGuru.php');
}else if($start == 54){
$unitTest = base_path('tests/CheckDeleteHtml.php');
}else if($start == 55){
$unitTest = base_path('tests/CheckDeleteHtmlGuru.php');
}
// Run PHPUnit tests using exec
$output = [];
$returnVar = 0;
exec("$phpunitExecutable $unitTest", $output, $returnVar);
Storage::deleteDirectory('/private/testingunit');
// Output the results
$outputString = "<br>PHPUnit Output: <br>";
$outputString .= implode("<br>", $output) . "<br>";
$outputString .= "Return Code: $returnVar<br>";
// dd($output);
$idUser = Auth::user()->id;
$pathuser = 'storage/private/'.$userName.'/'.$file_name.'';
$flag = $returnVar == 0 ? 'true' : 'false';
DB::insert("INSERT INTO php_user_submits(userid, path, flag, php_id, php_id_topic) values ('$idUser', '$pathuser', '$flag', $phpid, $start)");
// php_user_submits
return redirect('/php/detail-topics?phpid='.$phpid.'&start='.$start.'&output='.$outputString.'');
}
function unittesting2(){
$val = session('key');
DB::select("TRUNCATE TABLE php_user_submits");
DB::insert("insert into php_user_submits(userid) values ('$val')");
$path_test = base_path("phpunit.xml");
$path = base_path("vendor\bin\phpunit -c $path_test");
$output = shell_exec($path);
// echo dd($output);
// echo json_encode($output);
$string = htmlentities($output);
$string = str_replace("\n", ' ', $string);
$pattern = '/PHPUnit\s+(\d+\.\d+\.\d+).*Runtime:\s+PHP\s+(\d+\.\d+\.\d+).*Time:\s+(\d+:\d+\.\d+),\s+Memory:\s+(\d+\.\d+)\s+MB\s+OK\s+\((\d+)\stests,\s+(\d+)\sassertions\)/';
if (preg_match($pattern, $string, $matches)) {
$phpUnitVersion = $matches[1];
$phpVersion = $matches[2];
$executionTime = $matches[3];
$memoryUsage = $matches[4];
$numTests = $matches[5];
$numAssertions = $matches[6];
// Output the extracted information
echo "PHPUnit version: $phpUnitVersion <br />";
echo "PHP version: $phpVersion <br />";
echo "Execution time: $executionTime <br />";
echo "Memory usage: $memoryUsage MB <br />";
echo "Number of tests: $numTests <br />";
echo "Number of assertions: $numAssertions <br />";
$ok_position = strpos($string, 'OK');
if ($ok_position !== false) {
$ok_part = substr($string, $ok_position);
echo "Tests Run : ". $ok_part;
}
}else{
$string = json_encode($output);
$text = str_replace("\n", ' ', $output);
// Define patterns to extract relevant information
$pattern_phpunit_version = '/PHPUnit\s+(\d+\.\d+\.\d+)/';
$pattern_php_runtime = '/Runtime:\s+PHP\s+([\d.]+)/';
$pattern_configuration = '/Configuration:\s+(.+)/';
$pattern_failure_count = '/There was (\d+) failure/';
$pattern_failure_test_case = '/Failed asserting that \'(.*?)\' contains \'(.*?)\'./';
$pattern_failure_location = '/(C:\\\\.*?\\.php):(\d+)/';
// Perform matching
preg_match($pattern_phpunit_version, $text, $matches_phpunit_version);
preg_match($pattern_php_runtime, $text, $matches_php_runtime);
preg_match($pattern_configuration, $text, $matches_configuration);
preg_match($pattern_failure_count, $text, $matches_failure_count);
preg_match($pattern_failure_test_case, $text, $matches_failure_test_case);
preg_match($pattern_failure_location, $text, $matches_failure_location);
// Extracted information
$phpunit_version = isset($matches_phpunit_version[1]) ? $matches_phpunit_version[1] : "Not found";
$php_runtime = isset($matches_php_runtime[1]) ? $matches_php_runtime[1] : "Not found";
$configuration_path = isset($matches_configuration[1]) ? $matches_configuration[1] : "Not found";
$num_failures = isset($matches_failure_count[1]) ? $matches_failure_count[1] : "Not found";
$failed_assertion = isset($matches_failure_test_case[1]) ? htmlspecialchars($matches_failure_test_case[1]) : "Not found";
$expected_content = isset($matches_failure_test_case[2]) ? htmlspecialchars($matches_failure_test_case[2]) : "Not found";
$failure_location = isset($matches_failure_location[1]) ? $matches_failure_location[1] : "Not found";
$failure_line = isset($matches_failure_location[2]) ? $matches_failure_location[2] : "Not found";
// Output extracted information
echo "PHPUnit version: $phpunit_version <br >";
echo "PHP Runtime: $php_runtime <br >";
echo "Configuration path: $configuration_path <br >";
echo "Number of failures: $num_failures <br >";
echo "Failed assertion: $failed_assertion <br >";
echo "Expected content: $expected_content <br >";
echo "Failure location: $failure_location <br >";
echo "Failure line: $failure_line <br >";
}
}
function unittesting(){
$namaFile = 'febri syawaldi_CreateDB.php';
$phpunitExecutable = base_path('vendor/bin/phpunit');
$unitTest = base_path('tests/FileReadTest.php');
// Run PHPUnit tests using exec
$output = [];
$returnVar = 0;
exec("$phpunitExecutable $unitTest", $output, $returnVar);
// Output the results
// echo "PHPUnit Output: <br>";
// echo implode("<br>", $output) . "<br>";
// echo "Return Code: $returnVar<br>";
return response()->json($output);
}
function session_progress(){
// Cek Ada Data Tidak, Enrollement User
// Jika Tidak Insert
session(['params' => $_POST['params']]);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use App\Models\Literacy\LiteracyMaterial;
use App\Models\Literacy\LiteracyQuestion;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class LiteracyGenerateQuestionsController extends Controller
{
function generate_questions()
{
$generate_questions = LiteracyQuestion::all();
$materials = LiteracyMaterial::all();
$users = User::all();
$questions = LiteracyQuestion::all();
return view('literacy.teacher.generate_questions.index', [
'generate_questions' => $generate_questions,
'materials' => $materials,
'users' => $users,
'questions' => $questions,
]);
}
function generate_from_ai(Request $request)
{
try {
$fastApiUrl = 'http://127.0.0.1:8001/generate/'; // Pastikan ini benar
$response = Http::timeout(60)->post($fastApiUrl, [
'content' => $request->input('content'),
]);
if ($response->successful()) {
return response()->json([
'status' => 'success',
'generated_questions' => $response->json()['generated_questions'] ?? "No questions generated."
]);
} else {
return response()->json([
'status' => 'error',
'message' => 'Failed to fetch questions from AI.',
'fastapi_response' => $response->body()
], $response->status());
}
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'Internal Server Error: ' . $e->getMessage()
], 500);
}
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use App\Models\Literacy\LiteracyTopic;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use App\Models\PHP\Topic;
use App\Models\Literacy\LiteracyTopic_detail;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Auth;
class LiteracyGuruController extends Controller{
// public function topics()
// {
// $topics = LiteracyTopic::all();
// return view('literacy.teacher.materials.index', compact('materials'));
// }
function topics(){
$topics = LiteracyTopic::all();
return view('literacy.teacher.topics',[
'topics' => $topics,
]);
}
function add_topics( Request $request, $id ){
$detail = LiteracyTopic::findorfail($id);
$results = DB::select('SELECT * FROM react_topics_detail WHERE id_topics = ?', [$id]);
return view('literacy.teacher.topics_detail',[
'results' => $results,
'detail' => $detail->title,
'id' => $id,
]);
}
function simpan( Request $request){
$validatedData = $request->validate([
'title' => 'required|string|max:255',
'caption' => 'required|string|max:255',
'editor' => 'required|string',
'id' => 'required|int|max:11',
'materials' => 'required|file|max:10240', // Example: max 10MB
]);
// Periksa apakah file yang diunggah bukan PDF
if ($request->file('materials')->getClientOriginalExtension() !== 'pdf') {
return redirect()->back()->with('error', 'Materi harus berupa file PDF.');
}
$originName = $request->file('materials')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('materials')->getClientOriginalExtension();
$fileName = $fileName . '_' . time() . '.' . $extension;
$new_name = str_replace(" ",'_',$fileName);
$path = $request->file('materials')->move(public_path('react/document/A1_BASIC_PHP'), $new_name);
LiteracyTopic_detail::create([
'title' => $validatedData['title'],
'id_topics' => $validatedData['id'],
'controller' => $validatedData['caption'],
'description' => $validatedData['editor'],
'folder_path' => $path, // Save the path to the uploaded file
'file_name' => $new_name,
]);
$id = $validatedData['id'];
return Redirect::to("/literacy/teacher/topics/add/$id")->with('message', 'Data Berhasil Diinputkan');
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use App\Models\Literacy\LiteracyMaterial;
use App\Models\Literacy\LiteracyQuestion;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class LiteracyMaterialController extends Controller
{
function materials()
{
$materials = LiteracyMaterial::all();
$users = User::all();
$questions = LiteracyQuestion::all();
return view('literacy.teacher.materials.index', [
'materials' => $materials,
'users' => $users,
'questions' => $questions,
]);
}
public function show($id)
{
$material = LiteracyMaterial::findOrFail($id);
return view('literacy.teacher.materials.index', compact('material'));
}
public function create()
{
return view('literacy.teacher.materials.index');
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'file_path' => 'nullable|file|mimes:pdf,doc,docx|max:2048' // Ubah 'file_path' jadi 'file'
]);
$filePath = null;
if ($request->hasFile('file')) {
// Simpan file dengan nama asli ke folder public/literacy/document
$originalName = $request->file('file')->getClientOriginalName();
$filePath = 'literacy/document/' . $originalName;
$request->file('file')->move(public_path('literacy/document'), $originalName);
}
LiteracyMaterial::create([
'title' => $request->title,
'description' => $request->description,
'file_path' => $filePath, // Simpan path relatif ke folder public
'user_id' => auth()->id()
]);
return redirect()->route('literacy_teacher_materials');
}
public function edit($id)
{
$material = LiteracyMaterial::findOrFail($id);
return view('literacy.teacher.materials.index', compact('material'));
}
public function update(Request $request, $id)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'file_path' => 'nullable|file|mimes:pdf,doc,docx|max:2048' // Ubah 'file_path' jadi 'file'
]);
$material = LiteracyMaterial::findOrFail($id);
$filePath = $material->file_path;
if ($request->hasFile('file')) {
// Hapus file lama jika ada
if ($filePath) {
$oldFilePath = public_path($filePath);
if (file_exists($oldFilePath)) {
unlink($oldFilePath);
}
}
// Simpan file baru dengan nama asli ke folder public/literacy/document
$originalName = $request->file('file')->getClientOriginalName();
$filePath = 'literacy/document/' . $originalName;
$request->file('file')->move(public_path('literacy/document'), $originalName);
}
$material->update([
'title' => $request->title,
'description' => $request->description,
'file_path' => $filePath
]);
return redirect()->route('literacy_teacher_materials');
}
public function destroy($id)
{
$material = LiteracyMaterial::findOrFail($id);
// Hapus file kalau ada
if ($material->file_path) {
$filePath = public_path($material->file_path); // Mendapatkan path lengkap di public
if (file_exists($filePath)) {
unlink($filePath); // Hapus file secara langsung
}
}
$material->delete();
return redirect()->route('literacy_teacher_materials');
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers\Literacy;
use App\Http\Controllers\Controller;
use App\Models\Literacy\LiteracyQuestion;
use App\Models\Literacy\LiteracyAnswer;
use App\Models\Literacy\LiteracyMaterial;
use App\Models\Literacy\LiteracyOption;
use App\Models\Literacy\LiteracyAssessment;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class LiteracyQuestionController extends Controller
{
public function questions()
{
$questions = LiteracyQuestion::all();
$materials = LiteracyMaterial::all();
$users = User::all();
return view('literacy.teacher.questions.index', [
'questions' => $questions,
'materials' => $materials,
'users' => $users,
]);
}
public function show($id)
{
$questions = LiteracyQuestion::findOrFail($id);
return view('literacy.teacher.questions.index', compact('questions'));
}
public function create()
{
return view('literacy.teacher.questions.index');
}
public function store(Request $request)
{
$validated = $request->validate([
'material_id' => 'required|exists:materials,id',
'question_text' => 'required|string',
'type' => 'required|in:multiple_choice,essay',
]);
$question = LiteracyQuestion::create($validated);
// Jika tipe soal adalah pilihan ganda, simpan opsi jawaban
if ($request->type === 'multiple_choice' && $request->has('options')) {
foreach ($request->options as $option) {
$question->options()->create([
'option_text' => $option['text'] ?? null, // Gunakan null jika tidak ada teks
'score' => $option['score'] ?? 0, // Pastikan skor selalu ada
'is_correct' => isset($option['is_correct']) ? 1 : 0,
]);
}
}
// Jika tipe soal adalah isian (essay), simpan skor & jawaban yang benar
elseif ($request->type === 'essay') {
$question->update([
'essay_score' => $request->essay_score ?? 0,
'essay_answer' => $request->essay_answer ?? null,
]);
}
return redirect()->back()->with('success', 'Pertanyaan berhasil ditambahkan.');
}
public function publishAssessment()
{
$users = User::all();
$questionsCount = LiteracyQuestion::count();
if ($questionsCount === 0) {
return redirect()->back()->with('error', 'Belum ada soal yang tersedia untuk asesmen.');
}
foreach ($users as $user) {
LiteracyAssessment::firstOrCreate(
['user_id' => $user->id],
[
'status' => 'pending',
'score' => 0,
'feedback' => '',
'assessed_at' => null,
'created_at' => now(),
'updated_at' => now(),
]
);
}
return redirect()->back()->with('success', 'Asesmen berhasil dipublikasikan untuk semua siswa.');
}
public function edit($id)
{
$question = LiteracyQuestion::with('options')->findOrFail($id);
return view('literacy.teacher.questions.index', compact('question'));
}
public function update(Request $request, $id)
{
$request->validate([
'material_id' => 'required|exists:materials,id',
'question_text' => 'required|string|max:255',
'type' => 'required|string|in:multiple_choice,essay',
'essay_score' => 'nullable|integer|min:0|max:100',
'essay_answer' => 'nullable|string',
'options.*.id' => 'nullable|exists:literacy_options,id',
'options.*.option_text' => 'required_if:type,multiple_choice|string|max:255',
'options.*.score' => 'nullable|integer|min:0',
'options.*.is_correct' => 'nullable|boolean',
]);
$question = LiteracyQuestion::findOrFail($id);
$question->update([
'material_id' => $request->material_id, // Tambahkan material_id
'question_text' => $request->question_text,
'type' => $request->type,
'essay_score' => $request->type === 'essay' ? $request->essay_score : null,
'essay_answer' => $request->type === 'essay' ? $request->essay_answer : null,
]);
// Jika pertanyaan adalah pilihan ganda, kelola opsi jawaban
if ($request->type === 'multiple_choice' && $request->has('options')) {
$existingOptionIds = $question->options->pluck('id')->toArray();
$newOptionIds = collect($request->options)->pluck('id')->filter()->toArray();
// Hapus opsi yang tidak ada di input baru
$optionsToDelete = array_diff($existingOptionIds, $newOptionIds);
LiteracyOption::whereIn('id', $optionsToDelete)->delete();
foreach ($request->options as $option) {
if (isset($option['id'])) {
// Update opsi yang sudah ada
LiteracyOption::where('id', $option['id'])->update([
'option_text' => $option['option_text'],
'score' => $option['score'] ?? 0,
'is_correct' => isset($option['is_correct']) ? 1 : 0,
]);
} else {
// Tambahkan opsi baru
$question->options()->create([
'option_text' => $option['option_text'],
'score' => $option['score'] ?? 0,
'is_correct' => isset($option['is_correct']) ? 1 : 0,
]);
}
}
}
return redirect()->route('literacy_teacher_questions')->with('success', 'Pertanyaan berhasil diperbarui!');
}
public function destroy($id)
{
$question = LiteracyQuestion::findOrFail($id);
$question->delete();
return redirect()->route('literacy_teacher_questions')->with('success', 'Question deleted successfully.');
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers\Literacy;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Literacy\LiteracyMaterial;
use App\Models\Literacy\LiteracyQuestion;
use Illuminate\Support\Facades\Hash;
class LiteracyUserController extends Controller
{
// 🛡 Middleware untuk autentikasi dan cek admin
public function __construct()
{
$this->middleware(['auth', 'teacher']);
}
// 📋 1. Menampilkan daftar pengguna
public function users()
{
$users = User::all();
$materials = LiteracyMaterial::all();
$questions = LiteracyQuestion::all();
return view('literacy.teacher.users.index', [
'materials' => $materials,
'users' => $users,
'questions' => $questions,
]);
}
public function show($id)
{
$user = User::findOrFail($id);
return view('literacy.teacher.users.index', compact('user'));
}
// 2. Form tambah pengguna
public function create()
{
return view('literacy.teacher.users.index');
}
// 💾 3. Menyimpan pengguna baru
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:6',
'role' => 'required|in:teacher,student',
]);
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => $request->role,
]);
return redirect()->route('literacy_teacher_users')->with('success', 'Pengguna berhasil ditambahkan!');
}
// 🛠 4. Form edit pengguna
public function edit($id)
{
$user = User::findOrFail($id);
return view('literacy.teacher.users.index', compact('user'));
}
// 🔄 5. Update pengguna
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . $user->id,
'role' => 'required|in:teacher,student',
]);
$user->update([
'name' => $request->name,
'email' => $request->email,
'role' => $request->role,
]);
return redirect()->route('literacy_teacher_users')->with('success', 'Pengguna berhasil diperbarui!');
}
// ❌ 6. Hapus pengguna
public function destroy($id)
{
$user = User::findOrFail($id);
$user->delete();
return redirect()->route('literacy_teacher_users')->with('success', 'Pengguna berhasil dihapus!');
}
}

View File

@ -0,0 +1,250 @@
<?php
namespace App\Http\Controllers\Literacy\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Literacy\LiteracySubmitUser;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class LiteracyLogicalController extends Controller
{
// function upload file
public function uploadFile(Request $request)
{
try {
$request->validate([
'uploadFile' => 'required|file|max:1024',
]);
if ($request->hasFile('uploadFile')) {
$uploadedFile = $request->file('uploadFile');
$userName = Auth::user()->name;
$fileName = $uploadedFile->getClientOriginalName();
$uploadedFile->storeAs('private/' . $userName . '/React', $fileName);
$materiType = $this->getMateriType($fileName);
$comparisonResult = $this->compareFile($materiType, $uploadedFile);
// Menyimpan data ke database dengan status berdasarkan hasil perbandingan
try {
$submitUser = new LiteracySubmitUser();
$submitUser->id_user = Auth::id();
$submitUser->nama_user = $userName;
$submitUser->materi = 'React - ' . $fileName;
$submitUser->nilai = $comparisonResult === 'Congratulations, your answer is correct.' ? 100 : 0;
$submitUser->status = $comparisonResult === 'Congratulations, your answer is correct.' ? 'True' : 'False';
$submitUser->save();
} catch (\Exception $dbException) {
// Log error and return response
Log::error('Database save failed: ' . $dbException->getMessage());
return response()->json([
'error' => 'Database save failed.',
'message' => 'There was an error saving your result to the database.',
], 500);
}
return response()->json([
'uploaded' => true,
'fileName' => $fileName,
'message' => 'File uploaded successfully.',
'comparisonResult' => $comparisonResult,
]);
} else {
return response()->json([
'error' => 'File upload failed.',
'message' => 'The upload file field is required.',
], 400);
}
} catch (\Exception $e) {
Log::error('File upload failed: ' . $e->getMessage());
return response()->json([
'error' => 'File upload failed.',
'message' => $e->getMessage(),
], 500);
}
}
// function menentukan tipe materi
private function getMateriType($fileName)
{
if (strpos($fileName, 'Hello.js') !== false) {
return 'hello';
} elseif (strpos($fileName, 'Form.js') !== false) {
return 'form';
} elseif (strpos($fileName, 'Counter.js') !== false) {
return 'counter';
} elseif (strpos($fileName, 'FormStyle.js') !== false) {
return 'formStyle';
} elseif (strpos($fileName, 'Navbar.js') !== false) {
return 'navbar';
} else {
return 'unknown';
}
}
// function mengecek jawaban
private function compareFile($materiType, $uploadedFile)
{
switch ($materiType) {
case 'hello':
return $this->jawabanHello($uploadedFile);
case 'form':
return $this->jawabanForm($uploadedFile);
case 'counter':
return $this->jawabanCounter($uploadedFile);
case 'formStyle':
return $this->jawabanFormStyle($uploadedFile);
case 'navbar':
return $this->jawabanNavbar($uploadedFile);
default:
throw new \Exception('Harus Sesuai Materi');
}
}
// function mencari perbedaan
private function getDifference($uploadedContent, $answerKeyContent)
{
// Membandingkan baris per baris untuk menemukan perbedaan
$uploadedLines = explode("\n", $uploadedContent);
$answerKeyLines = explode("\n", $answerKeyContent);
$diffLines = [];
foreach ($uploadedLines as $lineNumber => $line) {
if (!isset($answerKeyLines[$lineNumber]) || $line !== $answerKeyLines[$lineNumber]) {
$diffLines[] = [
'line_number' => $lineNumber + 1,
'uploaded_line' => $line,
'answer_key_line' => isset($answerKeyLines[$lineNumber]) ? $answerKeyLines[$lineNumber] : '',
];
}
}
$diffMessage = '';
foreach ($diffLines as $diffLine) {
$diffMessage .= "Line{$diffLine['line_number']}: \n";
$diffMessage .= "Your Answer: {$diffLine['uploaded_line']} \n";
$diffMessage .= "Should: {$diffLine['answer_key_line']} \n\n";
}
return $diffMessage;
}
// function hello
private function jawabanHello($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Hello.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
// function form
private function jawabanForm($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Form.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
// function counter
private function jawabanCounter($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Counter.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
private function jawabanFormStyle($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/FormStyle.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
private function jawabanNavbar($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Navbar.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
public function getComparisonResults($userId)
{
$results = LiteracySubmitUser::where('id_user', $userId)->get();
return response()->json($results);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use App\Models\NodeJS\Project;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
class DashboardController extends Controller
{
public function index(Request $request)
{
$user = $request->user();
$projects = Project::skip(0)->take(3)->get();
if ($request->ajax()) {
$data = DB::connection('nodejsDB')->table('projects')
->select('projects.id', 'projects.title', DB::raw('COUNT(submissions.id) as submission_count'))
->leftJoin('submissions', function ($join) use ($user) {
$join->on('projects.id', '=', 'submissions.project_id')
->where('submissions.user_id', '=', $user->id);
})
->groupBy('projects.id');
return Datatables::of($data)
->addIndexColumn()
->addColumn('title', function ($row) {
$title_button = '<a href="/nodejs/submissions/project/' . $row->id . '" class="underline text-secondary">' . $row->title . '</a>';
return $title_button;
})
->addColumn('submission_count', function ($row) {
$submission_count = $row->submission_count ?? 0;
$text = $submission_count > 0 ? 'Submitted' : 'No Submission';
$span_color = $submission_count > 0 ? 'green' : 'gray';
$span = '<span class="inline-flex items-center justify-center px-2 py-1 rounded-lg text-xs font-bold leading-none bg-' . $span_color . '-100 text-' . $span_color . '-800">' . ucfirst($text) . '</span>';
return $span;
})
->rawColumns(['title', 'submission_count'])
->make(true);
}
return view('nodejs.dashboard.index', compact('projects'));
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ExecutionStepController extends Controller
{
//
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $request->user(),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}

View File

@ -0,0 +1,185 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use Exception;
use ZipArchive;
use App\Models\NodeJS\Project;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Symfony\Component\Process\Process;
class ProjectController extends Controller
{
public function index(Request $request)
{
$projects = Project::all();
return view('nodejs.projects.index', compact('projects'));
}
public function show(Request $request, $project_id)
{
$project = Project::find($project_id);
if (!$project) {
return redirect()->route('projects');
}
return view('nodejs.projects.show', compact('project'));
}
public function showPDF(Request $request)
{
if ($request->ajax()) {
if ($request->id) {
$mediaModel = new Media();
$mediaModel->setConnection('nodejsDB');
$media = $mediaModel->find($request->id);
if ($media) {
$path = $media->getUrl();
return response()->json($path, 200);
}
return response()->json(["message" => "media not found"], 404);
}
return response()->json(["message" => "no media was requested"], 400);
}
}
public function download(Request $request, $project_id)
{
if ($request->ajax()) {
$project = Project::find($project_id);
if (!$project) {
return response()->json(["message" => "project not found"], 404);
}
if ($request->type) {
switch ($request->type) {
case 'guides':
$zipMedia = $project->getMedia('project_zips')->where('file_name', 'guides.zip')->first();
if ($zipMedia) {
return response()->json($zipMedia->getUrl(), 200);
} else {
$guides = $project->getMedia('project_guides');
$tempDir = storage_path('app/public/assets/nodejs/projects/' . $project->title . '/zips');
if (!is_dir($tempDir)) mkdir($tempDir);
foreach ($guides as $guide) {
$path = $guide->getPath();
$filename = $guide->file_name;
copy($path, $tempDir . '/' . $filename);
}
$zipPath = $tempDir . '/guides.zip';
$zip = new ZipArchive;
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
$files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips');
foreach ($files as $file) {
$zip->addFile(storage_path('app/' . $file), basename($file));
}
$zip->close();
foreach ($files as $file) {
unlink(storage_path('app/' . $file));
}
} else {
throw new Exception('Failed to create zip archive');
}
$media = $project->addMedia($zipPath)->toMediaCollection('project_zips', 'nodejs_public_projects_files');
return response()->json($media->getUrl(), 200);
}
break;
case 'supplements':
$zipMedia = $project->getMedia('project_zips')->where('file_name', 'supplements.zip')->first();
if ($zipMedia) {
return response()->json($zipMedia->getUrl(), 200);
} else {
$supplements = $project->getMedia('project_supplements');
$tempDir = storage_path('app/public/assets/nodejs/projects/' . $project->title . '/zips');
if (!is_dir($tempDir)) mkdir($tempDir);
foreach ($supplements as $supplement) {
$path = $supplement->getPath();
$filename = $supplement->file_name;
copy($path, $tempDir . '/' . $filename);
}
$zipPath = $tempDir . '/supplements.zip';
$zip = new ZipArchive;
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
$files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips');
foreach ($files as $file) {
$zip->addFile(storage_path('app/' . $file), basename($file));
}
$zip->close();
foreach ($files as $file) {
unlink(storage_path('app/' . $file));
}
} else {
throw new Exception('Failed to create zip archive');
}
$media = $project->addMedia($zipPath)->toMediaCollection('project_zips', 'nodejs_public_projects_files');
return response()->json($media->getUrl(), 200);
}
break;
case 'tests':
$zipMedia = $project->getMedia('project_zips')->where('file_name', 'tests.zip')->first();
if ($zipMedia) {
return response()->json($zipMedia->getUrl(), 200);
} else {
$tests_api = $project->getMedia('project_tests_api');
$tests_web = $project->getMedia('project_tests_web');
$tests_images = $project->getMedia('project_tests_images');
$tempDir = storage_path('app/public/assets/nodejs/projects/' . $project->title . '/zips');
if (!is_dir($tempDir)) mkdir($tempDir);
if (!is_dir($tempDir . '/tests')) mkdir($tempDir . '/tests');
if (!is_dir($tempDir . '/tests/api')) mkdir($tempDir . '/tests/api');
if (!is_dir($tempDir . '/tests/web')) mkdir($tempDir . '/tests/web');
if (!is_dir($tempDir . '/tests/web/images')) mkdir($tempDir . '/tests/web/images');
foreach ($tests_api as $test) {
$path = $test->getPath();
$filename = $test->file_name;
copy($path, $tempDir . '/tests/api/' . $filename);
}
foreach ($tests_web as $test) {
$path = $test->getPath();
$filename = $test->file_name;
copy($path, $tempDir . '/tests/web/' . $filename);
}
foreach ($tests_images as $test) {
$path = $test->getPath();
$filename = $test->file_name;
copy($path, $tempDir . '/tests/web/images/' . $filename);
}
$zipPath = $tempDir . '/tests.zip';
$zip = new ZipArchive;
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
$zip->addEmptyDir('api');
$zip->addEmptyDir('web');
$zip->addEmptyDir('web/images');
$api_files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips/tests/api');
foreach ($api_files as $file) {
$zip->addFile(storage_path('app/' . $file), 'api/' . basename($file));
}
$api_files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips/tests/web');
foreach ($api_files as $file) {
$zip->addFile(storage_path('app/' . $file), 'web/' . basename($file));
}
$image_files = Storage::files('public/assets/nodejs/projects/' . $project->title . '/zips/tests/web/images');
foreach ($image_files as $file) {
$zip->addFile(storage_path('app/' . $file), 'web/images/' . basename($file));
}
$zip->close();
Process::fromShellCommandline("rm -rf {$tempDir}/tests")->run();
} else {
throw new Exception('Failed to create zip archive');
}
$media = $project->addMedia($zipPath)->toMediaCollection('project_zips', 'nodejs_public_projects_files');
return response()->json($media->getUrl(), 200);
}
break;
}
} else {
return response()->json(["message" => "no type was requested"], 400);
}
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ProjectExecutionStepController extends Controller
{
//
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ProjectsDefaultFileStructureController extends Controller
{
//
}

View File

@ -0,0 +1,757 @@
.<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use App\Jobs\NodeJS\AddEnvFile;
use App\Jobs\NodeJS\CloneRepository;
use App\Jobs\NodeJS\CopyTestsFolder;
use App\Jobs\NodeJS\DeleteTempDirectory;
use App\Jobs\NodeJS\ExamineFolderStructure;
use App\Jobs\NodeJS\NpmInstall;
use App\Jobs\NodeJS\NpmRunStart;
use App\Jobs\NodeJS\NpmRunTests;
use App\Jobs\NodeJS\ReplacePackageJson;
use App\Jobs\NodeJS\UnzipZipFiles;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Project;
use App\Models\NodeJS\Submission;
use App\Models\NodeJS\SubmissionHistory;
use App\Models\NodeJS\TemporaryFile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
use Yajra\DataTables\Facades\DataTables;
class SubmissionController extends Controller
{
public function index(Request $request)
{
$user = $request->user();
$projects = Project::all();
if ($request->ajax()) {
$data = DB::connection('nodejsDB')->table('projects')
->select(
'projects.id',
'projects.title',
// DB::raw('(SELECT COUNT(DISTINCT submissions.id) FROM submissions WHERE submissions.project_id = projects.id AND submissions.user_id = ?) as submission_count'),
DB::raw('(SELECT COUNT(*) FROM submission_histories INNER JOIN submissions ON submissions.id = submission_histories.submission_id WHERE submissions.project_id = projects.id AND submissions.user_id = ?) as attempts_count'),
DB::raw('(SELECT status FROM submissions WHERE submissions.project_id = projects.id AND submissions.user_id = ? ORDER BY id DESC LIMIT 1) as submission_status')
)
->groupBy('projects.id', 'projects.title')
->setBindings([
// $user->id,
$user->id,
$user->id
]);
return DataTables::of($data)
->addIndexColumn()
->addColumn('title', function ($row) {
$title_button = '<a href="/nodejs/submissions/project/' . $row->id . '" class="underline text-secondary">' . $row->title . '</a>';
return $title_button;
})
->addColumn('submission_status', function ($row) {
$status = $row->submission_status ?? 'No Submission';
$status_color = ($status == 'completed') ? 'green' : (($status == 'pending') ? 'blue' : (($status == 'processing') ? 'secondary' : 'red'));
$status_button = $status != 'No Submission' ? '<span class="inline-flex items-center justify-center px-2 py-1 rounded-lg text-xs font-bold leading-none bg-' . $status_color . '-100 text-' . $status_color . '-800">' . ucfirst($status) . '</span>'
: '<span class="inline-flex items-center justify-center px-2 py-1 rounded-lg text-xs font-bold leading-none bg-gray-100 text-gray-800">No Submission</span>';
return $status_button;
})
->addColumn('action', function ($row) use ($user) {
$submission = Submission::where('project_id', $row->id)->where('user_id', $user->id)->orderBy('id', 'DESC')->first();
$buttons = '
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
<div @click="open = ! open">
<button
class="flex items-center text-sm font-medium text-gray-900 hover:text-gray-500 dark:text-white dark:hover:text-gray-300 hover:underline">
<svg class="ml-1 h-5 w-5 text-gray-500 dark:text-gray-400"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<g id="Menu / Menu_Alt_02">
<path id="Vector" d="M11 17H19M5 12H19M11 7H19" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</g>
</svg>
</button>
</div>
<div x-show="open"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute z-50 mt-2 w-48 rounded-md shadow-lg origin-top"
style="display: none;"
@click="open = false">
<div class="rounded-md ring-1 ring-black ring-opacity-5 py-1 bg-white dark:bg-gray-700">
';
if ($submission !== null) {
$deleteButton = ' <a data-submission-id="' . $submission->id . '" data-request-type="delete" onclick="requestServer($(this))" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out">Delete submission</a> ';
$restartButton = ' <a data-submission-id="' . $submission->id . '" data-request-type="restart" onclick="requestServer($(this))" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out">Restart submission</a> ';
$changeSourceCodeButton = ' <a data-submission-id="' . $submission->id . '" data-request-type="change-source-code" onclick="requestServer($(this))" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out">Change source code</a> ';
if ($submission->status == 'failed' || $submission->status == 'pending') {
if (!$submission->isGithubUrl()) {
$buttons .= $restartButton . $changeSourceCodeButton . $deleteButton . '</div></div>';
} else {
$buttons .= $restartButton . $deleteButton . '</div></div>';
}
} else if ($submission->status == 'processing') {
$buttons .= $restartButton . $deleteButton . '</div></div>';
} else if ($submission->status == 'completed') {
$buttons .= $deleteButton . '</div></div>';
} else {
$buttons .= '</div></div>';
}
} else {
$buttons = '';
}
return $buttons;
})
->editColumn('attempts_count', function ($row) {
$attempts_count = $row->attempts_count ?? 0;
return $attempts_count + 1;
})
->rawColumns(['title', 'submission_status', 'action'])
->make(true);
}
return view('nodejs.submissions.index', compact('projects'));
}
public function upload(Request $request, $project_id)
{
if ($request->hasFile('folder_path')) {
$project_title = Project::find($project_id)->title;
$file = $request->file('folder_path');
$file_name = $file->getClientOriginalName();
$folder_path = 'public/nodejs/tmp/submissions/' . $request->user()->id . '/' . $project_title;
$file->storeAs($folder_path, $file_name);
TemporaryFile::create([
'folder_path' => $folder_path,
'file_name' => $file_name,
]);
return $folder_path;
}
return '';
}
public function submit(Request $request)
{
try {
$request->validate([
'project_id' => 'required|exists:nodejsDB.projects,id',
'folder_path' => 'required_without:github_url',
'github_url' => 'required_without:folder_path',
]);
if (Submission::where('project_id', $request->project_id)->where('user_id', $request->user()->id)->exists()) {
return response()->json([
'message' => 'Submission already exists',
], 400);
}
$submission = new Submission();
$submission->user_id = $request->user()->id;
$submission->project_id = $request->project_id;
if ($request->has('folder_path')) {
$submission->type = Submission::$FILE;
$submission->path = $request->folder_path;
$temporary_file = TemporaryFile::where('folder_path', $request->folder_path)->first();
if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
$submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path));
}
$temporary_file->delete();
}
} else {
$submission->type = Submission::$URL;
$submission->path = $request->github_url;
}
$submission->status = Submission::$PENDING;
$submission->start = now();
$submission->save();
return response()->json([
'message' => 'Submission created successfully',
'submission' => $submission,
], 201);
} catch (\Throwable $th) {
return response()->json([
'message' => 'Submission failed',
'error' => $th->getMessage(),
], 500);
}
}
public function showAllSubmissionsBasedOnProject(Request $request, $project_id)
{
$project = Project::find($project_id);
$submissions = Submission::where('project_id', $project_id)
->where('user_id', $request->user()->id)->get();
$submission_history = SubmissionHistory::whereIn('submission_id', $submissions->pluck('id')->toArray())->get();
if (!$project) {
return redirect()->route('submissions');
}
return view('nodejs.submissions.show', compact('project', 'submissions', 'submission_history'));
}
public function show(Request $request, $submission_id)
{
$user = Auth::user();
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
if ($submission) {
$steps = $submission->getExecutionSteps();
return view('nodejs.submissions.show', compact('submission', 'steps'));
}
return redirect()->route('submissions');
}
public function history(Request $request, $history_id)
{
$user = Auth::user();
$submission = SubmissionHistory::where('id', $history_id)->where('user_id', $user->id)->first();
if ($submission) {
$steps = $submission->getExecutionSteps();
return view('nodejs.submissions.show', compact('submission', 'steps'));
}
return redirect()->route('submissions');
}
public function status(Request $request, $submission_id)
{
$isNotHistory = filter_var($request->isNotHistory, FILTER_VALIDATE_BOOLEAN);
$user = Auth::user();
$submission = $isNotHistory ? Submission::where('id', $submission_id)->where('user_id', $user->id)->first() : SubmissionHistory::where('id', $submission_id)->where('user_id', $user->id)->first();
if (!$submission) {
return response()->json([
'message' => 'Submission not found',
], 404);
}
$completion_percentage = round($submission->getTotalCompletedSteps() / $submission->getTotalSteps() * 100);
if ($submission->status === Submission::$PENDING) {
return $this->returnSubmissionResponse(($isNotHistory ? "Submission is processing" : "History"), $submission->status, $submission->results, $currentStep ?? null, $completion_percentage);
} else if ($submission->status === Submission::$FAILED) {
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has failed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
} else if ($submission->status === Submission::$COMPLETED) {
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has completed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
} else if ($submission->status === Submission::$PROCESSING) {
$step = $isNotHistory ? $submission->getCurrentExecutionStep() : null;
if ($step) {
return $this->returnSubmissionResponse(
$isNotHistory ? 'Step ' . $step->executionStep->name . ' is ' . $submission->results->{$step->executionStep->name}->status : "History",
$submission->status,
$submission->results,
$step,
$completion_percentage
);
}
return $this->returnSubmissionResponse(
($isNotHistory ? 'Submission is processing meanwhile there is no step to execute' : "History"),
$submission->status,
$submission->results,
$step,
$completion_percentage
);
}
}
public function process(Request $request)
{
if ($request->submission_id == null || $request->isNotHistory == null) return response()->json([
'message' => 'Submission ID is required',
], 404);
$isNotHistory = filter_var($request->isNotHistory, FILTER_VALIDATE_BOOLEAN);
$user = Auth::user();
$submission = $isNotHistory ? Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first() : SubmissionHistory::where('id', $request->submission_id)->where('user_id', $user->id)->first();
if ($submission) {
$completion_percentage = round($submission->getTotalCompletedSteps() / $submission->getTotalSteps() * 100);
if ($submission->status === Submission::$PENDING) {
if ($isNotHistory) {
$submission->initializeResults();
$submission->updateStatus(Submission::$PROCESSING);
$currentStep = $submission->getCurrentExecutionStep();
}
return $this->returnSubmissionResponse(($isNotHistory ? "Submission is processing" : "History"), $submission->status, $submission->results, $currentStep ?? null, $completion_percentage);
} else if ($submission->status === Submission::$COMPLETED) {
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has completed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
} else if ($submission->status === Submission::$FAILED) {
return $this->returnSubmissionResponse(($isNotHistory ? "Submission has failed" : "History"), $submission->status, $submission->results, null, $completion_percentage);
} else if ($submission->status === Submission::$PROCESSING) {
$step = $isNotHistory ? $submission->getCurrentExecutionStep() : null;
if ($step) {
if ($submission->results->{$step->executionStep->name}->status == Submission::$PENDING) {
$submission->updateOneResult($step->executionStep->name, Submission::$PROCESSING, " ");
switch ($step->executionStep->name) {
case ExecutionStep::$CLONE_REPOSITORY:
$this->lunchCloneRepositoryJob($submission, $submission->path, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$UNZIP_ZIP_FILES:
$zipFileDir = $submission->getMedia('submissions')->first()->getPath();
$this->lunchUnzipZipFilesJob($submission, $zipFileDir, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$EXAMINE_FOLDER_STRUCTURE:
$this->lunchExamineFolderStructureJob($submission, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$ADD_ENV_FILE:
$envFile = $submission->project->getMedia('project_files')->where('file_name', '.env')->first()->getPath();
$this->lunchAddEnvFileJob($submission, $envFile, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$REPLACE_PACKAGE_JSON:
$packageJson = $submission->project->getMedia('project_files')->where('file_name', 'package.json')->first()->getPath();
$this->lunchReplacePackageJsonJob($submission, $packageJson, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$COPY_TESTS_FOLDER:
$this->lunchCopyTestsFolderJob($submission, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$NPM_INSTALL:
$this->lunchNpmInstallJob($submission, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$NPM_RUN_START:
$this->lunchNpmRunStartJob($submission, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$NPM_RUN_TESTS:
$this->lunchNpmRunTestsJob($submission, $this->getTempDir($submission), $step);
break;
case ExecutionStep::$DELETE_TEMP_DIRECTORY:
$this->lunchDeleteTempDirectoryJob($submission, $this->getTempDir($submission), $step);
break;
default:
break;
}
}
return $this->returnSubmissionResponse(
$isNotHistory ? 'Step ' . $step->executionStep->name . ' is ' . $submission->results->{$step->executionStep->name}->status : "History",
$submission->status,
$submission->results,
$step,
$completion_percentage
);
}
return $this->returnSubmissionResponse(
($isNotHistory ? 'Submission is processing meanwhile there is no step to execute' : "History"),
$submission->status,
$submission->results,
$step,
$completion_percentage
);
}
}
return response()->json([
'message' => 'Submission not found',
], 404);
}
public function returnSubmissionResponse($message, $status, $results, $next_step = null, $completion_percentage)
{
return response()->json([
'message' => $message,
'status' => $status,
'results' => $results,
'next_step' => $next_step,
'completion_percentage' => $completion_percentage,
], 200);
}
public function refresh(Request $request)
{
if ($request->submission_id == null) return response()->json([
'message' => 'Submission ID is required',
], 404);
$user = Auth::user();
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
if ($submission and $submission->status === Submission::$FAILED) {
// Create submission history
$submission->createHistory("Submission has failed, so it has been refreshed");
// if npm is installed
if ($submission->results->{ExecutionStep::$NPM_INSTALL}->status == Submission::$COMPLETED and !$this->is_dir_empty($this->getTempDir($submission))) {
$submission->restartAfterNpmInstall();
if ($submission->port != null) Process::fromShellCommandline('npx kill-port ' . $submission->port, null, null, null, 120)->run();
} else {
$commands = [];
if ($submission->port != null) {
$commands = [
['npx', 'kill-port', $submission->port],
['rm', '-rf', $this->getTempDir($submission)],
];
} else {
$commands = [
['rm', '-rf', $this->getTempDir($submission)],
];
}
// Delete temp directory
foreach ($commands as $command) {
$process = new Process($command, null, null, null, 120);
$process->run();
if ($process->isSuccessful()) {
Log::info('Command ' . implode(" ", $command) . ' is successful');
} else {
Log::error('Command ' . implode(" ", $command) . ' has failed ' . $process->getErrorOutput());
}
}
$submission->initializeResults();
$submission->updateStatus(Submission::$PENDING);
}
// Update submission status
$submission->increaseAttempts();
$submission->updatePort(null);
$submission->restartTime();
// Return response
return response()->json([
'message' => 'Submission has been refreshed',
'status' => $submission->status,
'results' => $submission->results,
'attempts' => $submission->attempts,
'completion_percentage' => 0,
], 200);
}
}
private function getTempDir($submission)
{
return storage_path('app/public/nodejs/tmp/submissions/' . $submission->user_id . '/' . $submission->project->title . '/' . $submission->id);
}
private function is_dir_empty($dir)
{
if (!is_readable($dir)) return true;
$handle = opendir($dir);
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
closedir($handle);
return false;
}
}
closedir($handle);
return true;
}
private function replaceCommandArraysWithValues($step_variables, $values, $step)
{
return array_reduce($step_variables, function ($commands, $variableValue) use ($values) {
return array_map(function ($command) use ($variableValue, $values) {
return $command === $variableValue ? $values[$variableValue] : $command;
}, $commands);
}, $step->executionStep->commands);
}
private function lunchCloneRepositoryJob($submission, $repoUrl, $tempDir, $step)
{
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ["{{repoUrl}}" => $repoUrl, '{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
dispatch(new CloneRepository($submission, $repoUrl, $tempDir, $commands))->onQueue(ExecutionStep::$CLONE_REPOSITORY);
}
private function lunchUnzipZipFilesJob($submission, $zipFileDir, $tempDir, $step)
{
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{zipFileDir}}' => $zipFileDir, '{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
dispatch(new UnzipZipFiles($submission, $zipFileDir, $tempDir, $commands))->onQueue(ExecutionStep::$UNZIP_ZIP_FILES);
}
private function lunchExamineFolderStructureJob($submission, $tempDir, $step)
{
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
dispatch(new ExamineFolderStructure($submission, $tempDir, $commands))->onQueue(ExecutionStep::$EXAMINE_FOLDER_STRUCTURE);
}
private function lunchAddEnvFileJob($submission, $envFile, $tempDir, $step)
{
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{envFile}}' => $envFile, '{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
dispatch(new AddEnvFile($submission, $envFile, $tempDir, $commands))->onQueue(ExecutionStep::$ADD_ENV_FILE);
}
private function lunchReplacePackageJsonJob($submission, $packageJson, $tempDir, $step)
{
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{packageJson}}' => $packageJson, '{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
dispatch(new ReplacePackageJson($submission, $packageJson, $tempDir, $commands))->onQueue(ExecutionStep::$REPLACE_PACKAGE_JSON);
}
private function lunchCopyTestsFolderJob($submission, $tempDir, $step)
{
$testsDir = [
'testsDirApi' => $submission->project->getMedia('project_tests_api'),
'testsDirWeb' => $submission->project->getMedia('project_tests_web'),
'testsDirImage' => $submission->project->getMedia('project_tests_images'),
];
// command 1: [1]cp [2]-r [3]{{testsDir}} [4]{{tempDir}}
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{testsDir}}' => $testsDir, '{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
$commandsArray = [];
foreach ($testsDir['testsDirApi'] as $key => $value) {
$commands[2] = $value->getPath();
$commands[3] = $tempDir . '/tests/api';
array_push($commandsArray, $commands);
}
foreach ($testsDir['testsDirWeb'] as $key => $value) {
$commands[2] = $value->getPath();
$commands[3] = $tempDir . '/tests/web';
array_push($commandsArray, $commands);
}
foreach ($testsDir['testsDirImage'] as $key => $value) {
$commands[2] = $value->getPath();
$commands[3] = $tempDir . '/tests/web/images';
array_push($commandsArray, $commands);
}
dispatch(new CopyTestsFolder($submission, $testsDir, $tempDir, $commandsArray))->onQueue(ExecutionStep::$COPY_TESTS_FOLDER);
}
private function lunchNpmInstallJob($submission, $tempDir, $step)
{
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{options}}' => " "];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
dispatch(new NpmInstall($submission, $tempDir, $commands))->onQueue(ExecutionStep::$NPM_INSTALL);
}
private function lunchNpmRunStartJob($submission, $tempDir, $step)
{
$commands = $step->executionStep->commands;
dispatch_sync(new NpmRunStart($submission, $tempDir, $commands));
}
private function lunchNpmRunTestsJob($submission, $tempDir, $step)
{
$commands = [];
$tests = $submission->project->projectExecutionSteps->where('execution_step_id', $step->executionStep->id)->first()->variables;
foreach ($tests as $testCommandValue) {
$command = implode(" ", $step->executionStep->commands);
$key = explode("=", $testCommandValue)[0];
$value = explode("=", $testCommandValue)[1];
$testName = str_replace($key, $value, $command);
array_push($commands, explode(" ", $testName));
}
dispatch_sync(new NpmRunTests($submission, $tempDir, $commands));
}
private function lunchDeleteTempDirectoryJob($submission, $tempDir, $step, $commands = null)
{
if ($commands == null) {
$commands = $step->executionStep->commands;
$step_variables = $step->variables;
$values = ['{{tempDir}}' => $tempDir];
$commands = $this->replaceCommandArraysWithValues($step_variables, $values, $step);
$commands = [$commands];
}
dispatch_sync(new DeleteTempDirectory($submission, $tempDir, $commands));
}
public function destroy(Request $request)
{
if ($request->ajax()) {
if ($request->submission_id == null) return response()->json([
'message' => 'Submission ID is required',
], 404);
$user = Auth::user();
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
if ($submission) {
$submission->delete();
// delete temp directory and media
if ($submission->type == Submission::$FILE) {
$submission->getMedia('submissions')->each(function ($media) {
$media->delete();
});
}
$tempDir = $this->getTempDir($submission);
if (!$this->is_dir_empty($tempDir)) {
Process::fromShellCommandline('rm -rf ' . $tempDir)->run();
}
return response()->json([
'message' => 'Submission has been deleted successfully',
], 200);
}
return response()->json([
'message' => 'Submission not found',
], 404);
}
}
public function restart(Request $request)
{
if ($request->ajax()) {
if ($request->submission_id == null) return response()->json([
'message' => 'Submission ID is required',
], 404);
$user = Auth::user();
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
if ($submission) {
$submission->createHistory("Submission has been restarted");
if ($submission->port != null) {
$commands = [
['npx', 'kill-port', $submission->port],
['rm', '-rf', $this->getTempDir($submission)],
];
} else {
$commands = [
['rm', '-rf', $this->getTempDir($submission)],
];
}
// Delete temp directory
foreach ($commands as $command) {
if (!$this->is_dir_empty($this->getTempDir($submission))) {
$process = new Process($command, null, null, null, 120);
$process->run();
if ($process->isSuccessful()) {
Log::info('Command ' . implode(" ", $command) . ' is successful');
} else {
Log::error('Command ' . implode(" ", $command) . ' has failed ' . $process->getErrorOutput());
}
}
}
$submission->restart();
return response()->json([
'message' => 'Submission has been restarted successfully',
], 200);
}
return response()->json([
'message' => 'Submission not found',
], 404);
}
}
public function changeSourceCode($submission_id)
{
$user = Auth::user();
$submission = Submission::where('id', $submission_id)->where('user_id', $user->id)->first();
if ($submission) {
return view('nodejs.submissions.change_source_code', compact('submission'));
}
return redirect()->route('submissions');
}
public function update(Request $request)
{
try {
$request->validate([
'submission_id' => 'required|exists:nodejsDB.submissions,id',
'folder_path' => 'required_without:github_url',
'github_url' => 'required_without:folder_path',
]);
$user = Auth::user();
$submission = Submission::where('id', $request->submission_id)->where('user_id', $user->id)->first();
$submission->createHistory("Code has been changed");
if (!$submission->isGithubUrl()) {
$submission->getMedia('submissions')->each(function ($media) {
$media->delete();
});
}
// delete temp directory if is not empty
$tempDir = $this->getTempDir($submission);
if (!$this->is_dir_empty($tempDir)) {
Process::fromShellCommandline('rm -rf ' . $tempDir)->run();
}
if ($request->has('folder_path')) {
$submission->type = Submission::$FILE;
$submission->path = $request->folder_path;
$temporary_file = TemporaryFile::where('folder_path', $request->folder_path)->first();
if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
$submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path));
}
$temporary_file->delete();
}
} else {
$submission->type = Submission::$URL;
$submission->path = $request->github_url;
}
$submission->save();
$submission->restart();
return response()->json([
'message' => 'Submission created successfully',
'submission' => $submission,
], 201);
} catch (\Throwable $th) {
return response()->json([
'message' => 'Submission failed',
'error' => $th->getMessage(),
], 500);
}
}
public function downloadHistory(Request $request, $id)
{
if (!$request->type) {
return redirect()->route('submissions');
}
$user = Auth::user();
$submission = $request->type == 'history' ? SubmissionHistory::where('id', $id)->where('user_id', $user->id)->first() : Submission::where('id', $id)->where('user_id', $user->id)->first();
if (!$submission) {
return redirect()->route('submissions');
}
if ($request->type == 'current' && $submission->status != Submission::$COMPLETED && $submission->status != Submission::$FAILED) {
return redirect()->route('submissions');
}
$results = json_encode($submission->results, JSON_PRETTY_PRINT);
$results_array = json_decode($results, true);
uasort($results_array, function ($a, $b) {
return $a['order'] - $b['order'];
});
$jsonResults = json_encode($results_array, JSON_PRETTY_PRINT);
$filename = 'submission_' . $submission->project->title . '_' . $user->id . '_' . $id . '_' . now()->format('Y-m-d_H-i-s') . '.json';
$headers = [
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename=' . $filename,
];
return response()->streamDownload(function () use ($submission, $user, $jsonResults) {
echo "Submission for project: " . $submission->project->title . " | User: " . $user->name . "\n";
echo "====================================================================================================\n";
echo $jsonResults;
echo "\n====================================================================================================";
}, $filename, $headers);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class SubmissionHistoryController extends Controller
{
//
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\NodeJS\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class WelcomeController extends Controller
{
public function index()
{
return view('nodejs.welcome');
}
}

View File

@ -0,0 +1,349 @@
<?php
namespace App\Http\Controllers\PHP;
use App\Http\Controllers\Controller;
use App\Models\NodeJS\Project;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use App\Models\PHP\Topic;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process;
use Illuminate\Support\Facades\Session;
class PHPController extends Controller
{
// Menampilkan halaman indeks materi PHP untuk mahasiswa.
public function index()
{
$actual = "";
$topics = Topic::all();
$topicsCount = count($topics);
return view('php.student.material.index', [
'result_up' => $actual,
'topics' => $topics,
'topicsCount' => $topicsCount,
]);
}
// Menampilkan detail materi PHP tertentu sesuai dengan phpid
public function php_material_detail()
{
$phpid = (int)$_GET['phpid'] ?? '';
$start = (int)$_GET['start'] ?? '';
$output = $_GET['output'] ?? '';
$results = DB::select("select * from php_topics_detail where id_topics = $phpid and id ='$start' ");
$html_start = '';
foreach ($results as $r) {
if ($phpid == $r->id_topics) {
$html_start = empty($r->file_name) ? $r->description : $r->file_name;
$pdf_reader = !empty($r->file_name) ? 1 : 0;
break;
}
}
$listTask = DB::select("select aa.*, us.name from php_user_submits aa join users us on aa.userid = us.id where php_id = $phpid and php_id_topic = $start ");
$idUser = Auth::user()->id;
$roleTeacher = DB::select("select role from users where id = $idUser");
$topics = Topic::all();
$detail = Topic::findOrFail($phpid);
$topicsCount = count($topics);
$detailCount = ($topicsCount / $topicsCount) * 10;
return view('php.student.material.topics_detail', [
'row' => $detail,
'topics' => $topics,
'phpid' => $phpid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader,
'topicsCount' => $topicsCount,
'detailCount' => $detailCount,
'output' => $output,
'flag' => isset($results[0]) ? $results[0]->flag : 0,
'listTask' => $listTask,
'role' => isset($roleTeacher[0]) ? $roleTeacher[0]->role : '',
]);
}
// Mengunggah file yang dikirim oleh pengguna
public function upload(Request $request)
{
if ($request->hasFile('upload')) {
$originName = $request->file('upload')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('upload')->getClientOriginalExtension();
$fileName = $fileName . '_' . time() . '.' . $extension;
// Mengambil nama asli file, menambahkan timestamp, dan memindahkan file
$request->file('upload')->move(public_path('media'), $fileName);
$url = asset('media/' . $fileName);
return response()->json(['fileName' => $fileName, 'uploaded' => 1, 'url' => $url]);
}
}
// Mengirim tugas yang dilakukan oleh siswa
public function send_task()
{
$phpid = (int)$_GET['phpid'] ?? '';
$task = (int)$_GET['task'] ?? '';
$output = $_GET['output'] ?? '';
$task_db = DB::table('php_task')
->where('id_topics', $phpid)
->where('id', $task)
->first();
if ($task_db && $task_db->id == $task) {
$html_start = $this->html_task();
} else {
$html_start = "";
}
$pdf_reader = 0;
$topics = Topic::all();
$detail = Topic::findOrFail($phpid);
$topicsCount = count($topics);
$persen = ($topicsCount / $topicsCount) * 10;
session(['params' => $persen]);
return view('php.student.material.topics_detail', [
'row' => $detail,
'topics' => $topics,
'phpid' => $phpid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader,
'detailCount' => $persen,
'output' => $output,
]);
}
public function html_task()
{
return view('php.student.task.form_submission_task', []);
}
public function php_admin()
{
return view('php.admin.material.upload_materi', []);
}
// Mengirimkan tugas dari siswa dan menjalankan tes unit
public function task_submission(Request $request)
{
$phpid = (int)$request->get('phpid');
$start = (int)$request->get('start');
$this->validate($request, [
'file' => 'required',
]);
$file = $request->file('file');
$file_name = Auth::user()->name . '_' . $file->getClientOriginalName();
Storage::disk('public')->makeDirectory('private/' . Auth::user()->name);
Storage::disk('public')->put('/private/' . Auth::user()->name . '/' . $file_name, File::get($file));
$userName = Auth::user()->name;
Session::put('user_name', $userName);
Session::put('ori_file_name', $file_name);
$path = storage_path("app/private/{$userName}/{$file_name}");
Session::put('path', $path);
$val = session('key');
// DB::select("TRUNCATE TABLE php_user_submits");
// DB::insert("insert into php_user_submits(userid) values ('$val')");
$phpunitExecutable = base_path('vendor/bin/phpunit');
$unitTest = '';
switch ($start) {
case 43:
$unitTest = base_path('tests/CreateDatabase.php');
break;
case 44:
$unitTest = base_path('tests/CheckConnection.php');
break;
case 45:
$unitTest = base_path('tests/CreateTableGuru.php');
break;
case 46:
$unitTest = base_path('tests/CreateTable.php');
break;
case 47:
$unitTest = base_path('tests/CheckInsertGuru.php');
break;
case 48:
$unitTest = base_path('tests/CheckInsert.php');
break;
// case 48:
// $unitTest = base_path('tests/CheckInsertHtml.php');
// break;
// case 49:
// $unitTest = base_path('tests/CheckInsertHtmlGuru.php');
// break;
case 51:
$unitTest = base_path('tests/CheckSelectHtmlGuru.php');
break;
case 52:
$unitTest = base_path('tests/CheckSelectHtml.php');
break;
case 53:
$unitTest = base_path('tests/CheckUpdateHtmlGuru.php');
break;
case 54:
$unitTest = base_path('tests/CheckUpdateHtml.php');
break;
case 55:
$unitTest = base_path('tests/CheckDeleteHtmlGuru.php');
break;
case 56:
$unitTest = base_path('tests/CheckDeleteHtml.php');
break;
default:
// Penanganan default jika $start tidak sesuai dengan yang diharapkan
break;
}
Storage::disk('local')->put('/private/testingunit/testingunit.php', File::get($file));
// Run PHPUnit tests using exec
$output = [];
$returnVar = 0;
exec("$phpunitExecutable $unitTest", $output, $returnVar);
Storage::deleteDirectory('/private/testingunit');
// Output the results
$outputString = "<br>PHPUnit Output: <br>";
$outputString .= implode("<br>", $output) . "<br>";
$outputString .= "Return Code: $returnVar<br>";
$idUser = Auth::user()->id;
$pathuser = 'storage/private/' . $userName . '/' . $file_name . '';
$flag = $returnVar == 0 ? 'true' : 'false';
DB::insert("INSERT INTO php_user_submits(userid, path, flag, php_id, php_id_topic) values ('$idUser', '$pathuser', '$flag', $phpid, $start)");
return redirect('/php/detail-topics?phpid=' . $phpid . '&start=' . $start . '&output=' . $outputString . '');
}
// Menjalankan semua tes unit yang ada.
function unittesting2(){
$val = session('key');
DB::select("TRUNCATE TABLE php_user_submits");
DB::insert("insert into php_user_submits(userid) values ('$val')");
$path_test = base_path("phpunit.xml");
$path = base_path("vendor\bin\phpunit -c $path_test");
$output = shell_exec($path);
// echo dd($output);
// echo json_encode($output);
$string = htmlentities($output);
$string = str_replace("\n", ' ', $string);
$pattern = '/PHPUnit\s+(\d+\.\d+\.\d+).*Runtime:\s+PHP\s+(\d+\.\d+\.\d+).*Time:\s+(\d+:\d+\.\d+),\s+Memory:\s+(\d+\.\d+)\s+MB\s+OK\s+\((\d+)\stests,\s+(\d+)\sassertions\)/';
if (preg_match($pattern, $string, $matches)) {
$phpUnitVersion = $matches[1];
$phpVersion = $matches[2];
$executionTime = $matches[3];
$memoryUsage = $matches[4];
$numTests = $matches[5];
$numAssertions = $matches[6];
// Output the extracted information
echo "PHPUnit version: $phpUnitVersion <br />";
echo "PHP version: $phpVersion <br />";
echo "Execution time: $executionTime <br />";
echo "Memory usage: $memoryUsage MB <br />";
echo "Number of tests: $numTests <br />";
echo "Number of assertions: $numAssertions <br />";
$ok_position = strpos($string, 'OK');
if ($ok_position !== false) {
$ok_part = substr($string, $ok_position);
echo "Tests Run : ". $ok_part;
}
}else{
$string = json_encode($output);
$text = str_replace("\n", ' ', $output);
// Define patterns to extract relevant information
$pattern_phpunit_version = '/PHPUnit\s+(\d+\.\d+\.\d+)/';
$pattern_php_runtime = '/Runtime:\s+PHP\s+([\d.]+)/';
$pattern_configuration = '/Configuration:\s+(.+)/';
$pattern_failure_count = '/There was (\d+) failure/';
$pattern_failure_test_case = '/Failed asserting that \'(.*?)\' contains \'(.*?)\'./';
$pattern_failure_location = '/(C:\\\\.*?\\.php):(\d+)/';
// Perform matching
preg_match($pattern_phpunit_version, $text, $matches_phpunit_version);
preg_match($pattern_php_runtime, $text, $matches_php_runtime);
preg_match($pattern_configuration, $text, $matches_configuration);
preg_match($pattern_failure_count, $text, $matches_failure_count);
preg_match($pattern_failure_test_case, $text, $matches_failure_test_case);
preg_match($pattern_failure_location, $text, $matches_failure_location);
// Extracted information
$phpunit_version = isset($matches_phpunit_version[1]) ? $matches_phpunit_version[1] : "Not found";
$php_runtime = isset($matches_php_runtime[1]) ? $matches_php_runtime[1] : "Not found";
$configuration_path = isset($matches_configuration[1]) ? $matches_configuration[1] : "Not found";
$num_failures = isset($matches_failure_count[1]) ? $matches_failure_count[1] : "Not found";
$failed_assertion = isset($matches_failure_test_case[1]) ? htmlspecialchars($matches_failure_test_case[1]) : "Not found";
$expected_content = isset($matches_failure_test_case[2]) ? htmlspecialchars($matches_failure_test_case[2]) : "Not found";
$failure_location = isset($matches_failure_location[1]) ? $matches_failure_location[1] : "Not found";
$failure_line = isset($matches_failure_location[2]) ? $matches_failure_location[2] : "Not found";
// Output extracted information
echo "PHPUnit version: $phpunit_version <br >";
echo "PHP Runtime: $php_runtime <br >";
echo "Configuration path: $configuration_path <br >";
echo "Number of failures: $num_failures <br >";
echo "Failed assertion: $failed_assertion <br >";
echo "Expected content: $expected_content <br >";
echo "Failure location: $failure_location <br >";
echo "Failure line: $failure_line <br >";
}
}
// Menjalankan tes unit tertentu untuk uji coba
function unittesting(){
$namaFile = 'admin_CreateDB.php';
$phpunitExecutable = base_path('vendor/bin/phpunit');
$unitTest = base_path('tests/FileReadTest.php');
// Run PHPUnit tests using exec
$output = [];
$returnVar = 0;
exec("$phpunitExecutable $unitTest", $output, $returnVar);
// Output the results
// echo "PHPUnit Output: <br>";
// echo implode("<br>", $output) . "<br>";
// echo "Return Code: $returnVar<br>";
return response()->json($output);
}
// Menyimpan parameter sesi
function session_progress(){
session(['params' => $_POST['params']]);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\PHP;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use App\Models\PHP\Topic;
use App\Models\PHP\Topic_detail;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Auth;
class PHPDosenController extends Controller{
function topics(){
$topics = Topic::all();
return view('php.teacher.topics',[
'topics' => $topics,
]);
}
function add_topics( Request $request, $id ){
$detail = Topic::findorfail($id);
$results = DB::select('SELECT * FROM php_topics_detail WHERE id_topics = ?', [$id]);
return view('php.teacher.topics_detail',[
'results' => $results,
'detail' => $detail->title,
'id' => $id,
]);
}
function simpan( Request $request){
$validatedData = $request->validate([
'title' => 'required|string|max:255',
'caption' => 'required|string|max:255',
'editor' => 'required|string',
'id' => 'required|int|max:11',
'materials' => 'required|file|max:10240', // Example: max 10MB
]);
// Periksa apakah file yang diunggah bukan PDF
if ($request->file('materials')->getClientOriginalExtension() !== 'pdf') {
return redirect()->back()->with('error', 'Materi harus berupa file PDF.');
}
$originName = $request->file('materials')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('materials')->getClientOriginalExtension();
$fileName = $fileName . '_' . time() . '.' . $extension;
$new_name = str_replace(" ",'_',$fileName);
$path = $request->file('materials')->move(public_path('php/document/A1_BASIC_PHP'), $new_name);
Topic_detail::create([
'title' => $validatedData['title'],
'id_topics' => $validatedData['id'],
'controller' => $validatedData['caption'],
'description' => $validatedData['editor'],
'folder_path' => $path, // Save the path to the uploaded file
'file_name' => $new_name,
]);
$id = $validatedData['id'];
return Redirect::to("/php/teacher/topics/add/$id")->with('message', 'Data Berhasil Diinputkan');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\PHP\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DashboardUnitControllers extends Controller
{
//
public function index(){
$page_title = "Persetujuan Hak Cipta";
$page_link = "02";
$content = "";
$form_upload = 'N';
return view('phpunit.dashboard', ['content' => $content,
'page_title' => $page_title,
'page_link' => $page_link,
'form_upload' => $form_upload
])->render();
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers\PHP\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class StudikasusController extends Controller
{
//
function index(){
echo 1;
}
////
function upload_jawaban(Request $request){
$this->validate($request, [
'file' => 'required',
]);
// menyimpan data file yang diupload ke variabel $file
$file = $request->file('file');
// nama file
echo 'File Name: '.$file->getClientOriginalName();
echo '<br>';
// ekstensi file
echo 'File Extension: '.$file->getClientOriginalExtension();
echo '<br>';
// real path
echo 'File Real Path: '.$file->getRealPath();
echo '<br>';
// ukuran file
echo 'File Size: '.$file->getSize();
echo '<br>';
// tipe mime
echo 'File Mime Type: '.$file->getMimeType();
// isi dengan nama folder tempat kemana file diupload
$file_name = Auth::user()->name.'_'.$file->getClientOriginalName();
//$file->move($tujuan_upload,$file->getClientOriginalName());
Storage::disk('local')->makeDirectory('private/'.Auth::user()->name);
Storage::disk('local')->put('/private/'.Auth::user()->name.'/'.$file_name,File::get($file));
$userName = Auth::user()->name;
Session::put('user_name', $userName);
$user_name = Session::get('user_name');
$name = "Udjir_GuideA1.html";
Session::put('sess_path', base_path("storage\app\private\\$user_name\\$name"));
return redirect('/phpunit/studi-kasus/projects/02')->with('status', 'File Berhasil Diupload!');
}
function unittesting(){
$path_test = base_path("phpunit.xml");
$path = base_path("vendor\bin\phpunit -c $path_test");
$output = shell_exec($path);
echo dd($output);
//echo json_encode($output);
}
function result_test(){
$path_test = base_path("phpunit.xml");
$path = base_path("vendor\bin\phpunit -c $path_test");
$output = shell_exec($path);
// dd($output);
$string = json_encode($output);
$pattern = '/OK \((\d+ test), (\d+ assertion)\)/';
if (preg_match($pattern, $string, $matches)) {
$numberOfTest = $matches[0];
$numberOfTests = $matches[1];
$numberOfAssertions = $matches[2];
echo "Status Tests : $numberOfTest\n";
} else {
echo "Pattern not found.";
}
}
function upload_test(){
$actual = base_path('storage\app\private\Udjir\Udjir_GuideA1.php');
//$filename = $actual."/temp/$file";
//include "$filename";
$php_output = shell_exec("C:\wamp64\bin\php\php7.4.33\php.exe $actual 2>&1");
//echo $php_output;
}
//
function projects(string $id){
$page_title = "Studi Kasus $id";
$page_link = "$id";
$content = "
<table>
<tr>
<td>
Buatlah Tampilan Seperti Berikut :
<br />
<br />
</td>
</tr>
</table>
";
$form_upload = 'Y';
return view('phpunit.dashboard', ['content' => $content,
'page_title' => $page_title,
'page_link' => $page_link,
'form_upload'=> $form_upload])->with('form_upload','Y');
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\PHP\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\DB;
use App\Models\PHP\TaskSubmission;
use App\Models\PHP\UserCheck;
class WelcomeController extends Controller
{
function result_test(){
$uname = DB::select("SELECT * FROM users
WHERE name COLLATE utf8mb4_general_ci IN (SELECT userid FROM php_user_submits) ");
if (!empty($uname)) {
$firstUser = reset($uname); // Get the first element of the array
$sess_name = isset($firstUser->name) ? $firstUser->name : ''; // Access the 'name' property of the first user
}
$task = DB::table('php_submits_submission')->where('username', "$sess_name")->first();
$username = $task->username;
$userfile = $task->userfile;
$ket = htmlspecialchars($task->ket);
$actual = storage_path("app/private/{$username}/{$userfile}");
$php_output = shell_exec("PHP $actual 2>&1");
$test = str_replace(array("\r\n","\r","\n"," "),"",htmlspecialchars($php_output));
return view('php.student.task.result_submssion_task',[
'result_up' => $test,
]);
// echo "$value == $sess_name";
}
function get_session(){
if (Auth::check()) {
$value = Auth::user()->name;
return $value;
}
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Session;
class AuthController extends Controller
{
public function proses( Request $request ) {
$request->validate([
'email' => 'required',
'password' => 'required',
]);
$credential = request(['email', 'password']);
if ( Auth::attempt( $credential ) ) {
if ( Auth::user()->role == "student" ) {
return redirect('dashboard-student');
} else if ( Auth::user()->role == "teacher" ) {
return redirect('dashboard_teacher');
} else if( Auth::user()->role == "Admin" ) {
session(['key' => Auth::user()->name]);
session(['email' => Auth::user()->email]);
return redirect('dashboard-student');
}
} else {
echo "okee err";
}
}
public function signup( Request $request ) {
$data = $request->validate([
'name' => 'required',
'email' => 'required',
'password' => 'required|confirmed',
'teacher' => 'required'
]);
$data['password'] = bcrypt($data['password']);
User::create($data);
return redirect('/');
}
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
public function redirect() {
return Socialite::driver(driver:'google')->redirect();
}
public function googleCallback()
{
$user = Socialite::driver('google')->user();
$userDatabase = User::where('google_id', $user->getId())->first();
$token = $user->token;
session(['google_token' => $token]);
if (!$userDatabase) {
$data = [
'google_id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'role' => 'Student',
];
$existingUser = User::where('email', $data['email'])->first();
if ($existingUser) {
$existingUser->update($data);
$userDatabase = $existingUser;
} else {
$userDatabase = User::create($data);
}
auth()->login($userDatabase);
session()->regenerate();
return redirect()->route('dashboard-student');
} else {
auth()->login($userDatabase);
session()->regenerate();
return redirect()->route('dashboard-student');
}
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Task;
use App\Models\Material;
use App\Models\StudentSubmission;
use Illuminate\Support\Facades\Storage;
class PythonMaterialController extends Controller
{
public function index()
{
$materials = Material::all(); // atau query lain sesuai kebutuhan
return view('learning_student', compact('materials'));
}
public function materialDetail(Request $request)
{
$materialId = $request->query('material_id');
$material = Material::findOrFail($materialId);
return view('material_detail', compact('material'));
}
public function showTask($taskId)
{
$task = Task::findOrFail($taskId);
$submission = StudentSubmission::where('user_id', auth()->id())
->where('task_id', $taskId)
->latest()
->first();
$submittedCode = '';
if ($submission && $submission->file_path) {
$submittedCode = Storage::get($submission->file_path);
}
return view('task_detail', compact('task', 'submission', 'submittedCode'));
}
public function downloadPdf(Task $task)
{
$pdfPath = 'pdfs/' . $task->pdf_path;
if (Storage::disk('public')->exists($pdfPath)) {
return Storage::disk('public')->download($pdfPath);
}
abort(404, 'PDF tidak ditemukan');
}
}

View File

@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers;
use App\Models\StudentSubmission;
use App\Models\Task;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class StudentSubmissionController extends Controller
{
public function store(Request $request)
{
// dd($request->all());
// $data =
$request->validate([
'task_id' => 'required|exists:tasks,id',
'file' => 'required|file|mimes:txt,py' // Validasi untuk file Python
]);
// dd($data);
$user = auth()->user();
$task = Task::findOrFail($request->task_id);
$uploadedFile = $request->file('file');
$expectedFileName = $this->getExpectedFileName($task);
// Simpan file dengan nama yang ditentukan
$filePath = $uploadedFile ->storeAs('./public/submissions', $expectedFileName, 'local');
//Menambahkan validasi nama file sebelum menyimpan file:
if (!$this->isFileNameValid($uploadedFile->getClientOriginalName(), $expectedFileName)) {
return redirect()->back()->with('error', 'Nama file tidak sesuai. Harap gunakan format: ' . $expectedFileName);
}
// Log untuk memastikan file tersimpan dengan benar
\Log::info('File saved at: ' . $filePath);
\Log::info('File content after save: ' . Storage::disk('public')->get($filePath));
// Tambahkan kode debugging di sini
\Log::info('File uploaded: ' . $filePath);
if (Storage::disk('local')->exists($filePath)) {
\Log::info('File exists at: ' . Storage::disk('local')->path($filePath));
} else {
\Log::error('File not found at: ' . Storage::disk('local')->path($filePath));
}
$task = Task::findOrFail($request->task_id);
$existingSubmission = StudentSubmission::where('user_id', $user->id)
->where('task_id', $task->id)
->first();
if ($existingSubmission) {
// Jika sudah ada, update submission yang ada
$submissionCount = $existingSubmission->submission_count + 1;
$existingSubmission->update([
'submission_count' => $submissionCount,
'file_path' => $filePath,
]);
} else {
// Jika belum ada, buat submission baru
$submissionCount = 1;
StudentSubmission::create([
'user_id' => $user->id,
'task_id' => $task->id,
'submission_count' => $submissionCount,
'file_path' => $filePath,
]);
}
// Di sini Anda bisa menambahkan logika untuk menjalankan tes
// dan menyimpan hasilnya ke $submission->test_result
// return redirect()->back()->with('success', 'Submission berhasil.');
}
// }
private function getExpectedFileName(Task $task)
{ // mengekstrak nama file pdf dan merubah namanya menjadi answer_{path}.py
// untuk pathnya adalah path dari bab dan percobaan nya
// misalnya jika pathnya adalah bab1_Percobaan1 maka nama file nya akan answer_bab1_Percobaan1.py
// dari hasil ini kemudian akan jadi pembanding untuk nama file yang diupload
// Ambil nama file PDF dari kolom pdf_path pada tabel tasks
$pdfPath = $task->pdf_path;
// Ekstrak nama file dari path
$pdfFileName = basename($pdfPath);
// Ekstrak bagian yang diperlukan (misalnya 'bab1_Percobaan1')
preg_match('/bab\d+_Percobaan\d+/i', $pdfFileName, $matches);
$extractedPart = $matches[0] ?? '';
// Buat nama file yang diharapkan
return 'answer_' . strtolower($extractedPart) . '.py';
}
private function isFileNameValid($uploadedFileName, $expectedFileName)
{
return strtolower($uploadedFileName) === strtolower($expectedFileName);
}
public function storeTestResult(Request $request)
{
// Validasi request jika diperlukan
$request->validate([
'output' => 'required|string',
'task_id' => 'required|exists:tasks,id',
]);
// Mendapatkan pengguna yang sedang login
$user_id = auth()->user()->id;
// Mendapatkan data dari request
$output = $request->input('output');
$task_id = $request->input('task_id');
// Mencari submission yang sesuai
$submission = StudentSubmission::where('user_id', $user_id)
->where('task_id', $task_id)
->first();
if ($submission) {
// Update kolom test_result dengan data output
$submission->update([
'test_result' => $output,
]);
// Mengembalikan respons sukses
return response()->json(['message' => 'Data berhasil disimpan.', 'data' => $submission]);
} else {
// Jika submission tidak ditemukan, kembalikan respons error
return response()->json(['message' => 'Submission tidak ditemukan.'], 404);
}
}
// public function show($id)
// {
// $submission = StudentSubmission::findOrFail($id);
// $task = Task::findOrFail($id);
// return view('task_detail', compact('submission','task'));
// }
private function runTest(StudentSubmission $submission)
{
$testFilePath = storage_path('app/public/test_file/test_' . $submission->task_id . '.py');
$submissionFilePath = storage_path('app/' . $submission->file_path);
$process = new Process(['python', $testFilePath, $submissionFilePath]);
$process->run();
return $process->getOutput();
}
}

View File

@ -0,0 +1,486 @@
<?php
namespace App\Http\Controllers\React;
use App\Http\Controllers\Controller;
use App\Models\NodeJS\Project;
use App\Models\React\ReactTopic;
use App\Models\React\ReactTopic_detail;
use App\Models\React\ReactUserEnroll;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process;
use Illuminate\Support\Facades\Session;
class ReactController extends Controller{
public function submit_score_baru(Request $request)
{
$userId = Auth::id(); // Get the authenticated user's ID
$score = $request->input('score');
$topicsId = $request->input('topics_id');
// Insert the new score into user_submissions using the Query Builder
DB::table('react_user_submits')->insert([
'user_id' => $userId,
'score' => $score,
'topics_id' => $topicsId,
'flag' => $score > 50 ? true : false,
'created_at' => now(),
'updated_at' => now()
]);
// Retrieve or create an entry in user_rank for specific user and topic
$currentRank = DB::table('react_student_rank')
->where('id_user', $userId)
->where('topics_id', $topicsId)
->first();
if (!$currentRank) {
// If no entry exists, create one
DB::table('react_student_rank')->insert([
'id_user' => $userId,
'best_score' => $score,
'topics_id' => $topicsId,
'created_at' => now(),
'updated_at' => now()
]);
} else if ($score > $currentRank->best_score) {
// Update the best_score if the new score is higher
DB::table('react_student_rank')
->where('id_user', $userId)
->where('topics_id', $topicsId)
->update([
'best_score' => $score,
'updated_at' => now()
]);
}
if ($score > 50 ) {
$exists = DB::table('react_student_enroll')
->where('id_users', Auth::user()->id)
->where('php_topics_detail_id', $topicsId)
->exists();
if (!$exists) {
// Record does not exist, so insert a new one
$flags = $results[0]->flag ?? 0;
if ( $flags == 0) {
DB::table('react_student_enroll')->insert([
'id_users' => Auth::user()->id,
'php_topics_detail_id' => $topicsId,
'created_at' => now()
]);
}
}
}
return response()->json(['message' => 'Score submitted successfully']);
}
public function index(){
$actual = "";
$topics = ReactTopic::all();
$topics_detail = ReactTopic_detail::all();
$topicsCount = count($topics);
$topicsDetailCount = count($topics_detail);
$idUser = Auth::user()->id;
$roleTeacher = DB::select("select role from users where id = $idUser");
// Retrieve all completed topics based on role
if ($roleTeacher[0]->role == "student") {
$completedTopics = DB::table('react_student_enroll')
->join('users', 'react_student_enroll.id_users', '=', 'users.id')
->join('react_topics_detail', 'react_student_enroll.php_topics_detail_id', '=', 'react_topics_detail.id')
->select('react_student_enroll.*', 'users.name as user_name', 'react_topics_detail.*')
->where('react_student_enroll.id_users', $idUser)
->get();
} else if ($roleTeacher[0]->role == "teacher") {
$completedTopics = DB::table('react_student_enroll')
->join('users', 'react_student_enroll.id_users', '=', 'users.id')
->join('react_topics_detail', 'react_student_enroll.php_topics_detail_id', '=', 'react_topics_detail.id')
->select('react_student_enroll.*', 'users.name as user_name', 'react_topics_detail.*')
->get();
} else {
; // Return an empty collection if role is neither student nor teacher
}
// Calculate progress for each user
$progress = [];
foreach ($completedTopics as $enrollment) {
$userId = $enrollment->id_users;
if (!isset($progress[$userId])) {
$userCompletedCount = DB::table('react_student_enroll')
->where('id_users', $userId)
->count();
$progress[$userId] = [
'name' => $enrollment->user_name,
'percent' => round(($userCompletedCount / $topicsDetailCount) * 100, 2)
];
}
}
return view('react.student.material.index',[
'result_up' => $actual,
'topics' => $topics_detail,
'topicsCount' => $topicsDetailCount,
'completedTopics' => $completedTopics,
'role' => $roleTeacher[0] ? $roleTeacher[0]->role : '',
'progress' => $progress
]);
}
function php_material_detail(){
$phpid = isset($_GET['phpid']) ? (int)$_GET['phpid'] : '';
$start = isset($_GET['start']) ? (int)$_GET['start'] : '';
$output = isset($_GET['output']) ? $_GET['output'] : '';
$results = DB::select("select * from react_topics_detail where id_topics =$start and id ='$phpid' ");
foreach($results as $r){
if( $start == $r->id_topics){
if(empty($r->file_name)){
$contain = $r->description;
$pdf_reader = 0;
}else{
$contain = $r->file_name;
$pdf_reader = 1;
}
$html_start = $this->html_persyaratan($contain,$pdf_reader);
}else{
$html_start = "";
}
}
// dd($html_start);
// $listTask = DB::select("select aa.*, us.name from react_user_submits aa join users us on aa.userid = us.id where php_id = $phpid and php_id_topic = $start ");
$idUser = Auth::user()->id;
$roleTeacher = DB::select("select role from users where id = $idUser");
$topics = ReactTopic::all();
$detail = ReactTopic::findorfail($start);
$topicsCount = count($topics);
$detailCount = ($topicsCount/$topicsCount)*10;
$topics_detail = ReactTopic_detail::all();
// Check if the record already exists
$exists = DB::table('react_student_enroll')
->where('id_users', Auth::user()->id)
->where('php_topics_detail_id', $phpid)
->exists();
if (!$exists) {
// Record does not exist, so insert a new one
$flags = $results[0]->flag ?? 0;
if ( $flags == 0) {
DB::table('react_student_enroll')->insert([
'id_users' => Auth::user()->id,
'php_topics_detail_id' => $phpid,
'created_at' => now()
]);
}
}
$completedTopics = ReactUserEnroll::where('id_users', Auth::user()->id)->distinct('php_topics_detail_id')->count();
$progress = ( $completedTopics/ count($topics_detail) ) * 100;
return view('react.student.material.topics_detail',[
'row' => $detail,
'topics' => $topics,
'phpid' => $phpid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader ,
'topicsCount'=> $topicsCount ,
'detailCount'=> $detailCount ,
'output' => $output,
'flag' => $results[0] ? $results[0]->flag : 0,
'listTask' => [],
'role' => $roleTeacher[0] ? $roleTeacher[0]->role : '',
'progress' => round($progress, 0)
]);
}
function html_start(){
$html ="<div style='text-align:center;font-size:18px'><em>Modul kelas Belajar Pengembangan Aplikasi Android Intermediate dalam bentuk cetak (buku) maupun elektronik sudah didaftarkan ke Dirjen HKI, Kemenkumham RI. Segala bentuk penggandaan dan atau komersialisasi, sebagian atau seluruh bagian, baik cetak maupun elektronik terhadap modul kelas <em>Belajar Pengembangan Aplikasi Android Intermediate</em> tanpa izin formal tertulis kepada pemilik hak cipta akan diproses melalui jalur hukum.</em></div>";
return $html;
}
function html_persyaratan($desc,$pdf_reader){
$html = $desc;
return $html;
}
public function upload(Request $request)
{
if ($request->hasFile('upload')) {
$originName = $request->file('upload')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('upload')->getClientOriginalExtension();
$fileName = $fileName . '_' . time() . '.' . $extension;
$request->file('upload')->move(public_path('media'), $fileName);
$url = asset('media/' . $fileName);
return response()->json(['fileName' => $fileName, 'uploaded' => 1, 'url' => $url]);
}
}
function send_task(){
$phpid = isset($_GET['phpid']) ? $_GET['phpid'] : '';
$task = isset($_GET['task']) ? $_GET['task'] : '';
$output = isset($_GET['output']) ? $_GET['output'] : '';
$task_db = DB::table('php_task')
->where('id_topics', $phpid)
->where('id', $task)
->first();
if($task_db->id == $task){
$html_start = $this->html_task();
}else{
$html_start = "";
}
$pdf_reader = 0;
$topics = Topic::all();
$detail = Topic::findorfail($phpid);
$topicsCount = count($topics);
$persen = ($topicsCount/$topicsCount)*10;
session(['params' => $persen]);
return view('php.student.material.topics_detail',[
'row' => $detail,
'topics' => $topics,
'phpid' => $phpid,
'html_start' => $html_start,
'pdf_reader' => $pdf_reader ,
'detailCount'=> $persen,
'output' => $output,
]);
}
function html_task(){
return view('php.student.task.form_submission_task',[]);
}
function php_admin(){
return view('php.admin.material.upload_materi',[]);
}
function task_submission(Request $request){
$phpid = (int)$request->get('phpid');
$start = (int)$request->get('start');
$this->validate($request, [
'file' => 'required',
]);
// menyimpan data file yang diupload ke variabel $file
$file = $request->file('file');
$file_name = Auth::user()->name.'_'.$file->getClientOriginalName();
Storage::disk('public')->makeDirectory('private/'.Auth::user()->name);
Storage::disk('public')->put('/private/'.Auth::user()->name.'/'.$file_name,File::get($file));
$userName = Auth::user()->name;
Session::put('user_name', $userName);
$user_name = Session::get('user_name');
$name = Session::put('ori_file_name', $file_name);
$path = storage_path("app/private/{$userName}/{$file_name}");
Session::put('path', $path);
$val = session('key');
// DB::select("TRUNCATE TABLE php_user_submits");
// DB::insert("insert into php_user_submits(userid) values ('$val')");
$phpunitExecutable = base_path('vendor/bin/phpunit');
Storage::disk('local')->put('/private/testingunit/testingunit.php',File::get($file));
if($start == 43){
$unitTest = base_path('tests/CreateDatabase.php');
}else if($start == 42){
$unitTest = base_path('tests/CheckConnection.php');
}else if($start == 44){
$unitTest = base_path('tests/CreateTable.php');
}else if($start == 45){
$unitTest = base_path('tests/CreateTableGuru.php');
}else if($start == 46){
$unitTest = base_path('tests/CheckInsert.php');
}else if($start == 47){
$unitTest = base_path('tests/CheckInsertGuru.php');
}else if($start == 48){
$unitTest = base_path('tests/CheckInsertHtml.php');
}else if($start == 49){
$unitTest = base_path('tests/CheckInsertHtmlGuru.php');
}else if($start == 50){
$unitTest = base_path('tests/CheckSelectHtml.php');
}else if($start == 51){
$unitTest = base_path('tests/CheckSelectHtmlGuru.php');
}else if($start == 52){
$unitTest = base_path('tests/CheckUpdateHtml.php');
}else if($start == 53){
$unitTest = base_path('tests/CheckUpdateHtmlGuru.php');
}else if($start == 54){
$unitTest = base_path('tests/CheckDeleteHtml.php');
}else if($start == 55){
$unitTest = base_path('tests/CheckDeleteHtmlGuru.php');
}
// Run PHPUnit tests using exec
$output = [];
$returnVar = 0;
exec("$phpunitExecutable $unitTest", $output, $returnVar);
Storage::deleteDirectory('/private/testingunit');
// Output the results
$outputString = "<br>PHPUnit Output: <br>";
$outputString .= implode("<br>", $output) . "<br>";
$outputString .= "Return Code: $returnVar<br>";
// dd($output);
$idUser = Auth::user()->id;
$pathuser = 'storage/private/'.$userName.'/'.$file_name.'';
$flag = $returnVar == 0 ? 'true' : 'false';
DB::insert("INSERT INTO php_user_submits(userid, path, flag, php_id, php_id_topic) values ('$idUser', '$pathuser', '$flag', $phpid, $start)");
// php_user_submits
return redirect('/php/detail-topics?phpid='.$phpid.'&start='.$start.'&output='.$outputString.'');
}
function unittesting2(){
$val = session('key');
DB::select("TRUNCATE TABLE php_user_submits");
DB::insert("insert into php_user_submits(userid) values ('$val')");
$path_test = base_path("phpunit.xml");
$path = base_path("vendor\bin\phpunit -c $path_test");
$output = shell_exec($path);
// echo dd($output);
// echo json_encode($output);
$string = htmlentities($output);
$string = str_replace("\n", ' ', $string);
$pattern = '/PHPUnit\s+(\d+\.\d+\.\d+).*Runtime:\s+PHP\s+(\d+\.\d+\.\d+).*Time:\s+(\d+:\d+\.\d+),\s+Memory:\s+(\d+\.\d+)\s+MB\s+OK\s+\((\d+)\stests,\s+(\d+)\sassertions\)/';
if (preg_match($pattern, $string, $matches)) {
$phpUnitVersion = $matches[1];
$phpVersion = $matches[2];
$executionTime = $matches[3];
$memoryUsage = $matches[4];
$numTests = $matches[5];
$numAssertions = $matches[6];
// Output the extracted information
echo "PHPUnit version: $phpUnitVersion <br />";
echo "PHP version: $phpVersion <br />";
echo "Execution time: $executionTime <br />";
echo "Memory usage: $memoryUsage MB <br />";
echo "Number of tests: $numTests <br />";
echo "Number of assertions: $numAssertions <br />";
$ok_position = strpos($string, 'OK');
if ($ok_position !== false) {
$ok_part = substr($string, $ok_position);
echo "Tests Run : ". $ok_part;
}
}else{
$string = json_encode($output);
$text = str_replace("\n", ' ', $output);
// Define patterns to extract relevant information
$pattern_phpunit_version = '/PHPUnit\s+(\d+\.\d+\.\d+)/';
$pattern_php_runtime = '/Runtime:\s+PHP\s+([\d.]+)/';
$pattern_configuration = '/Configuration:\s+(.+)/';
$pattern_failure_count = '/There was (\d+) failure/';
$pattern_failure_test_case = '/Failed asserting that \'(.*?)\' contains \'(.*?)\'./';
$pattern_failure_location = '/(C:\\\\.*?\\.php):(\d+)/';
// Perform matching
preg_match($pattern_phpunit_version, $text, $matches_phpunit_version);
preg_match($pattern_php_runtime, $text, $matches_php_runtime);
preg_match($pattern_configuration, $text, $matches_configuration);
preg_match($pattern_failure_count, $text, $matches_failure_count);
preg_match($pattern_failure_test_case, $text, $matches_failure_test_case);
preg_match($pattern_failure_location, $text, $matches_failure_location);
// Extracted information
$phpunit_version = isset($matches_phpunit_version[1]) ? $matches_phpunit_version[1] : "Not found";
$php_runtime = isset($matches_php_runtime[1]) ? $matches_php_runtime[1] : "Not found";
$configuration_path = isset($matches_configuration[1]) ? $matches_configuration[1] : "Not found";
$num_failures = isset($matches_failure_count[1]) ? $matches_failure_count[1] : "Not found";
$failed_assertion = isset($matches_failure_test_case[1]) ? htmlspecialchars($matches_failure_test_case[1]) : "Not found";
$expected_content = isset($matches_failure_test_case[2]) ? htmlspecialchars($matches_failure_test_case[2]) : "Not found";
$failure_location = isset($matches_failure_location[1]) ? $matches_failure_location[1] : "Not found";
$failure_line = isset($matches_failure_location[2]) ? $matches_failure_location[2] : "Not found";
// Output extracted information
echo "PHPUnit version: $phpunit_version <br >";
echo "PHP Runtime: $php_runtime <br >";
echo "Configuration path: $configuration_path <br >";
echo "Number of failures: $num_failures <br >";
echo "Failed assertion: $failed_assertion <br >";
echo "Expected content: $expected_content <br >";
echo "Failure location: $failure_location <br >";
echo "Failure line: $failure_line <br >";
}
}
function unittesting(){
$namaFile = 'febri syawaldi_CreateDB.php';
$phpunitExecutable = base_path('vendor/bin/phpunit');
$unitTest = base_path('tests/FileReadTest.php');
// Run PHPUnit tests using exec
$output = [];
$returnVar = 0;
exec("$phpunitExecutable $unitTest", $output, $returnVar);
// Output the results
// echo "PHPUnit Output: <br>";
// echo implode("<br>", $output) . "<br>";
// echo "Return Code: $returnVar<br>";
return response()->json($output);
}
function session_progress(){
// Cek Ada Data Tidak, Enrollement User
// Jika Tidak Insert
session(['params' => $_POST['params']]);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\React;
use App\Http\Controllers\Controller;
use App\Models\React\ReactTopic;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use App\Models\PHP\Topic;
use App\Models\React\ReactTopic_detail;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Auth;
class ReactDosenController extends Controller{
function topics(){
$topics = ReactTopic::all();
return view('react.teacher.topics',[
'topics' => $topics,
]);
}
function add_topics( Request $request, $id ){
$detail = ReactTopic::findorfail($id);
$results = DB::select('SELECT * FROM react_topics_detail WHERE id_topics = ?', [$id]);
return view('react.teacher.topics_detail',[
'results' => $results,
'detail' => $detail->title,
'id' => $id,
]);
}
function simpan( Request $request){
$validatedData = $request->validate([
'title' => 'required|string|max:255',
'caption' => 'required|string|max:255',
'editor' => 'required|string',
'id' => 'required|int|max:11',
'materials' => 'required|file|max:10240', // Example: max 10MB
]);
// Periksa apakah file yang diunggah bukan PDF
if ($request->file('materials')->getClientOriginalExtension() !== 'pdf') {
return redirect()->back()->with('error', 'Materi harus berupa file PDF.');
}
$originName = $request->file('materials')->getClientOriginalName();
$fileName = pathinfo($originName, PATHINFO_FILENAME);
$extension = $request->file('materials')->getClientOriginalExtension();
$fileName = $fileName . '_' . time() . '.' . $extension;
$new_name = str_replace(" ",'_',$fileName);
$path = $request->file('materials')->move(public_path('react/document/A1_BASIC_PHP'), $new_name);
ReactTopic_detail::create([
'title' => $validatedData['title'],
'id_topics' => $validatedData['id'],
'controller' => $validatedData['caption'],
'description' => $validatedData['editor'],
'folder_path' => $path, // Save the path to the uploaded file
'file_name' => $new_name,
]);
$id = $validatedData['id'];
return Redirect::to("/react/teacher/topics/add/$id")->with('message', 'Data Berhasil Diinputkan');
}
}

View File

@ -0,0 +1,250 @@
<?php
namespace App\Http\Controllers\React\Student;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\React\ReactSubmitUser;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class ReactLogicalController extends Controller
{
// function upload file
public function uploadFile(Request $request)
{
try {
$request->validate([
'uploadFile' => 'required|file|max:1024',
]);
if ($request->hasFile('uploadFile')) {
$uploadedFile = $request->file('uploadFile');
$userName = Auth::user()->name;
$fileName = $uploadedFile->getClientOriginalName();
$uploadedFile->storeAs('private/' . $userName . '/React', $fileName);
$materiType = $this->getMateriType($fileName);
$comparisonResult = $this->compareFile($materiType, $uploadedFile);
// Menyimpan data ke database dengan status berdasarkan hasil perbandingan
try {
$submitUser = new ReactSubmitUser();
$submitUser->id_user = Auth::id();
$submitUser->nama_user = $userName;
$submitUser->materi = 'React - ' . $fileName;
$submitUser->nilai = $comparisonResult === 'Congratulations, your answer is correct.' ? 100 : 0;
$submitUser->status = $comparisonResult === 'Congratulations, your answer is correct.' ? 'True' : 'False';
$submitUser->save();
} catch (\Exception $dbException) {
// Log error and return response
Log::error('Database save failed: ' . $dbException->getMessage());
return response()->json([
'error' => 'Database save failed.',
'message' => 'There was an error saving your result to the database.',
], 500);
}
return response()->json([
'uploaded' => true,
'fileName' => $fileName,
'message' => 'File uploaded successfully.',
'comparisonResult' => $comparisonResult,
]);
} else {
return response()->json([
'error' => 'File upload failed.',
'message' => 'The upload file field is required.',
], 400);
}
} catch (\Exception $e) {
Log::error('File upload failed: ' . $e->getMessage());
return response()->json([
'error' => 'File upload failed.',
'message' => $e->getMessage(),
], 500);
}
}
// function menentukan tipe materi
private function getMateriType($fileName)
{
if (strpos($fileName, 'Hello.js') !== false) {
return 'hello';
} elseif (strpos($fileName, 'Form.js') !== false) {
return 'form';
} elseif (strpos($fileName, 'Counter.js') !== false) {
return 'counter';
} elseif (strpos($fileName, 'FormStyle.js') !== false) {
return 'formStyle';
} elseif (strpos($fileName, 'Navbar.js') !== false) {
return 'navbar';
} else {
return 'unknown';
}
}
// function mengecek jawaban
private function compareFile($materiType, $uploadedFile)
{
switch ($materiType) {
case 'hello':
return $this->jawabanHello($uploadedFile);
case 'form':
return $this->jawabanForm($uploadedFile);
case 'counter':
return $this->jawabanCounter($uploadedFile);
case 'formStyle':
return $this->jawabanFormStyle($uploadedFile);
case 'navbar':
return $this->jawabanNavbar($uploadedFile);
default:
throw new \Exception('Harus Sesuai Materi');
}
}
// function mencari perbedaan
private function getDifference($uploadedContent, $answerKeyContent)
{
// Membandingkan baris per baris untuk menemukan perbedaan
$uploadedLines = explode("\n", $uploadedContent);
$answerKeyLines = explode("\n", $answerKeyContent);
$diffLines = [];
foreach ($uploadedLines as $lineNumber => $line) {
if (!isset($answerKeyLines[$lineNumber]) || $line !== $answerKeyLines[$lineNumber]) {
$diffLines[] = [
'line_number' => $lineNumber + 1,
'uploaded_line' => $line,
'answer_key_line' => isset($answerKeyLines[$lineNumber]) ? $answerKeyLines[$lineNumber] : '',
];
}
}
$diffMessage = '';
foreach ($diffLines as $diffLine) {
$diffMessage .= "Line{$diffLine['line_number']}: \n";
$diffMessage .= "Your Answer: {$diffLine['uploaded_line']} \n";
$diffMessage .= "Should: {$diffLine['answer_key_line']} \n\n";
}
return $diffMessage;
}
// function hello
private function jawabanHello($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Hello.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
// function form
private function jawabanForm($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Form.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
// function counter
private function jawabanCounter($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Counter.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
private function jawabanFormStyle($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/FormStyle.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
private function jawabanNavbar($uploadedFile)
{
$uploadedContent = file_get_contents($uploadedFile->getRealPath());
$answerKeyPath = storage_path('app/private/_answerKey_/React/Navbar.js');
if (!file_exists($answerKeyPath)) {
throw new \Exception('Answer key file not found.');
}
$answerKeyContent = file_get_contents($answerKeyPath);
if ($uploadedContent === $answerKeyContent) {
return 'Congratulations, your answer is correct.';
} else {
$diff = $this->getDifference($uploadedContent, $answerKeyContent);
$errorMessage = 'Your answer is still wrong. Fix it again, OK? Differences in ' . $diff;
return $errorMessage;
}
}
public function getComparisonResults($userId)
{
$results = ReactSubmitUser::where('id_user', $userId)->get();
return response()->json($results);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*/
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip());
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
*/
public function rules(): array
{
return [
'name' => ['string', 'max:255'],
'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
];
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'teacher' => \App\Http\Middleware\TeacherMiddleware::class,
'student' => \App\Http\Middleware\StudentMiddleware::class
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('generated::VZkQ7NuBzZw9YG9l');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class StudentMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// Cek apakah user sudah login
if (!Auth::check() || Auth::user()->role !== 'student') {
abort(403, "Unauthorized Action.");
}
return $next($request);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class TeacherMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->role !== "teacher") {
abort(403, "Unauthorized action.");
}
return $next($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts(): array
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
'literacy/teacher/generate_questions/ai'
];
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class AddEnvFile implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $envFile;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $envFile, $tempDir, $command)
{
$this->submission = $submission;
$this->envFile = $envFile;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Adding env file {$this->envFile} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Adding env file");
try {
// processing
$process = new Process($this->command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
Log::info("Added env file {$this->envFile} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Added env file");
} else {
Log::error("Failed to add env file {$this->envFile} " . $process->getErrorOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to add env file");
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
} catch (\Throwable $th) {
// failed
Log::error("Failed to add env file {$this->envFile} " . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to add env file");
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$ADD_ENV_FILE;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class CloneRepository implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $repoUrl;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $repoUrl, $tempDir, $command)
{
$this->submission = $submission;
$this->repoUrl = $repoUrl;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Cloning repo {$this->repoUrl} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Cloning repo {$this->repoUrl}");
try {
// processing
$process = new Process($this->command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
// completed
Log::info("Cloned repo {$this->repoUrl} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Cloned repo {$this->repoUrl}");
} else {
// failed
Log::error("Failed to clone repo {$this->repoUrl} " . $process->getErrorOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to clone repo {$this->repoUrl}");
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
} catch (\Throwable $th) {
// failed
Log::error("Failed to clone repo {$this->repoUrl} " . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to clone repo {$this->repoUrl}");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$CLONE_REPOSITORY;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class CopyTestsFolder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $testsDir;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $testsDir, $tempDir, $command)
{
$this->submission = $submission;
$this->testsDir = $testsDir;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Copying tests folder to {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Copying tests folder");
try {
// processing
if (is_dir($this->tempDir . '/tests')) {
Log::info("Removing old tests folder from {$this->tempDir}");
Process::fromShellCommandline("rm -rf {$this->tempDir}/tests")->run();
}
mkdir($this->tempDir . '/tests', 0777, true);
mkdir($this->tempDir . '/tests/api', 0777, true);
mkdir($this->tempDir . '/tests/web', 0777, true);
mkdir($this->tempDir . '/tests/web/images', 0777, true);
foreach ($this->command as $key => $value) {
$process = new Process($value);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
Log::info("Copied tests {$value[2]} folder to {$value[3]}");
} else {
Log::error("Failed to copying tests {$value[2]} folder to {$value[3]}");
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
Process::fromShellCommandline('kill ' . $process_pid)->run();
throw new \Exception($process->getErrorOutput());
}
}
// completed
Log::info("Copied tests folder to {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Copied tests folder");
} catch (\Throwable $th) {
Log::error("Failed to copying tests folder to {$this->tempDir} " . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to copying tests folder");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$COPY_TESTS_FOLDER;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class DeleteTempDirectory implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $tempDir, $command)
{
$this->submission = $submission;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Deleting folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Deleting folder");
try {
// processing
foreach ($this->command as $key => $value) {
$process = new Process($value, null, null, null, 120);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
Log::info('Command ' . implode(" ", $value) . ' is successful');
} else {
Log::error("Failed to delete folder {$this->tempDir} " . $process->getErrorOutput());
// $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to delete folder");
}
Process::fromShellCommandline('kill ' . $process_pid)->run();
}
// completed
Log::info("Deleted folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Deleted folder");
} catch (\Throwable $th) {
Log::error("Failed to delete folder {$this->tempDir} " . $th->getMessage());
// $this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to delete folder");
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$DELETE_TEMP_DIRECTORY;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class ExamineFolderStructure implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $tempDir, $command)
{
$this->submission = $submission;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Examining folder structure from {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Examining folder structure");
try {
// processing
$process = new Process($this->command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
// completed
$projectStructure = $submission->project->defaultFileStructure;
$defaultStructure = $projectStructure->structure;
$excludedFolders = $projectStructure->excluded;
$replacementFolders = $projectStructure->replacements;
$submissionStructure = $this->getDirectoryStructure($this->tempDir, $excludedFolders, $replacementFolders);
$diff = $this->compare_file_structures($defaultStructure, $submissionStructure);
$missingFiles = [];
foreach ($diff as $key => $value) {
if (gettype($key) == 'integer') {
if (!in_array($value, $excludedFolders)) array_push($missingFiles, $value);
} else {
if (!in_array($key, $excludedFolders)) array_push($missingFiles, [$key => $value]);
}
}
Log::info("Finished examining folder structure from {$this->tempDir}");
if (empty($missingFiles)) {
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Finished examining folder structure from successfully");
} else {
Log::error("Failed to examine folder structure from {$this->tempDir} " . json_encode($missingFiles) . " are missing");
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Submitted project is missing the following files " . json_encode($missingFiles));
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
} else {
Log::error("Failed to examine folder structure from {$this->tempDir} " . $process->getErrorOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to examine folder structure");
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
} catch (\Throwable $th) {
Log::error("Failed to examine folder structure from {$this->tempDir}" . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to examine folder structure");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function getDirectoryStructure($dirPath, $excludedFolders, $replacementFolders)
{
$structure = [];
$files = glob($dirPath . '/*');
foreach ($files as $file) {
if (is_dir($file)) {
$dirName = basename($file);
if (!in_array($dirName, $excludedFolders)) {
if (isset($replacementFolders[$dirName])) {
$dirName = $replacementFolders[$dirName];
}
$structure[$dirName] = $this->getDirectoryStructure($file, $excludedFolders, $replacementFolders);
}
} else {
$structure[basename($file)] = '';
}
}
return $structure;
}
private function compare_file_structures($defaultStructure, $submittedStructure)
{
$diff = [];
foreach ($defaultStructure as $key => $value) {
if (is_array($value)) {
if (!isset($submittedStructure[$key])) {
$diff[$key] = $value;
} else {
$new_diff = $this->compare_file_structures($value, $submittedStructure[$key]);
if (!empty($new_diff)) {
$diff[$key] = $new_diff;
}
}
} else if (!array_key_exists($key, $submittedStructure) || $submittedStructure[$key] !== $value) {
$diff[] = $key;
}
}
return $diff;
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$EXAMINE_FOLDER_STRUCTURE;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class NpmInstall implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $tempDir, $command)
{
$this->submission = $submission;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("NPM installing in folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM installing");
try {
// processing
// check if the module is already exist within the assets folder
$process = new Process($this->command, $this->tempDir, null, null, 120);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
Log::info("NPM installed in folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "NPM installed");
} else {
Log::error("Failed to NPM install in folder {$this->tempDir} " . $process->getErrorOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to NPM install");
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
throw new \Exception($process->getErrorOutput());
}
} catch (\Throwable $th) {
Log::error("Failed to NPM install in folder {$this->tempDir}" . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to NPM install");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$NPM_INSTALL;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
class NpmRunStart
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $tempDir, $command)
{
$this->submission = $submission;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
$tempDir = $this->tempDir;
$command = $this->command;
Log::info("NPM run start is processing in folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM run start is processing");
// Change port number in .env file
$port = $this->getAvailablePort();
if (!$port) {
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to find an available port for the project");
return;
}
$submission->updatePort($port);
// Change port number in .env file
$envPath = "$tempDir/.env";
if (file_exists($envPath)) {
$envContent = file_get_contents($envPath);
$envContent = preg_replace('/PORT=\d+/', "PORT=$port", $envContent);
file_put_contents($envPath, $envContent);
}
// Run NPM start command
try {
$process = new Process($command, $tempDir, null, null, null);
$process->start();
$process->waitUntil(function ($type, $output) use ($port) {
return strpos($output, "Server started on port $port") !== false || strpos($output, "MongoNetworkError") !== false;
}, 60000); // Wait for 60 seconds
if (strpos($process->getOutput(), "Server started on port $port") !== false) {
Log::info("NPM run start is completed in folder {$tempDir} the application is running on port $port");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, $process->getOutput());
$process->wait();
} else {
Log::error("Failed to NPM run start in folder {$tempDir} due to error " . $process->getOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port");
Process::fromShellCommandline("npx kill-port $port")->run();
}
} catch (ProcessTimedOutException $th) {
$process->stop();
Log::error("Failed to NPM run start in folder {$tempDir} due to timeout " . $process->getOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port due to timeout");
Process::fromShellCommandline("npx kill-port $port")->run();
} catch (\Throwable $th) {
Log::error("Failed to NPM run start in folder {$tempDir}" . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to start application on port $port");
Process::fromShellCommandline("npx kill-port $port")->run();
}
}
/**
* Get an available port number.
*
* @return int|null
*/
private function getAvailablePort(): ?int
{
$minPort = 9000;
$maxPort = 9999;
for ($port = $minPort; $port <= $maxPort; $port++) {
$fp = @fsockopen('localhost', $port, $errno, $errstr, 1);
if (!$fp && Submission::where('port', $port)->doesntExist()) {
return $port;
} else {
if (is_resource($fp)) {
fclose($fp);
}
}
}
return null;
}
/**
* Update the submission status and result of a specific step.
*
* @param Submission $submission
* @param string $status
* @param string $output
* @return void
*/
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$NPM_RUN_START;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class NpmRunTests implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $tempDir, $command)
{
$this->submission = $submission;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("NPM running tests in folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "NPM running tests");
try {
// processing
$pass_all = [];
$commands = $this->command;
foreach ($commands as $key => $command) {
$command_string = implode(" ", $command);
Log::info("Running {$command_string} in folder {$this->tempDir}");
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$PROCESSING, "Running");
usleep(100000);
$process = new Process($command, $this->tempDir, null, null, 120);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
$pass_all[$key] = true;
Log::info("{$command_string} in folder {$this->tempDir}");
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$COMPLETED, "Completed");
} else {
$pass_all[$key] = false;
Log::error("Failed to NPM run test {$command_string} " . $process->getErrorOutput());
$this->updateSubmissionTestsResultsStatus($command_string, $submission, Submission::$FAILED, $process->getErrorOutput());
Process::fromShellCommandline('kill ' . $process_pid)->run();
}
}
if (in_array(false, $pass_all) == false) {
Log::info("NPM ran tests in folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "NPM tested");
} else {
Log::info("NPM failed to run tests in folder {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to run NPM tests");
if ($submission->port) Process::fromShellCommandline("npx kill-port $submission->port")->run();
}
} catch (\Throwable $th) {
Log::error("Failed to NPM run tests in folder {$this->tempDir} " . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to NPM running tests");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function updateSubmissionTestsResultsStatus($testName, Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$NPM_RUN_TESTS;
$submission->updateOneTestResult($stepName, $testName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$NPM_RUN_TESTS;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class ReplacePackageJson implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $packageJson;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $packageJson, $tempDir, $command)
{
$this->submission = $submission;
$this->packageJson = $packageJson;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Replacing package.json to {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Replacing package.json");
try {
// processing
$process = new Process($this->command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
// completed
Log::info("Replaced package.json to {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Replaced package.json");
} else {
// failed
Log::error("Failed to replace package.json to {$this->tempDir} " . $process->getErrorOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to replace package.json");
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
} catch (\Throwable $th) {
// failed
Log::error("Failed to replace package.json to {$this->tempDir} " . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to replace package.json");
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$REPLACE_PACKAGE_JSON;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Jobs\NodeJS;
use App\Models\NodeJS\ExecutionStep;
use App\Models\NodeJS\Submission;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
class UnzipZipFiles implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $submission;
public $zipFileDir;
public $tempDir;
public $command;
/**
* Create a new job instance.
*/
public function __construct($submission, $zipFileDir, $tempDir, $command)
{
$this->submission = $submission;
$this->zipFileDir = $zipFileDir;
$this->tempDir = $tempDir;
$this->command = $command;
}
/**
* Execute the job.
*/
public function handle(): void
{
$submission = $this->submission;
Log::info("Unzipping {$this->zipFileDir} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$PROCESSING, "Unzipping submitted folder");
try {
if (!file_exists($this->tempDir)) mkdir($this->tempDir, 0777, true);
// processing
$process = new Process($this->command);
$process->start();
$process_pid = $process->getPid();
$process->wait();
if ($process->isSuccessful()) {
Log::info("Unzipped {$this->zipFileDir} into {$this->tempDir}");
$this->updateSubmissionStatus($submission, Submission::$COMPLETED, "Unzipped submitted folder");
} else {
Log::error("Failed to unzip {$this->zipFileDir} " . $process->getErrorOutput());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed to unzip submitted folder");
Process::fromShellCommandline('kill ' . $process_pid)->run();
Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
} catch (\Throwable $th) {
// failed
Log::error("Failed to unzip {$this->zipFileDir} " . $th->getMessage());
$this->updateSubmissionStatus($submission, Submission::$FAILED, "Failed tp unzip submitted folder");
// Process::fromShellCommandline("rm -rf {$this->tempDir}")->run();
}
}
private function updateSubmissionStatus(Submission $submission, string $status, string $output): void
{
$stepName = ExecutionStep::$UNZIP_ZIP_FILES;
if ($status != Submission::$PROCESSING) $submission->updateOneResult($stepName, $status, $output);
if ($status != Submission::$COMPLETED) $submission->updateStatus($status);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models\Android;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Enrollment extends Model
{
protected $table = "android_enrollment";
protected $fillable = ["android_topic_id", "user_id", "status"];
public function user() : BelongsTo {
return $this->belongsTo(User::class);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Models\Android;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use App\Models\Android\Topic;
use App\Models\Android\Task;
use App\Models\User;
use App\Models\Android\SubmitsTestCase;
class Submits extends Model
{
protected $table = "android_submits";
protected $fillable = ["user_id", "android_topic_id", "android_task_id", "duration", "upload", "comment", "validator"];
public function topic() : BelongsTo {
return $this->belongsTo(Topic::class, 'android_topic_id', 'id');
}
public function task() : BelongsTo {
return $this->belongsTo(Task::class, 'android_task_id', 'id');
}
public function user() : BelongsTo {
return $this->belongsTo(User::class, 'user_id', 'id');
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models\Android;
use Illuminate\Database\Eloquent\Model;
class SubmitsFinalSubmission extends Model
{
protected $table = "android_submits_submission";
protected $fillable = ["user_id", "android_topic_id", "tipe", "userfile"];
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models\Android;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Android\Testcase;
class SubmitsTestCase extends Model
{
protected $table = "android_submits_testcase";
protected $fillable = ["user_id", "android_submit_id", "android_testcase_id", "status", "status_waiting"];
public function android_testcase() : BelongsTo{
return $this->belongsTo( Testcase::class, 'android_testcase_id', 'id' );
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Models\Android;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $table = "android_task";
protected $fillable = ['android_topic_id', 'task_no', 'task_name', 'caption', 'material', 'tipe'];
public $timestamp = true;
// create and update at menggunakan timestamp dari server (epoch)
public function getCreatedAtAttribute( $value ) {
return Carbon::parse($value)->timestamp;
}
public function getUpdatedAtAttribute( $value ) {
return Carbon::parse($value)->timestamp;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models\Android;
use Illuminate\Database\Eloquent\Model;
class Task_waiting extends Model
{
protected $table = "android_task_waiting";
protected $fillable = ["user_id", "android_topic_id", "android_task_id"];
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Models\Android;
use Illuminate\Database\Eloquent\Model;
class Testcase extends Model
{
protected $table = "android_testcase";
protected $fillable = ["task_id", "case", "score"];
public $timestamps = false;
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Models\Android;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Topic extends Model
{
protected $table = "android_topics";
protected $fillable = ['title', 'description', 'folder_path', 'picturePath', 'status'];
public $timestamp = true;
// create and update at menggunakan timestamp dari server (epoch)
public function getCreatedAtAttribute( $value ) {
return Carbon::parse($value)->timestamp;
}
public function getUpdatedAtAttribute( $value ) {
return Carbon::parse($value)->timestamp;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models\Flutter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Topic extends Model
{
protected $table = 'flutter_topics'; // Sesuaikan dengan nama tabel di database
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Flutter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Topic_detail extends Model
{
protected $table = 'flutter_topics_detail'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['title', 'id_topics', 'controller', 'description', 'folder_path', 'file_name'];
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Flutter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserCheck extends Model
{
protected $table = 'flutter_user_submits'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['userid'];
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Flutter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserLogin extends Model
{
protected $table = 'users'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['name', 'email'];
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class FlutterTestResult extends Model
{
use HasFactory;
protected $table = 'flutter_test_results'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['user_id', 'success_tests', 'failed_tests', 'score', 'flutterid'];
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyAnswer extends Model
{
use HasFactory;
protected $fillable = ['question_id', 'assessment_id', 'answer_text', 'submitted_at'];
public function question()
{
return $this->belongsTo(LiteracyQuestion::class, 'question_id');
}
public function assessment()
{
return $this->belongsTo(LiteracyAssessment::class, 'assessment_id');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class LiteracyAssessment extends Model
{
use HasFactory;
protected $table = 'literacy_assessments';
// Tentukan kolom yang bisa diisi secara massal
protected $fillable = [
'user_id',
'status',
'score',
'feedback',
'assessed_at',
'created_at',
];
/**
* Relasi ke User (siswa yang mengerjakan asesmen)
*/
public function user()
{
return $this->belongsTo(\App\Models\User::class, 'user_id');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyMaterial extends Model
{
use HasFactory;
protected $table = 'literacy_materials';
protected $fillable = ['user_id', 'title', 'description', 'file_path'];
public function teacher()
{
return $this->belongsTo(\App\Models\User::class, 'user_id');
}
public function questions()
{
return $this->hasMany(LiteracyQuestion::class, 'material_id');
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyOption extends Model
{
use HasFactory;
protected $table = 'literacy_options';
protected $fillable = ['question_id', 'option_text', 'is_correct', 'score'];
public function question()
{
return $this->belongsTo(LiteracyQuestion::class, 'question_id');
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyQuestion extends Model
{
use HasFactory;
protected $table = 'literacy_questions';
protected $fillable = ['material_id', 'question_text', 'type', 'essay_answer', 'essay_score'];
public function material()
{
return $this->belongsTo(LiteracyMaterial::class, 'material_id');
}
public function answers()
{
return $this->hasMany(LiteracyAnswer::class, 'question_id');
}
public function options()
{
return $this->hasMany(LiteracyOption::class, 'question_id');
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
use App\Models\Literacy\LiteracyTopic_detail;
class LiteracySubmitUser extends Model
{
use HasFactory;
protected $table = 'react_submit_user';
protected $fillable = [
'id_user', 'nama_user', 'materi', 'topic_id', 'nilai', 'status'
];
public function user()
{
return $this->belongsTo(User::class, 'id_user');
}
public function topicDetail()
{
return $this->belongsTo(LiteracyTopic_detail::class, 'topic_id');
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyTopic extends Model
{
protected $table = 'react_topics'; // Sesuaikan dengan nama tabel di database
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyTopic_detail extends Model
{
protected $table = 'react_topics_detail'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['title', 'id_topics' ,'controller', 'description', 'folder_path','file_name'];
public function user_enroll()
{
return $this->hasMany(LiteracyUserEnroll::class);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyUserCheck extends Model
{
protected $table = 'react_user_submits'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['userid'];
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyUserEnroll extends Model
{
use HasFactory;
protected $fillable = [
'id',
'id_users',
'php_topics_detail_id',
'created_at'
];
protected $table = 'react_student_enroll';
public function user()
{
return $this->belongsTo(\App\Models\User::class, 'id_users');
}
public function reactTopicDetail()
{
return $this->belongsTo(LiteracyTopic_detail::class, 'php_topics_detail_id');
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Literacy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LiteracyUserLogin extends Model
{
protected $table = 'users'; // Sesuaikan dengan nama tabel di database
protected $fillable = ['name','email'];
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Material extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'title',
'description',
'folder_path',
'status',
];
protected $dates = ['deleted_at'];
public function tasks()
{
return $this->hasMany(Task::class);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Models\NodeJS;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ExecutionStep extends Model
{
use HasFactory;
protected $connection = 'nodejsDB';
protected $fillable = [
'name',
'commands',
];
protected $casts = [
'commands' => 'array',
];
static $CLONE_REPOSITORY = 'Clone Repository';
static $UNZIP_ZIP_FILES = 'Unzip ZIP Files';
static $REMOVE_ZIP_FILES = 'Remove ZIP Files';
static $EXAMINE_FOLDER_STRUCTURE = 'Examine Folder Structure';
static $ADD_ENV_FILE = 'Add .env File';
static $REPLACE_PACKAGE_JSON = 'Replace package.json';
static $COPY_TESTS_FOLDER = "Copy 'tests' Folder";
static $NPM_INSTALL = 'NPM Install';
static $NPM_RUN_START = 'NPM Run Start';
static $NPM_RUN_TESTS = 'NPM Run Tests';
static $DELETE_TEMP_DIRECTORY = 'Delete Temp Directory';
public function getCommandsAttribute($value)
{
return json_decode($value);
}
public function setCommandsAttribute($value)
{
$this->attributes['commands'] = json_encode($value);
}
public function projectExecutionSteps()
{
return $this->hasMany(ProjectExecutionStep::class);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Models\NodeJS;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Project extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia;
protected $connection = 'nodejsDB';
protected $fillable = [
'title',
'description',
'tech_stack',
'github_url',
'image',
];
protected $casts = [
'tech_stack' => 'array',
];
public function defaultFileStructure(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectsDefaultFileStructure::class);
}
public function submissions(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Submission::class);
}
public function projectExecutionSteps(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(ProjectExecutionStep::class);
}
public function getTechStackAttribute($value): array
{
return json_decode($value, true);
}
public function setTechStackAttribute($value): void
{
$this->attributes['tech_stack'] = json_encode($value);
}
public function getImageAttribute(): string
{
return $this->getFirstMediaUrl('project_images');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Models\NodeJS;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectExecutionStep extends Model
{
use HasFactory;
protected $connection = 'nodejsDB';
protected $fillable = [
'project_id',
'execution_step_id',
'order',
'variables',
];
protected $casts = [
'variables' => 'array',
];
public function getVariablesAttribute($value)
{
return json_decode($value);
}
public function setVariablesAttribute($value)
{
$this->attributes['variables'] = json_encode($value);
}
public function project()
{
return $this->belongsTo(Project::class);
}
public function executionStep()
{
return $this->belongsTo(ExecutionStep::class);
}
}

Some files were not shown because too many files have changed in this diff Show More