feat: Initial commit for Saqel Platform architecture, backend, Docker, and documentation

This commit is contained in:
Hamza-Ayed
2026-08-26 15:45:55 +03:00
commit d53b64500a
177 changed files with 19909 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AiReport extends Model
{
protected $fillable = ['user_id', 'type', 'payload', 'summary'];
protected function casts(): array
{
return [
'payload' => 'array',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Answer extends Model
{
protected $fillable = [
'quiz_attempt_id',
'question_id',
'selected_option_id',
'is_correct',
'time_spent_seconds',
];
protected $casts = [
'is_correct' => 'boolean',
];
public function attempt(): BelongsTo
{
return $this->belongsTo(QuizAttempt::class, 'quiz_attempt_id');
}
public function question(): BelongsTo
{
return $this->belongsTo(Question::class);
}
public function selectedOption(): BelongsTo
{
return $this->belongsTo(QuestionOption::class, 'selected_option_id');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Database\Factories\ConceptFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Concept extends Model
{
/** @use HasFactory<ConceptFactory> */
use HasFactory;
protected $fillable = ['subject_id', 'name', 'remedial_lesson_id'];
public function subject(): BelongsTo
{
return $this->belongsTo(Subject::class);
}
public function remedialLesson(): BelongsTo
{
return $this->belongsTo(Lesson::class, 'remedial_lesson_id');
}
public function questions(): HasMany
{
return $this->hasMany(Question::class);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ConceptMastery extends Model
{
protected $table = 'concept_mastery';
protected $fillable = [
'user_id',
'concept_id',
'correct_count',
'wrong_count',
'mastery_score',
];
public function concept(): BelongsTo
{
return $this->belongsTo(Concept::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Database\Factories\CourseFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Course extends Model
{
/** @use HasFactory<CourseFactory> */
use HasFactory;
protected $fillable = ['subject_id', 'grade', 'track', 'title', 'description'];
public function subject(): BelongsTo
{
return $this->belongsTo(Subject::class);
}
public function lessons(): HasMany
{
return $this->hasMany(Lesson::class)->orderBy('order_index');
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Database\Factories\LessonFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Lesson extends Model
{
/** @use HasFactory<LessonFactory> */
use HasFactory;
protected $fillable = ['course_id', 'title', 'order_index', 'video_id', 'duration_seconds', 'transcript'];
public function course(): BelongsTo
{
return $this->belongsTo(Course::class);
}
public function concepts(): BelongsToMany
{
return $this->belongsToMany(Concept::class);
}
public function quizzes(): HasMany
{
return $this->hasMany(Quiz::class);
}
public function progressFor(int $userId): ?LessonProgress
{
/** @var LessonProgress|null $progress */
$progress = $this->hasOne(LessonProgress::class)
->where('user_id', $userId)
->first();
return $progress;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class LessonProgress extends Model
{
protected $table = 'lesson_progress';
protected $fillable = [
'user_id',
'lesson_id',
'last_position_seconds',
'watched_percentage',
'completed_at',
];
protected function casts(): array
{
return [
'completed_at' => 'datetime',
];
}
public function lesson(): BelongsTo
{
return $this->belongsTo(Lesson::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Database\Factories\QuestionFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Question extends Model
{
/** @use HasFactory<QuestionFactory> */
use HasFactory;
protected $fillable = ['quiz_id', 'concept_id', 'body', 'explanation'];
public function quiz(): BelongsTo
{
return $this->belongsTo(Quiz::class);
}
public function concept(): BelongsTo
{
return $this->belongsTo(Concept::class);
}
public function options(): HasMany
{
return $this->hasMany(QuestionOption::class);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class QuestionOption extends Model
{
protected $fillable = ['question_id', 'body', 'is_correct'];
protected $casts = [
'is_correct' => 'boolean',
];
public function question(): BelongsTo
{
return $this->belongsTo(Question::class);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Database\Factories\QuizFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Quiz extends Model
{
/** @use HasFactory<QuizFactory> */
use HasFactory;
protected $fillable = ['lesson_id', 'title', 'trigger_seconds'];
public function lesson(): BelongsTo
{
return $this->belongsTo(Lesson::class);
}
public function questions(): HasMany
{
return $this->hasMany(Question::class);
}
public function attempts(): HasMany
{
return $this->hasMany(QuizAttempt::class);
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class QuizAttempt extends Model
{
protected $fillable = ['user_id', 'quiz_id', 'score'];
public function quiz(): BelongsTo
{
return $this->belongsTo(Quiz::class);
}
public function answers(): HasMany
{
return $this->hasMany(Answer::class);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Database\Factories\SubjectFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Subject extends Model
{
/** @use HasFactory<SubjectFactory> */
use HasFactory;
protected $fillable = ['name', 'slug'];
public function courses(): HasMany
{
return $this->hasMany(Course::class);
}
public function concepts(): HasMany
{
return $this->hasMany(Concept::class);
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'email', 'password', 'role', 'api_token', 'grade', 'track'])]
#[Hidden(['password', 'remember_token', 'api_token'])]
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function isGuardian(): bool
{
return $this->role === 'guardian';
}
public function students(): BelongsToMany
{
return $this->belongsToMany(User::class, 'guardian_student', 'guardian_id', 'student_id');
}
public function guardians(): BelongsToMany
{
return $this->belongsToMany(User::class, 'guardian_student', 'student_id', 'guardian_id');
}
public function lessonProgress(): HasMany
{
return $this->hasMany(LessonProgress::class);
}
public function conceptMastery(): HasMany
{
return $this->hasMany(ConceptMastery::class);
}
public function aiReports(): HasMany
{
return $this->hasMany(AiReport::class);
}
}