diff --git a/.env.example b/.env.example
index 5dd89dc..e6ff2eb 100644
--- a/.env.example
+++ b/.env.example
@@ -7,12 +7,15 @@ APP_URL=http://localhost
SEED_ADMIN_EMAIL=admin@example.com
SEED_ADMIN_PASSWORD=admin_user
-SEED_NEWS_EDITOR_EMAIL="news.editor@example.com"
+SEED_NEWS_EDITOR_EMAIL="user+news.editor@example.com"
SEED_NEWS_EDITOR_PASSWORD="news_editor"
-SEED_EVENT_EDITOR_EMAIL="events.editor@example.com"
+SEED_EVENT_EDITOR_EMAIL="user+events.editor@example.com"
SEED_EVENT_EDITOR_PASSWORD="events_editor"
+SEED_COURSE_MANAGER_EMAIL="course_manager@portal.ce.pdn.ac.lk"
+SEED_COURSE_MANAGER_PASSWORD="course_manager"
+
SEED_USER_EMAIL=user@user.com
SEED_USER_PASSWORD=regular_user
@@ -22,6 +25,7 @@ APP_READ_ONLY_LOGIN=true
DEBUGBAR_ENABLED=false
LOG_CHANNEL=daily
LOG_LEVEL=debug
+LOG_DISCORD_WEBHOOK_URL=
# Drivers
DB_CONNECTION=mysql
diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel_pull_request.yml
similarity index 100%
rename from .github/workflows/laravel.yml
rename to .github/workflows/laravel_pull_request.yml
diff --git a/.github/workflows/laravel_push.yml b/.github/workflows/laravel_push.yml
new file mode 100644
index 0000000..d6f4a21
--- /dev/null
+++ b/.github/workflows/laravel_push.yml
@@ -0,0 +1,31 @@
+name: Laravel Push Test
+
+on: [push]
+
+jobs:
+ laravel-tests:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e
+ with:
+ php-version: '8.0'
+ - uses: actions/checkout@v2
+ - name: Copy .env
+ run: php -r "file_exists('.env') || copy('.env.example', '.env');"
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
+ - name: Generate key
+ run: php artisan key:generate
+ - name: Directory Permissions
+ run: chmod -R 777 storage bootstrap/cache
+ - name: Create Database
+ run: |
+ mkdir -p database
+ touch database/database.sqlite
+ - name: Execute tests (Unit and Feature tests) via PHPUnit
+ env:
+ DB_CONNECTION: sqlite
+ DB_DATABASE: database/database.sqlite
+ run: |
+ php artisan test -p --colors --debug
\ No newline at end of file
diff --git a/README.md b/README.md
index 191ace7..9b47e4e 100644
--- a/README.md
+++ b/README.md
@@ -14,18 +14,18 @@ Please make sure you already created a Database and a Database User Account.
#### Install Dependencies
-```
+```bash
// Install PHP dependencies
composer install
-// Install Node dependencies (development mode)
-npm install
-npm run dev
+// Install Node dependencies (development mode, can use `npm` as well, but recommended to use `pnpm` here)
+pnpm install
+pnpm run dev
```
##### Additional useful commands
-```
+```bash
// If you received mmap() error, use this command
php -d memory_limit=-1 /usr/local/bin/composer install
@@ -42,7 +42,7 @@ First you need to copy `.env.example` and save as `.env` in the root folder, and
Next follow the below commands
-```
+```bash
// Prepare the public link for storage
php artisan storage:link
@@ -56,20 +56,20 @@ git config --local core.hooksPath .githooks
#### Serve in the Local environment
-```
+```bash
// Serve PHP web server
php artisan serve
// Serve PHP web server, in a specific IP & port
php artisan serve --host=0.0.0.0 --port=8000
-// To work with Vue components, you need to run this in parallel
-npm run watch
+// To work with Vue components, you need to run this in parallel (can use `npm` as well, but recommended to use `pnpm` here)
+pnpm run watch
```
#### Cache and optimization
-```
+```bash
// Remove dev dependencies
composer install --optimize-autoloader --no-dev
@@ -84,14 +84,14 @@ php artisan view:clear
#### Maintenance related commands
-```
+```bash
php artisan down --message="{Message}" --retry=60
php artisan up
```
#### Other useful instructions
-```
+```bash
// Create Model, Controller and Database Seeder
php artisan make:model {name} --migration --controller --seed
@@ -106,6 +106,15 @@ php artisan test
```
+#### Maintenance Scripts
+
+Can be found under `./scripts.` folder. In the production environment, scripts need to be run with `sudo` from the base directory to work correctly.
+
+Ex:
+```bash
+sudo sh ./scripts/deploy-prod.sh
+```
+
#### Resource Routes - Standardard Pattern
| Verb | URI | Action | Route Name |
diff --git a/app/Domains/AcademicProgram/AcademicProgram.php b/app/Domains/AcademicProgram/AcademicProgram.php
new file mode 100644
index 0000000..248b4ce
--- /dev/null
+++ b/app/Domains/AcademicProgram/AcademicProgram.php
@@ -0,0 +1,38 @@
+ 'Undergraduate',
+ 'postgraduate' => 'Postgraduate'
+ ];
+ }
+
+ public static function getVersions(): array
+ {
+ // TODO integrate with Taxonomies
+ return [
+ 1 => 'Current Curriculum',
+ 2 => 'Curriculum - Effective from E22'
+ ];
+ }
+
+ public static function getTypes(): array
+ {
+ return [
+ 'Found' => 'Foundation',
+ 'Core' => 'Core',
+ 'GE' => 'General Elective',
+ 'TE' => 'Technical Elective'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Domains/AcademicProgram/Course/Models/Course.php b/app/Domains/AcademicProgram/Course/Models/Course.php
new file mode 100644
index 0000000..d55c46d
--- /dev/null
+++ b/app/Domains/AcademicProgram/Course/Models/Course.php
@@ -0,0 +1,136 @@
+ 'string',
+ 'type' => 'string',
+ 'objectives' => 'json',
+ 'time_allocation' => 'json',
+ 'marks_allocation' => 'json',
+ 'ilos' => 'json',
+ 'references' => 'json',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ public static function getILOTemplate(): array
+ {
+ // TODO Get the list from Taxonomies
+ return [
+ 'general' => [],
+ 'knowledge' => [],
+ 'skills' => [],
+ 'attitudes' => [],
+ ];
+ }
+ public static function getMarksAllocation(): array
+ {
+ // TODO Get the list from Taxonomies
+ return [
+ 'practicals' => null,
+ 'tutorials' => null,
+ 'quizzes' => null,
+ 'projects' => null,
+ 'participation' => null,
+ 'mid_exam' => null,
+ 'end_exam' => null,
+ ];
+ }
+
+ public static function getTimeAllocation(): array
+ {
+ // TODO Get the list from Taxonomies
+ return [
+ 'lecture' => null,
+ 'tutorial' => null,
+ 'practical' => null,
+ 'design' => null,
+ 'assignment' => null,
+ 'independent_learning' => null
+ ];
+ }
+
+ public function academicProgram()
+ {
+ return $this->getAcademicPrograms()[$this->academic_program];
+ }
+
+ public function createdUser()
+ {
+ return $this->belongsTo(User::class, 'created_by');
+ }
+
+ public function updatedUser()
+ {
+ return $this->belongsTo(User::class, 'updated_by');
+ }
+
+ public function semester()
+ {
+ return $this->belongsTo(Semester::class, 'semester_id');
+ }
+
+ public function version()
+ {
+ return $this->getVersions()[$this->version];
+ }
+
+ public function modules()
+ {
+ return $this->hasMany(CourseModule::class);
+ }
+
+ protected static function newFactory()
+ {
+ return CourseFactory::new();
+ }
+}
\ No newline at end of file
diff --git a/app/Domains/AcademicProgram/Course/Models/CourseModule.php b/app/Domains/AcademicProgram/Course/Models/CourseModule.php
new file mode 100644
index 0000000..84d36d1
--- /dev/null
+++ b/app/Domains/AcademicProgram/Course/Models/CourseModule.php
@@ -0,0 +1,56 @@
+ 'integer',
+ 'topic' => 'string',
+ 'description' => 'string',
+ 'time_allocation' => 'json',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ 'created_by' => 'integer',
+ 'updated_by' => 'integer',
+ ];
+
+ public function course()
+ {
+ return $this->belongsTo(Course::class);
+ }
+}
\ No newline at end of file
diff --git a/app/Domains/AcademicProgram/Course/Models/Traits/Scope/CourseScope.php b/app/Domains/AcademicProgram/Course/Models/Traits/Scope/CourseScope.php
new file mode 100644
index 0000000..cc11480
--- /dev/null
+++ b/app/Domains/AcademicProgram/Course/Models/Traits/Scope/CourseScope.php
@@ -0,0 +1,57 @@
+where('academic_program', $program);
+ }
+
+ /**
+ * Scope a query to only include courses for a specific semester.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $semesterId
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeForSemester($query, $semesterId)
+ {
+ return $query->where('semester_id', $semesterId);
+ }
+
+ /**
+ * Scope a query to only include courses of a specific type (Core, General Elective, Technical Elective).
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param string $type
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeOfType($query, $type)
+ {
+ return $query->where('type', $type);
+ }
+
+ /**
+ * Scope a query to only include courses of a specific version.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $version
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeOfVersion($query, $version)
+ {
+ return $query->where('version', $version);
+ }
+}
\ No newline at end of file
diff --git a/app/Domains/AcademicProgram/Semester/Models/Semester.php b/app/Domains/AcademicProgram/Semester/Models/Semester.php
new file mode 100644
index 0000000..47a8d60
--- /dev/null
+++ b/app/Domains/AcademicProgram/Semester/Models/Semester.php
@@ -0,0 +1,61 @@
+ 'string',
+ 'version' => 'integer',
+ 'academic_program' => 'string',
+ 'description' => 'string',
+ 'url' => 'string',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ 'created_by' => 'integer',
+ 'updated_by' => 'integer',
+ ];
+
+
+ public function getLatestCurriculumAttribute()
+ {
+ $maxVersion = self::where('title', $this->title)->max('version');
+ return $this->version === $maxVersion;
+ }
+
+ public function createdUser()
+ {
+ return $this->belongsTo(User::class, 'created_by', 'id');
+ }
+
+ public function updatedUser()
+ {
+ return $this->belongsTo(User::class, 'updated_by', 'id');
+ }
+
+ public function academicProgram()
+ {
+ return $this->getAcademicPrograms()[$this->academic_program];
+ }
+
+ protected static function newFactory()
+ {
+ return SemesterFactory::new();
+ }
+}
\ No newline at end of file
diff --git a/app/Domains/AcademicProgram/Semester/Models/Traits/Scope/SemesterScope.php b/app/Domains/AcademicProgram/Semester/Models/Traits/Scope/SemesterScope.php
new file mode 100644
index 0000000..9fcde35
--- /dev/null
+++ b/app/Domains/AcademicProgram/Semester/Models/Traits/Scope/SemesterScope.php
@@ -0,0 +1,33 @@
+where('version', $version);
+ }
+
+ /**
+ * Scope a query to only include semesters for a specific academic program.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param string $program
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeForProgram($query, $program)
+ {
+ return $query->where('academic_program', $program);
+ }
+}
\ No newline at end of file
diff --git a/app/Domains/Auth/Http/Controllers/Frontend/Auth/RegisterController.php b/app/Domains/Auth/Http/Controllers/Frontend/Auth/RegisterController.php
index 3fadeb9..81739ad 100644
--- a/app/Domains/Auth/Http/Controllers/Frontend/Auth/RegisterController.php
+++ b/app/Domains/Auth/Http/Controllers/Frontend/Auth/RegisterController.php
@@ -8,6 +8,7 @@
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use LangleyFoxall\LaravelNISTPasswordRules\PasswordRules;
+use App\Rules\ValidateAsInternalEmail;
/**
* Class RegisterController.
@@ -74,7 +75,7 @@ protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:100'],
- 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')],
+ 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users'), new ValidateAsInternalEmail()],
'password' => array_merge(['max:100'], PasswordRules::register($data['email'] ?? null)),
'terms' => ['required', 'in:1'],
'g-recaptcha-response' => ['required_if:captcha_status,true', new Captcha],
diff --git a/app/Domains/Auth/Http/Controllers/Frontend/Auth/SocialController.php b/app/Domains/Auth/Http/Controllers/Frontend/Auth/SocialController.php
index de0a032..46c7c82 100644
--- a/app/Domains/Auth/Http/Controllers/Frontend/Auth/SocialController.php
+++ b/app/Domains/Auth/Http/Controllers/Frontend/Auth/SocialController.php
@@ -5,6 +5,8 @@
use App\Domains\Auth\Events\User\UserLoggedIn;
use App\Domains\Auth\Services\UserService;
use Laravel\Socialite\Facades\Socialite;
+use Illuminate\Support\Facades\Validator;
+use App\Rules\ValidateAsInternalEmail;
/**
* Class SocialController.
@@ -30,7 +32,30 @@ public function redirect($provider)
*/
public function callback($provider, UserService $userService)
{
- $user = $userService->registerProvider(Socialite::driver($provider)->user(), $provider);
+ // Validate for internal user
+ $info = Socialite::driver($provider)->user();
+ $validator = Validator::make(
+ ['email' => $info->email, 'name' => $info->name],
+ ['email' => ['required', 'email', new ValidateAsInternalEmail()], 'name' => ['required']]
+ );
+
+ if ($validator->fails()) {
+ $errorMessage = "";
+ $errors = $validator->errors();
+
+ foreach ($errors->messages() as $key => $messages) {
+ if (is_array($messages)) {
+ foreach ($messages as $message) {
+ $errorMessage .= $message . ' ';
+ }
+ } else {
+ $errorMessage .= $messages . ' ';
+ }
+ }
+ return redirect()->route('frontend.auth.login')->withFlashDanger(trim($errorMessage));
+ }
+
+ $user = $userService->registerProvider($info, $provider);
if (!$user->isActive()) {
auth()->logout();
@@ -43,4 +68,4 @@ public function callback($provider, UserService $userService)
return redirect()->route(homeRoute());
}
-}
+}
\ No newline at end of file
diff --git a/app/Domains/Event/Models/Event.php b/app/Domains/Event/Models/Event.php
index 2f78a16..5077a10 100644
--- a/app/Domains/Event/Models/Event.php
+++ b/app/Domains/Event/Models/Event.php
@@ -2,11 +2,12 @@
namespace App\Domains\Event\Models;
-use App\Domains\Event\Models\Traits\Scope\EventScope;
+use App\Domains\Auth\Models\User;
use Database\Factories\EventFactory;
-use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
+use App\Domains\Event\Models\Traits\Scope\EventScope;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* Class News.
@@ -53,6 +54,11 @@ public function thumbURL()
else return config('constants.frontend.dummy_thumb');
}
+ public function user()
+ {
+ return $this->belongsTo(User::class, 'created_by');
+ }
+
/**
* Create a new factory instance for the model.
*
diff --git a/app/Domains/News/Models/News.php b/app/Domains/News/Models/News.php
index 589c87c..3fb86c8 100644
--- a/app/Domains/News/Models/News.php
+++ b/app/Domains/News/Models/News.php
@@ -2,11 +2,13 @@
namespace App\Domains\News\Models;
-use App\Domains\News\Models\Traits\Scope\NewsScope;
+use App\Domains\Auth\Models\User;
use Database\Factories\NewsFactory;
-use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
+use App\Domains\News\Models\Traits\Scope\NewsScope;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* Class News.
@@ -47,6 +49,11 @@ public function thumbURL()
else return config('constants.frontend.dummy_thumb');
}
+ public function user()
+ {
+ return $this->belongsTo(User::class, 'created_by');
+ }
+
/**
* Create a new factory instance for the model.
*
diff --git a/app/Http/Controllers/API/CourseApiController.php b/app/Http/Controllers/API/CourseApiController.php
new file mode 100644
index 0000000..4e164e5
--- /dev/null
+++ b/app/Http/Controllers/API/CourseApiController.php
@@ -0,0 +1,38 @@
+has('curriculum')) {
+ $query->where('version', $request->curriculum);
+ }
+
+ if ($request->has('semester')) {
+ $query->where('semester_id', $request->semester);
+ }
+
+ if ($request->has('type')) {
+ $query->where('type', $request->type);
+ }
+
+ $courses = $query->paginate(20);
+
+ return CourseResource::collection($courses);
+ } catch (\Exception $e) {
+ Log::error('Error in CourseApiController@index', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching courses'], 500);
+ }
+ }
+}
diff --git a/app/Http/Controllers/API/EventApiController.php b/app/Http/Controllers/API/EventApiController.php
index 909108d..80e9c10 100644
--- a/app/Http/Controllers/API/EventApiController.php
+++ b/app/Http/Controllers/API/EventApiController.php
@@ -5,59 +5,74 @@
use App\Domains\Event\Models\Event;
use App\Http\Controllers\Controller;
use App\Http\Resources\EventResource;
+use Illuminate\Support\Facades\Log;
class EventApiController extends Controller
{
public function index()
{
- $perPage = 20;
- $event = Event::where('enabled', 1)->orderBy('start_at', 'desc')
- ->paginate($perPage);
+ try {
+ $perPage = 20;
+ $event = Event::where('enabled', 1)->orderBy('start_at', 'desc')->paginate($perPage);
- if ($event->count() > 0) {
return EventResource::collection($event);
- } else {
- return response()->json(['message' => 'Events not found'], 404);
+ } catch (\Exception $e) {
+ Log::error('Error in EventApiController@index', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching events'], 500);
}
}
public function upcoming()
{
- $perPage = 20;
- $event = Event::getUpcomingEvents()
- ->orderBy('start_at', 'asc')
- ->paginate($perPage);
+ try {
+ $perPage = 20;
+ $event = Event::getUpcomingEvents()
+ ->orderBy('start_at', 'asc')
+ ->paginate($perPage);
- if ($event->count() > 0) {
- return EventResource::collection($event);
- } else {
- return response()->json(['message' => 'Events not found'], 404);
+ if ($event->count() > 0) {
+ return EventResource::collection($event);
+ } else {
+ return response()->json(['message' => 'Events not found'], 404);
+ }
+ } catch (\Exception $e) {
+ Log::error('Error in EventApiController@upcoming', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching upcoming events'], 500);
}
}
public function past()
{
- $perPage = 20;
- $event = Event::getPastEvents()
- ->orderBy('start_at', 'desc')
- ->paginate($perPage);
+ try {
+ $perPage = 20;
+ $event = Event::getPastEvents()
+ ->orderBy('start_at', 'desc')
+ ->paginate($perPage);
- if ($event->count() > 0) {
- return EventResource::collection($event);
- } else {
- return response()->json(['message' => 'Events not found'], 404);
+ if ($event->count() > 0) {
+ return EventResource::collection($event);
+ } else {
+ return response()->json(['message' => 'Events not found'], 404);
+ }
+ } catch (\Exception $e) {
+ Log::error('Error in EventApiController@past', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching past events'], 500);
}
}
-
public function show($id)
{
- $event = Event::find($id);
+ try {
+ $event = Event::find($id);
- if ($event) {
- return new EventResource($event);
- } else {
- return response()->json(['message' => 'Event not found'], 404);
+ if ($event) {
+ return new EventResource($event);
+ } else {
+ return response()->json(['message' => 'Event not found'], 404);
+ }
+ } catch (\Exception $e) {
+ Log::error('Error in EventApiController@show', ['error' => $e->getMessage(), 'id' => $id]);
+ return response()->json(['message' => 'An error occurred while fetching the event'], 500);
}
}
}
\ No newline at end of file
diff --git a/app/Http/Controllers/API/NewsApiController.php b/app/Http/Controllers/API/NewsApiController.php
index d69038d..993ed24 100644
--- a/app/Http/Controllers/API/NewsApiController.php
+++ b/app/Http/Controllers/API/NewsApiController.php
@@ -5,30 +5,35 @@
use App\Http\Resources\NewsResource;
use App\Http\Controllers\Controller;
use App\Domains\News\Models\News;
+use Illuminate\Support\Facades\Log;
class NewsApiController extends Controller
{
public function index()
{
- $perPage = 20;
- $news = News::latest()->where('enabled', 1)->paginate($perPage);
+ try {
+ $perPage = 20;
+ $news = News::latest()->where('enabled', 1)->paginate($perPage);
- if ($news->count() > 0) {
return NewsResource::collection($news);
- } else {
- return response()->json(['message' => 'News not found'], 404);
+ } catch (\Exception $e) {
+ Log::error('Error in NewsApiController@index', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching news'], 500);
}
}
-
public function show($id)
{
- $news = News::find($id);
-
- if ($news) {
- return new NewsResource($news);
- } else {
- return response()->json(['message' => 'News not found'], 404);
+ try {
+ $news = News::find($id);
+ if ($news) {
+ return new NewsResource($news);
+ } else {
+ return response()->json(['message' => 'News not found'], 404);
+ }
+ } catch (\Exception $e) {
+ Log::error('Error in NewsApiController@show', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching news'], 500);
}
}
}
\ No newline at end of file
diff --git a/app/Http/Controllers/API/SemesterApiController.php b/app/Http/Controllers/API/SemesterApiController.php
new file mode 100644
index 0000000..a493183
--- /dev/null
+++ b/app/Http/Controllers/API/SemesterApiController.php
@@ -0,0 +1,34 @@
+has('curriculum')) {
+ $query->where('version', $request->curriculum);
+ }
+
+ if ($request->has('semester')) {
+ $query->where('id', $request->semester);
+ }
+
+ $semesters = $query->paginate(20);
+
+ return SemesterResource::collection($semesters);
+ } catch (\Exception $e) {
+ Log::error('Error in SemesterApiController@index', ['error' => $e->getMessage()]);
+ return response()->json(['message' => 'An error occurred while fetching semesters'], 500);
+ }
+ }
+}
diff --git a/app/Http/Controllers/Backend/AnnouncementController.php b/app/Http/Controllers/Backend/AnnouncementController.php
index d17b2ae..62eb25b 100644
--- a/app/Http/Controllers/Backend/AnnouncementController.php
+++ b/app/Http/Controllers/Backend/AnnouncementController.php
@@ -6,6 +6,7 @@
use Illuminate\Http\Request;
use App\Domains\Announcement\Models\Announcement;
use Illuminate\Validation\Rule;
+use Illuminate\Support\Facades\Log;
class AnnouncementController extends Controller
{
@@ -17,9 +18,14 @@ class AnnouncementController extends Controller
*/
public function create()
{
- $areas = Announcement::areas();
- $types = Announcement::types();
- return view('backend.announcements.create', compact('areas', 'types'));
+ try {
+ $areas = Announcement::areas();
+ $types = Announcement::types();
+ return view('backend.announcements.create', compact('areas', 'types'));
+ } catch (\Exception $ex) {
+ Log::error('Failed to load announcement creation page', ['error' => $ex->getMessage()]);
+ return abort(500);
+ }
}
/**
@@ -46,6 +52,7 @@ public function store(Request $request)
return redirect()->route('dashboard.announcements.index', $announcement)->with('Success', 'Announcement was created !');
} catch (\Exception $ex) {
+ Log::error('Failed to create announcement', ['error' => $ex->getMessage()]);
return abort(500);
}
}
@@ -58,9 +65,14 @@ public function store(Request $request)
*/
public function edit(Announcement $announcement)
{
- $areas = Announcement::areas();
- $types = Announcement::types();
- return view('backend.announcements.edit', compact('announcement', 'areas', 'types'));
+ try {
+ $areas = Announcement::areas();
+ $types = Announcement::types();
+ return view('backend.announcements.edit', compact('announcement', 'areas', 'types'));
+ } catch (\Exception $ex) {
+ Log::error('Failed to load announcement edit page', ['announcement_id' => $announcement->id, 'error' => $ex->getMessage()]);
+ return abort(500);
+ }
}
/**
@@ -71,7 +83,7 @@ public function edit(Announcement $announcement)
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, Announcement $announcement)
- {
+ {
$data = request()->validate([
'area' => ['required', Rule::in(array_keys(Announcement::areas()))],
'type' => ['required', Rule::in(array_keys(Announcement::types()))],
@@ -86,6 +98,7 @@ public function update(Request $request, Announcement $announcement)
$announcement->update($data);
return redirect()->route('dashboard.announcements.index')->with('Success', 'Announcement was updated !');
} catch (\Exception $ex) {
+ Log::error('Failed to update announcement', ['announcement_id' => $announcement->id, 'error' => $ex->getMessage()]);
return abort(500);
}
}
@@ -114,6 +127,7 @@ public function destroy(Announcement $announcement)
$announcement->delete();
return redirect()->route('dashboard.announcements.index')->with('Success', 'Announcement was deleted !');
} catch (\Exception $ex) {
+ Log::error('Failed to delete announcement', ['announcement_id' => $announcement->id, 'error' => $ex->getMessage()]);
return abort(500);
}
}
diff --git a/app/Http/Controllers/Backend/CourseController.php b/app/Http/Controllers/Backend/CourseController.php
new file mode 100644
index 0000000..c06c69d
--- /dev/null
+++ b/app/Http/Controllers/Backend/CourseController.php
@@ -0,0 +1,149 @@
+getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Show the form for creating a new course.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function create()
+ {
+ try {
+ return view('backend.courses.create');
+ } catch (\Exception $e) {
+ Log::error('Error loading course creation page: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Store a newly created course in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\Response
+ */
+ public function store(Request $request)
+ {
+ $validatedData = $request->validate([
+ 'code' => 'required|string|max:16|unique:courses,code',
+ 'semester_id' => 'required|integer|exists:semesters,id',
+ 'academic_program' => ['required', Rule::in(array_values(Course::getAcademicPrograms()))],
+ 'version' => ['required', 'integer', Rule::in(array_keys(Course::getVersions()))],
+ 'name' => 'required|string|max:255',
+ 'credits' => 'required|integer',
+ 'type' => ['required', Rule::in(array_keys(Course::getTypes()))],
+ 'content' => 'nullable|string',
+ 'objectives' => 'nullable|json',
+ 'time_allocation' => 'nullable|json',
+ 'marks_allocation' => 'nullable|json',
+ 'ilos' => 'nullable|json',
+ 'urls' => 'nullable|json',
+ 'references' => 'nullable|json',
+ ]);
+
+ try {
+ $course = Course::create($validatedData);
+ return redirect()->route('dashboard.courses.index')->with('success', 'Course created successfully.');
+ } catch (\Exception $e) {
+ Log::error('Error creating course: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Show the form for editing the specified course.
+ *
+ * @param \App\Models\Course $course
+ * @return \Illuminate\Http\Response
+ */
+ public function edit(Course $course)
+ {
+ try {
+ return view('backend.courses.edit', compact('course'));
+ } catch (\Exception $e) {
+ Log::error('Error loading course edit page: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+ /**
+ * Update the specified course in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \App\Models\Course $course
+ * @return \Illuminate\Http\Response
+ */
+ public function update(Request $request, Course $course)
+ {
+ $validatedData = $request->validate([
+ 'code' => 'required|string|max:16|unique:courses,code,' . $course->id,
+ 'semester_id' => 'required|integer|exists:semesters,id',
+ 'academic_program' => ['required', Rule::in(array_values(Course::getAcademicPrograms()))],
+ 'version' => ['required', 'integer', Rule::in(array_keys(Course::getVersions()))],
+ 'name' => 'required|string|max:255',
+ 'credits' => 'required|integer',
+ 'type' => ['required', Rule::in(array_values(Course::getTypes()))],
+ 'type' => ['required', Rule::in(array_values(Course::getTypes()))],
+ 'content' => 'nullable|string',
+ 'objectives' => 'nullable|json',
+ 'time_allocation' => 'nullable|json',
+ 'marks_allocation' => 'nullable|json',
+ 'ilos' => 'nullable|json',
+ 'urls' => 'nullable|json',
+ 'references' => 'nullable|json',
+ ]);
+ try {
+ $course->update($validatedData);
+ return redirect()->route('dashboard.courses.index')->with('success', 'Course updated successfully.');
+ } catch (\Exception $e) {
+ Log::error('Error updating course: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Remove the specified course from storage.
+ *
+ * @param \App\Models\Course $course
+ * @return \Illuminate\Http\Response
+ */
+ public function delete(Course $course)
+ {
+ return view('backend.courses.delete', compact('course'));
+ }
+
+ public function destroy(Course $course)
+ {
+ try {
+ $course->delete();
+ return redirect()->route('dashboard.courses.index')->with('success', 'Course deleted successfully.');
+ } catch (\Exception $e) {
+ Log::error('Error in deleting course: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+}
diff --git a/app/Http/Controllers/Backend/DashboardController.php b/app/Http/Controllers/Backend/DashboardController.php
index 3d00896..ad63d4f 100644
--- a/app/Http/Controllers/Backend/DashboardController.php
+++ b/app/Http/Controllers/Backend/DashboardController.php
@@ -2,6 +2,8 @@
namespace App\Http\Controllers\Backend;
+use Illuminate\Support\Facades\Log;
+
/**
* Class DashboardController.
*/
@@ -12,6 +14,11 @@ class DashboardController
*/
public function index()
{
- return view('backend.dashboard');
+ try{
+ return view('backend.dashboard');
+ }catch (\Exception $ex) {
+ Log::error('Failed to load dashboard', ['error' => $ex->getMessage()]);
+ return abort(500);
+ }
}
}
diff --git a/app/Http/Controllers/Backend/EventController.php b/app/Http/Controllers/Backend/EventController.php
index 54f2e0d..314ec10 100644
--- a/app/Http/Controllers/Backend/EventController.php
+++ b/app/Http/Controllers/Backend/EventController.php
@@ -20,7 +20,12 @@ class EventController extends Controller
*/
public function create()
{
- return view('backend.event.create');
+ try {
+ return view('backend.event.create');
+ } catch (\Exception $ex) {
+ Log::error('Failed to load event creation page', ['error' => $ex->getMessage()]);
+ return abort(500);
+ }
}
/**
@@ -31,6 +36,7 @@ public function create()
*/
public function store(Request $request)
{
+
$data = request()->validate([
'title' => 'string|required',
'url' => ['required', 'unique:events'],
@@ -43,6 +49,7 @@ public function store(Request $request)
'end_at' => 'nullable|date_format:Y-m-d\\TH:i',
'location' => 'string|required',
]);
+
if ($request->hasFile('image')) {
$data['image'] = $this->uploadThumb(null, $request->image, "events");
}
@@ -56,11 +63,10 @@ public function store(Request $request)
return redirect()->route('dashboard.event.index', $event)->with('Success', 'Event was created !');
} catch (\Exception $ex) {
- Log::error($ex->getMessage());
+ Log::error('Failed to create event', ['error' => $ex->getMessage()]);
return abort(500);
}
}
-
/**
* Show the form for editing the specified resource.
*
@@ -69,7 +75,12 @@ public function store(Request $request)
*/
public function edit(Event $event)
{
- return view('backend.event.edit', compact('event'));
+ try {
+ return view('backend.event.edit', compact('event'));
+ } catch (\Exception $ex) {
+ Log::error('Failed to edit event', ['error' => $ex->getMessage()]);
+ return abort(500);
+ };
}
/**
@@ -81,12 +92,12 @@ public function edit(Event $event)
*/
public function update(Request $request, Event $event)
{
+
$data = request()->validate([
'title' => ['required'],
- 'url' =>
- ['required', Rule::unique('news')->ignore($event->id)],
+ 'url' => ['required', Rule::unique('events')->ignore($event->id)],
'published_at' => 'required|date_format:Y-m-d',
- 'description' => 'string',
+ 'description' => 'string|required',
'enabled' => 'nullable',
'link_url' => 'nullable|url',
'link_caption' => 'nullable|string',
@@ -94,6 +105,8 @@ public function update(Request $request, Event $event)
'end_at' => 'nullable|date_format:Y-m-d\\TH:i',
'location' => 'string|required',
]);
+
+
if ($request->hasFile('image')) {
$data['image'] = $this->uploadThumb($event->image, $request->image, "events");
} else {
@@ -109,10 +122,12 @@ public function update(Request $request, Event $event)
return redirect()->route('dashboard.event.index')->with('Success', 'Event was updated !');
} catch (\Exception $ex) {
+ Log::error('Failed to update event', ['event_id' => $event->id, 'error' => $ex->getMessage()]);
return abort(500);
}
}
+
/**
* Confirm to delete the specified resource from storage.
*
@@ -136,23 +151,21 @@ public function destroy(Event $event)
$event->delete();
return redirect()->route('dashboard.event.index')->with('Success', 'Event was deleted !');
} catch (\Exception $ex) {
+ Log::error('Failed to delete event', ['event_id' => $event->id, 'error' => $ex->getMessage()]);
return abort(500);
}
}
-
// Private function to handle deleting images
private function deleteThumb($currentURL)
{
if ($currentURL != null) {
$oldImage = public_path($currentURL);
- if (File::exists($oldImage)) unlink($oldImage);
+ if (File::exists($oldImage)) unlink($oldImage);
}
}
-
// Private function to handle uploading images
private function uploadThumb($currentURL, $newImage, $folder)
{
- // Delete the existing image
$this->deleteThumb($currentURL);
$imageName = time() . '.' . $newImage->extension();
@@ -163,4 +176,4 @@ private function uploadThumb($currentURL, $newImage, $folder)
return $imageName;
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/Backend/NewsController.php b/app/Http/Controllers/Backend/NewsController.php
index 76f4501..6b6db16 100644
--- a/app/Http/Controllers/Backend/NewsController.php
+++ b/app/Http/Controllers/Backend/NewsController.php
@@ -9,10 +9,10 @@
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\File;
use Illuminate\Validation\Rule;
+use Illuminate\Support\Facades\Log;
class NewsController extends Controller
{
-
/**
* Show the form for creating a new resource.
*
@@ -20,9 +20,13 @@ class NewsController extends Controller
*/
public function create()
{
- return view('backend.news.create');
+ try{
+ return view('backend.news.create');
+ }catch (\Exception $ex) {
+ Log::error('Failed to load news creation page', ['error' => $ex->getMessage()]);
+ return abort(500);
+ }
}
-
/**
* Store a newly created resource in storage.
*
@@ -41,6 +45,7 @@ public function store(Request $request)
'link_url' => 'nullable|url',
'link_caption' => 'nullable|string',
]);
+
if ($request->hasFile('image')) {
$data['image'] = $this->uploadThumb(null, $request->image, "news");
}
@@ -54,10 +59,10 @@ public function store(Request $request)
return redirect()->route('dashboard.news.index', $news)->with('Success', 'News was created !');
} catch (\Exception $ex) {
+ Log::error('Failed to create news', ['error' => $ex->getMessage()]);
return abort(500);
}
}
-
/**
* Show the form for editing the specified resource.
*
@@ -66,9 +71,13 @@ public function store(Request $request)
*/
public function edit(News $news)
{
- return view('backend.news.edit', compact('news'));
+ try{
+ return view('backend.news.edit', ['news' => $news]);
+ }catch (\Exception $ex) {
+ Log::error('Failed to load news edit page', ['error' => $ex->getMessage()]);
+ return abort(500);
+ }
}
-
/**
* Update the specified resource in storage.
*
@@ -78,6 +87,7 @@ public function edit(News $news)
*/
public function update(Request $request, News $news)
{
+
$data = request()->validate([
'title' => ['required'],
'url' => ['required', Rule::unique('news')->ignore($news->id)],
@@ -87,6 +97,7 @@ public function update(Request $request, News $news)
'link_url' => 'nullable|url',
'link_caption' => 'nullable|string',
]);
+
if ($request->hasFile('image')) {
$data['image'] = $this->uploadThumb($news->image, $request->image, "news");
} else {
@@ -102,11 +113,11 @@ public function update(Request $request, News $news)
return redirect()->route('dashboard.news.index')->with('Success', 'News was updated !');
} catch (\Exception $ex) {
+ Log::error('Failed to update news', ['news_id' => $news->id, 'error' => $ex->getMessage()]);
return abort(500);
}
}
-
- /**
+ /**
* Confirm to delete the specified resource from storage.
*
* @param \App\Models\News $news
@@ -132,31 +143,34 @@ public function destroy(News $news)
$news->delete();
return redirect()->route('dashboard.news.index')->with('Success', 'News was deleted !');
} catch (\Exception $ex) {
+ Log::error('Failed to delete news', ['news_id' => $news->id, 'error' => $ex->getMessage()]);
return abort(500);
}
}
+ // Private function to handle deleting images
+ private function deleteThumb($currentURL)
+ {
+ if ($currentURL != null && $currentURL != config('constants.frontend.dummy_thumb')) {
+ $oldImage = public_path($currentURL);
+ if (File::exists($oldImage)) {
+ unlink($oldImage);
+ }
+ }
+ }
+
+ // Private function to handle uploading images
+ private function uploadThumb($currentURL, $newImage, $folder)
+ {
+ // Delete the existing image
+ $this->deleteThumb($currentURL);
+
+ $imageName = time() . '.' . $newImage->extension();
+ $newImage->move(public_path('img/' . $folder), $imageName);
+ $imagePath = "/img/$folder/" . $imageName;
+ $image = Image::make(public_path($imagePath));
+ $image->save();
+
+ return $imageName;
+ }
+}
- // Private function to handle deleting images
- private function deleteThumb($currentURL)
- {
- if ($currentURL != null && $currentURL != config('constants.frontend.dummy_thumb')) {
- $oldImage = public_path($currentURL);
- if (File::exists($oldImage)) unlink($oldImage);
- }
- }
-
- // Private function to handle uploading images
- private function uploadThumb($currentURL, $newImage, $folder)
- {
- // Delete the existing image
- $this->deleteThumb($currentURL);
-
- $imageName = time() . '.' . $newImage->extension();
- $newImage->move(public_path('img/' . $folder), $imageName);
- $imagePath = "/img/$folder/" . $imageName;
- $image = Image::make(public_path($imagePath));
- $image->save();
-
- return $imageName;
- }
-}
\ No newline at end of file
diff --git a/app/Http/Controllers/Backend/SemesterController.php b/app/Http/Controllers/Backend/SemesterController.php
new file mode 100644
index 0000000..9b76503
--- /dev/null
+++ b/app/Http/Controllers/Backend/SemesterController.php
@@ -0,0 +1,160 @@
+getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Show the form for creating a new semester.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function create()
+ {
+ try {
+ return view('backend.semesters.create');
+ } catch (\Exception $e) {
+ Log::error('Error loading semester creation page: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Store a newly created semester in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\Response
+ */
+ public function store(Request $request)
+ {
+ $validatedData = $request->validate([
+ 'title' => 'required|string|max:255',
+ 'version' => ['required', 'integer', Rule::in(array_keys(Semester::getVersions()))],
+ 'academic_program' => ['required', Rule::in(array_keys(Semester::getAcademicPrograms()))],
+ 'description' => 'nullable|string',
+ 'url' => [
+ 'required',
+ 'string',
+ 'unique:semesters',
+ ],
+ ]);
+
+ try {
+ $semester = new Semester($validatedData);
+ $semester->created_by = Auth::user()->id;
+ $semester->updated_by = Auth::user()->id;
+ $semester->url = urlencode(str_replace(" ", "-", $request->url));
+ $semester->save();
+
+ return redirect()->route('dashboard.semesters.index')->with('success', 'Semester created successfully.');
+ } catch (\Exception $e) {
+ Log::error('Error in storing semester: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Show the form for editing the specified semester.
+ *
+ * @param \App\Domains\AcademicProgram\Semester\Models\Semester $semester
+ * @return \Illuminate\Http\Response
+ */
+ public function edit(Semester $semester)
+ {
+ try {
+ return view('backend.semesters.edit', compact('semester'));
+ } catch (\Exception $e) {
+ Log::error('Error loading semester edit page: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Update the specified semester in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \App\Domains\AcademicProgram\Semester\Models\Semester $semester
+ * @return \Illuminate\Http\Response
+ */
+ public function update(Request $request, Semester $semester)
+ {
+ $validatedData = $request->validate([
+ 'title' => 'required|string|max:255',
+ 'version' => ['required', 'integer', Rule::in(array_keys(Semester::getVersions()))],
+ 'academic_program' => ['required', Rule::in(array_keys(Semester::getAcademicPrograms()))],
+ 'description' => 'nullable|string',
+ 'url' => [
+ 'required',
+ 'string',
+ Rule::unique('semesters', 'url')->ignore($semester->id),
+ ],
+ ]);
+
+ try {
+ $semester->update($validatedData);
+ $semester->updated_by = Auth::user()->id;
+ $semester->url = urlencode(str_replace(" ", "-", $request->url));
+ $semester->save();
+
+ return redirect()->route('dashboard.semesters.index')->with('success', 'Semester updated successfully.');
+ } catch (\Exception $e) {
+ Log::error('Error in updating semester: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+
+ /**
+ * Remove the specified semester from storage.
+ *
+ * @param \App\Domains\AcademicProgram\Semester\Models\Semester $semester
+ * @return \Illuminate\Http\Response
+ */
+ public function delete(Semester $semester)
+ {
+ $courses = Course::where('semester_id', $semester->id)->get();
+ return view('backend.semesters.delete', compact('semester', 'courses'));
+ }
+
+
+ public function destroy(Semester $semester)
+ {
+ $courses = Course::where('semester_id', $semester->id)->get();
+
+ if ($courses->count() > 0) {
+ return redirect()->route('dashboard.semesters.index')
+ ->withErrors('Can not delete the semester as it already has associated courses. Please reassign or delete those courses first.');
+ }
+
+ try {
+ $semester->delete();
+ return redirect()->route('dashboard.semesters.index')->with('success', 'Semester deleted successfully.');
+ } catch (\Exception $e) {
+ Log::error('Error in deleting semester: ' . $e->getMessage());
+ return abort(500);
+ }
+ }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 43b5aff..081a7e1 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -79,7 +79,7 @@ class Kernel extends HttpKernel
'is_admin' => \App\Domains\Auth\Http\Middleware\AdminCheck::class,
'is_super_admin' => \App\Domains\Auth\Http\Middleware\SuperAdminCheck::class,
'is_user' => \App\Domains\Auth\Http\Middleware\UserCheck::class,
- 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+ 'password.confirm' => \App\Http\Middleware\CustomRequirePassword::class, // \Illuminate\Auth\Middleware\RequirePassword::class,
'password.expires' => \App\Domains\Auth\Http\Middleware\PasswordExpires::class,
'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
@@ -105,4 +105,4 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
-}
+}
\ No newline at end of file
diff --git a/app/Http/Livewire/Backend/CourseTable.php b/app/Http/Livewire/Backend/CourseTable.php
new file mode 100644
index 0000000..2188eff
--- /dev/null
+++ b/app/Http/Livewire/Backend/CourseTable.php
@@ -0,0 +1,83 @@
+searchable()->sortable(),
+ Column::make("Name", "name")
+ ->searchable()->sortable(),
+ Column::make("Semester", "semester")
+ ->searchable(),
+ Column::make("Academic Program", "academic_program")
+ ->sortable(),
+ Column::make("Type", "type")
+ ->sortable(),
+ Column::make("Curriculum", "version")
+ ->sortable(),
+ Column::make("Credits", "credits")
+ ->searchable(),
+ Column::make("Updated by", "created_by")
+ ->sortable(),
+ Column::make("Updated At", "updated_at")
+ ->sortable(),
+ Column::make("Actions")
+ ];
+ }
+
+ public function query(): Builder
+ {
+ return Course::query()
+ ->when($this->getFilter('academic_program'), fn($query, $type) => $query->where('academic_program', $type))
+ ->when($this->getFilter('semester_id'), fn($query, $type) => $query->where('semester_id', $type))
+ ->when($this->getFilter('version'), fn($query, $version) => $query->where('version', $version));;
+ }
+
+ public function filters(): array
+ {
+ $academicProgramOptions = ["" => "Any"];
+ foreach (Course::getAcademicPrograms() as $key => $value) {
+ $academicProgramOptions[$key] = $value;
+ }
+
+ $typeOptions = ["" => "Any"];
+ foreach (Course::getTypes() as $key => $value) {
+ $typeOptions[$key] = $value;
+ }
+
+ $versionOptions = ["" => "Any"];
+ foreach (Course::getVersions() as $key => $value) {
+ $versionOptions[$key] = $value;
+ }
+
+ return [
+ 'academic_program' => Filter::make('Academic Program')
+ ->select($academicProgramOptions),
+ 'type' => Filter::make('Type')
+ ->select($typeOptions),
+ 'version' => Filter::make('Curriculum')
+ ->select($versionOptions),
+ ];
+ }
+
+ public function rowView(): string
+ {
+ return 'backend.courses.index-table-row';
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Livewire/Backend/CreateCourses.php b/app/Http/Livewire/Backend/CreateCourses.php
new file mode 100644
index 0000000..a2c18f7
--- /dev/null
+++ b/app/Http/Livewire/Backend/CreateCourses.php
@@ -0,0 +1,304 @@
+ 'required|string',
+ 'semester' => 'required|string',
+ 'version' => ['required', 'string', Rule::in(array_keys(Course::getVersions()))],
+ 'type' => ['required', 'string', Rule::in(array_keys(Course::getTypes()))],
+ 'code' => 'required|string|unique:courses,code',
+ 'name' => 'required|string|max:255',
+ 'credits' => 'required|integer|min:1|max:18',
+ 'faq_page' => 'nullable|url',
+ 'content' => 'nullable|string',
+ 'time_allocation.lecture' => 'nullable|integer|min:0',
+ 'time_allocation.tutorial' => 'nullable|integer|min:0',
+ 'time_allocation.practical' => 'nullable|integer|min:0',
+ 'time_allocation.assignment' => 'nullable|integer|min:0',
+ 'marks_allocation.practicals' => 'nullable|integer|min:0|max:100',
+ 'marks_allocation.project' => 'nullable|integer|min:0|max:100',
+ 'marks_allocation.mid_exam' => 'nullable|integer|min:0|max:100',
+ 'marks_allocation.end_exam' => 'nullable|integer|min:0|max:100',
+ 'modules' => 'nullable|array',
+ 'modules.*.name' => 'required|string|max:255',
+ 'modules.*.description' => 'nullable|string',
+ 'modules.*.time_allocation.lectures' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.tutorials' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.practicals' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.assignments' => 'nullable|integer|min:0',
+ ];
+ }
+
+ public function messages()
+ {
+ return [
+ 'academicProgram.required' => 'Please select an academic program.',
+ 'semester.required' => 'Please select a semester.',
+ 'version.required' => 'Please provide a curriculum.',
+ 'type.required' => 'Please select a course type.',
+ 'type.in' => 'The course type must be Core, GE, or TE.',
+ 'code.required' => 'Please provide a course code.',
+ 'code.unique' => 'This course code is already in use.',
+ 'name.required' => 'Please provide a course name.',
+ 'credits.required' => 'Please specify the number of credits.',
+ 'credits.min' => 'The course must have at least 1 credit.',
+ 'credits.max' => 'The course cannot have more than 18 credits.',
+ 'modules.*.name.required' => 'Each module must have a name.',
+ 'modules.*.description.required' => 'Each module must have a description.',
+ 'modules.*.description.min' => 'Module descriptions should be at least 10 characters long.',
+ ];
+ }
+
+ protected function validateCurrentStep()
+ {
+ switch ($this->formStep) {
+ case 1:
+ $validationRules = [
+ 'academicProgram' => 'required|string',
+ 'semester' => 'required|string',
+ 'version' => ['required', 'string', Rule::in(array_keys(Course::getVersions()))],
+ 'type' => ['required', 'string', Rule::in(array_keys(Course::getTypes()))],
+ 'code' => 'required|string|unique:courses,code',
+ 'name' => 'required|string|max:255',
+ 'credits' => 'required|integer|min:1|max:18',
+ 'faq_page' => 'nullable|url',
+ 'content' => 'nullable|string',
+ ];
+
+ foreach (Course::getTimeAllocation() as $key => $value) {
+ $validationRules["time_allocation.$key"] = 'nullable|integer|min:0';
+ }
+ foreach (Course::getMarksAllocation() as $key => $value) {
+ $validationRules["marks_allocation.$key"] = 'nullable|integer|min:0|max:100';
+ }
+
+ $this->validate($validationRules);
+ $this->validateMarksAllocation();
+ if ($this->getErrorBag()->has('marks_allocation.total')) {
+ return;
+ }
+ break;
+
+ case 3:
+ $validationRules = [
+ 'modules' => 'nullable|array',
+ 'modules.*.name' => 'required|string|min:3|max:255',
+ 'modules.*.description' => 'required|string',
+ ];
+
+ foreach (Course::getTimeAllocation() as $key => $value) {
+ $validationRules["modules.*.time_allocation.$key"] = 'nullable|integer|min:0';
+ }
+
+ $this->validate($validationRules);
+ break;
+ }
+ }
+
+ protected function validateMarksAllocation()
+ {
+ $totalMarks = 0;
+ $hasValue = false;
+
+ foreach ($this->marks_allocation as $key => $value) {
+ if (!empty($value)) {
+ $hasValue = true;
+ $totalMarks += (int) $value;
+ }
+ }
+
+ if ($hasValue && $totalMarks != 100) {
+ $this->addError('marks_allocation.total', 'The total of marks allocation must be 100.');
+ }
+ }
+
+ public function updated($propertyName)
+ {
+ $this->validateOnly($propertyName);
+ }
+
+ protected $listeners = ['itemsUpdated' => 'updateItems'];
+
+ public function mount()
+ {
+ $this->academicProgramsList = Course::getAcademicPrograms();
+ $this->time_allocation = Course::getTimeAllocation();
+ $this->marks_allocation = Course::getMarksAllocation();
+ $this->module_time_allocation = Course::getTimeAllocation();
+ $this->ilos = Course::getILOTemplate();
+ }
+
+ public function updateItems($type, $newItems)
+ {
+ if ($type == 'references') {
+ $this->$type = $newItems;
+ } else {
+ $this->ilos[$type] = $newItems;
+ }
+ }
+
+ public function next()
+ {
+ $this->validateCurrentStep();
+ if ($this->getErrorBag()->has('marks_allocation.total')) {
+ return; // Do not proceed to the next step if the marks total is invalid
+ }
+ $this->formStep++;
+ }
+
+ public function previous()
+ {
+ $this->formStep--;
+ }
+
+ public function submit()
+ {
+ \Log::info("Submit method called");
+ try {
+ $this->validate();
+ $this->storeCourse();
+ return redirect()->route('dashboard.courses.index')->with('Success', 'Course created successfully.');
+ } catch (\Exception $e) {
+ \Log::error("Error in submit method: " . $e->getMessage());
+ session()->flash('error', 'There was an error creating the course: ' . $e->getMessage());
+ }
+ $this->resetForm();
+ }
+
+ public function updatedAcademicProgram()
+ {
+ $this->updateSemestersList();
+ }
+
+ public function updatedVersion()
+ {
+ $this->updateSemestersList();
+ }
+
+ public function updateSemestersList()
+ {
+ if ($this->academicProgram && $this->version) {
+ $this->semestersList = Semester::where('academic_program', $this->academicProgram)
+ ->where('version', $this->version)
+ ->pluck('title', 'id')
+ ->toArray();
+ } else {
+ $this->semestersList = [];
+ }
+ }
+
+
+ protected function storeCourse()
+ {
+ try {
+ \DB::beginTransaction();
+ $course = Course::create([
+ 'academic_program' => $this->academicProgram,
+ 'semester_id' => (int)$this->semester,
+ 'version' => (int)$this->version,
+ 'type' => $this->type,
+ 'code' => $this->code,
+ 'name' => $this->name,
+ 'credits' => (int)$this->credits,
+ 'faq_page' => $this->faq_page,
+ 'content' => $this->content,
+ 'time_allocation' => json_encode($this->time_allocation),
+ 'marks_allocation' => json_encode($this->marks_allocation),
+ 'objectives' => $this->objectives,
+ 'ilos' => json_encode($this->ilos),
+ 'references' => json_encode($this->references),
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id()
+ ]);
+
+ if (empty($this->modules)) {
+ \Log::warning("No modules to create");
+ } else {
+ foreach ($this->modules as $module) {
+ CourseModule::create([
+ 'course_id' => $course->id,
+ 'topic' => $module['name'],
+ 'description' => $module['description'],
+ 'time_allocation' => json_encode($module['time_allocation']),
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+ }
+ }
+
+ \DB::commit();
+ } catch (\Exception $e) {
+ \DB::rollBack();
+ \Log::error("Error in storeCourse method: " . $e->getMessage());
+ throw $e;
+ }
+ }
+
+
+ protected function resetForm()
+ {
+ $this->formStep = 1;
+ $this->academicProgram = '';
+ $this->semester = '';
+ $this->version = '';
+ $this->type = '';
+ $this->code = '';
+ $this->name = '';
+ $this->credits = 0;
+ $this->faq_page = '';
+ $this->content = '';
+ $this->time_allocation = Course::getTimeAllocation();
+ $this->marks_allocation = Course::getMarksAllocation();
+ $this->module_time_allocation = Course::getTimeAllocation();
+ $this->objectives = '';
+ $this->ilos = Course::getILOTemplate();
+ $this->references = [];
+ $this->modules = [];
+ }
+
+ public function render()
+ {
+ return view('livewire.backend.create-courses');
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Livewire/Backend/EditCourses.php b/app/Http/Livewire/Backend/EditCourses.php
new file mode 100644
index 0000000..ab54e3e
--- /dev/null
+++ b/app/Http/Livewire/Backend/EditCourses.php
@@ -0,0 +1,328 @@
+ [],
+ 'skills' => [],
+ 'attitudes' => [],
+ ];
+
+ // 3rd form step
+ public $references = [];
+ public $modules = [];
+
+ public function rules()
+ {
+
+ $validationRules = [
+ 'academicProgram' => 'required|string',
+ 'semester' => 'required|int',
+ 'version' => ['required', 'string', Rule::in(array_keys(Course::getVersions()))],
+ 'type' => ['required', 'string', Rule::in(array_keys(Course::getTypes()))],
+ 'code' => 'required|string',
+ 'name' => 'required|string|max:255',
+ 'credits' => 'required|integer|min:1|max:18',
+ 'faq_page' => 'nullable|url',
+ 'content' => 'nullable|string',
+ 'modules' => 'nullable|array',
+ 'modules.*.name' => 'required|string|max:255',
+ 'modules.*.description' => 'nullable|string',
+ 'modules.*.time_allocation.lectures' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.tutorials' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.practicals' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.assignments' => 'nullable|integer|min:0',
+ ];
+
+ foreach (Course::getTimeAllocation() as $key => $value) {
+ $validationRules["time_allocation.$key"] = 'nullable|integer|min:0';
+ $validationRules["modules.*.time_allocation.$key"] = 'nullable|integer|min:0';
+ }
+ foreach (Course::getMarksAllocation() as $key => $value) {
+ $validationRules["marks_allocation.$key"] = 'nullable|integer|min:0|max:100';
+ }
+
+ return $validationRules;
+ }
+
+ public function messages()
+ {
+ return [
+ 'academicProgram.required' => 'Please select an academic program.',
+ 'semester.required' => 'Please select a semester.',
+ 'version.required' => 'Please provide a curriculum.',
+ 'type.required' => 'Please select a course type.',
+ 'type.in' => 'The course type must be Core, GE, or TE.',
+ 'code.required' => 'Please provide a course code.',
+ 'name.required' => 'Please provide a course name.',
+ 'credits.required' => 'Please specify the number of credits.',
+ 'credits.min' => 'The course must have at least 1 credit.',
+ 'credits.max' => 'The course cannot have more than 18 credits.',
+ 'modules.*.name.required' => 'Each module must have a name.',
+ 'modules.*.description.required' => 'Each module must have a description.',
+ 'modules.*.description.min' => 'Module descriptions should be at least 10 characters long.',
+ ];
+ }
+
+ protected function validateCurrentStep()
+ {
+ switch ($this->formStep) {
+ case 1:
+ $this->validate($this->rules());
+ $this->validateMarksAllocation();
+ break;
+
+ case 3:
+ $this->validate([
+ 'modules' => 'nullable|array',
+ 'modules.*.name' => 'nullable|string|max:255',
+ 'modules.*.description' => 'nullable|string',
+ 'modules.*.time_allocation.lectures' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.tutorials' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.practicals' => 'nullable|integer|min:0',
+ 'modules.*.time_allocation.assignments' => 'nullable|integer|min:0',
+ ]);
+ break;
+ }
+ }
+
+ protected function validateMarksAllocation()
+ {
+ $totalMarks = 0;
+ $hasValue = false;
+
+ foreach ($this->marks_allocation as $key => $value) {
+ if (!empty($value)) {
+ $hasValue = true;
+ $totalMarks += (int) $value;
+ }
+ }
+
+ if ($hasValue && $totalMarks != 100) {
+ $this->addError('marks_allocation.total', 'The total of marks allocation must be 100.');
+ }
+ }
+
+ public function updated($propertyName)
+ {
+ $this->canUpdate = false;
+ $this->validateCurrentStep();
+ if ($this->getErrorBag()->has('marks_allocation.total')) {
+ return;
+ }
+ $this->canUpdate = true;
+ }
+
+ protected $listeners = ['itemsUpdated' => 'updateItems'];
+
+ public function mount(Course $course)
+ {
+ $this->academicProgramsList = Course::getAcademicPrograms();
+ $this->time_allocation = Course::getTimeAllocation();
+ $this->module_time_allocation = Course::getTimeAllocation();
+ $this->marks_allocation = Course::getMarksAllocation();
+ $this->course = $course;
+
+ // Populate form fields with existing course data
+ $this->academicProgram = $course->academic_program;
+ $this->semester = $course->semester_id;
+ $this->version = $course->version;
+ $this->type = $course->type;
+ $this->code = $course->code;
+ $this->name = $course->name;
+ $this->credits = $course->credits;
+ $this->faq_page = $course->faq_page;
+ $this->content = $course->content;
+ $this->time_allocation = array_merge(Course::getTimeAllocation(), json_decode($course->time_allocation, true));
+ $this->marks_allocation = array_merge(Course::getMarksAllocation(), json_decode($course->marks_allocation, true));
+ $this->objectives = $course->objectives;
+ $this->ilos = array_merge(Course::getILOTemplate(), json_decode($course->ilos, true) ?? []);
+ $this->references = json_decode($course->references, true) ?? [];
+
+ // Load modules
+ $this->modules = $course->modules()->get()->map(function ($module, $index) {
+ return [
+ 'id' => $index + 1, // or use $module->id if available
+ 'name' => $module->topic,
+ 'description' => $module->description,
+ 'time_allocation' => array_merge(Course::getTimeAllocation(), json_decode($module->time_allocation, true))
+ ];
+ })->toArray();
+ // Update semesters list based on academic program and version
+ $this->updateSemestersList();
+ }
+
+ public function updateItems($type, $newItems)
+ {
+ if ($type == 'references') {
+ $this->$type = $newItems;
+ } else {
+ $this->ilos[$type] = $newItems;
+ }
+
+ $this->emit('refreshItems' . ucfirst($type), $newItems);
+ }
+
+
+ public function next()
+ {
+ $this->validateCurrentStep();
+ if ($this->getErrorBag()->has('marks_allocation.total')) {
+ return; // Do not proceed to the next step if the marks total is invalid
+ }
+ $this->formStep++;
+ }
+
+ public function previous()
+ {
+ $this->formStep--;
+ }
+
+ public function update()
+ {
+ \Log::info("update method called");
+ try {
+
+ $this->validateCurrentStep();
+ $this->updateCourse();
+ return redirect()->route('dashboard.courses.index')->with('Success', 'Course updated successfully.');
+ } catch (\Exception $e) {
+ \Log::error("Error in update method: " . $e->getMessage());
+ session()->flash('error', 'There was an error updating the course: ' . $e->getMessage());
+ }
+ $this->resetForm();
+ }
+
+ public function updatedAcademicProgram()
+ {
+ $this->updateSemestersList();
+ }
+
+ public function updatedVersion()
+ {
+ $this->updateSemestersList();
+ }
+
+ public function updateSemestersList()
+ {
+ if ($this->academicProgram && $this->version) {
+ $this->semestersList = Semester::where('academic_program', $this->academicProgram)
+ ->where('version', $this->version)
+ ->pluck('title', 'id')
+ ->toArray();
+ } else {
+ $this->semestersList = [];
+ }
+ }
+
+ protected function updateCourse()
+ {
+ \Log::info("updateCourse method called");
+
+ try {
+ \DB::beginTransaction();
+
+ $course = Course::where('id', $this->course->id)->firstOrFail();
+
+ $course->update([
+ 'academic_program' => $this->academicProgram,
+ 'semester_id' => (int)$this->semester,
+ 'version' => (int)$this->version,
+ 'type' => $this->type,
+ 'code' => $this->code,
+ 'name' => $this->name,
+ 'credits' => (int)$this->credits,
+ 'faq_page' => $this->faq_page,
+ 'content' => $this->content,
+ 'time_allocation' => json_encode($this->time_allocation),
+ 'marks_allocation' => json_encode($this->marks_allocation),
+ 'objectives' => $this->objectives,
+ 'ilos' => json_encode($this->ilos),
+ 'references' => json_encode($this->references),
+ 'updated_by' => auth()->id()
+ ]);
+
+ \Log::info("Course updated with ID: " . $course->id);
+
+ $course->modules()->delete(); // Delete existing modules before adding new ones
+
+ if (!empty($this->modules)) {
+ foreach ($this->modules as $module) {
+ $createdModule = CourseModule::create([
+ 'course_id' => $course->id,
+ 'topic' => $module['name'],
+ 'description' => $module['description'],
+ 'time_allocation' => json_encode($module['time_allocation']),
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+ \Log::info("Created module with ID: " . $createdModule->id);
+ }
+ }
+
+ \DB::commit();
+ \Log::info("updateCourse method completed successfully");
+ } catch (\Exception $e) {
+ \DB::rollBack();
+ \Log::error("Error in updateCourse method: " . $e->getMessage());
+ throw $e;
+ }
+ }
+
+ protected function resetForm()
+ {
+ $this->formStep = 1;
+ $this->academicProgram = '';
+ $this->semester = '';
+ $this->version = '';
+ $this->type = '';
+ $this->code = '';
+ $this->name = '';
+ $this->credits = null;
+ $this->faq_page = '';
+ $this->content = '';
+ $this->time_allocation = Course::getTimeAllocation();
+ $this->marks_allocation = Course::getMarksAllocation();
+ $this->module_time_allocation = Course::getTimeAllocation();
+ $this->objectives = '';
+ $this->ilos = Course::getILOTemplate();
+ $this->references = [];
+ $this->modules = [];
+ }
+
+ public function render()
+ {
+ return view('livewire.backend.edit-courses');
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Livewire/Backend/EventsTable.php b/app/Http/Livewire/Backend/EventsTable.php
index 70da63d..de81950 100644
--- a/app/Http/Livewire/Backend/EventsTable.php
+++ b/app/Http/Livewire/Backend/EventsTable.php
@@ -54,7 +54,7 @@ public function query(): Builder
} elseif ($enabled === 0) {
$query->where('enabled', false);
}
- });
+ })->orderBy('published_at', 'desc');
}
public function toggleEnable($eventId)
{
diff --git a/app/Http/Livewire/Backend/ItemAdder.php b/app/Http/Livewire/Backend/ItemAdder.php
new file mode 100644
index 0000000..bea59d7
--- /dev/null
+++ b/app/Http/Livewire/Backend/ItemAdder.php
@@ -0,0 +1,22 @@
+items = $items;
+ $this->type = $type;
+ }
+
+ public function render()
+ {
+ return view('livewire.backend.item-adder');
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Livewire/Backend/NewsTable.php b/app/Http/Livewire/Backend/NewsTable.php
index 26774fd..08a30e0 100644
--- a/app/Http/Livewire/Backend/NewsTable.php
+++ b/app/Http/Livewire/Backend/NewsTable.php
@@ -31,7 +31,7 @@ public function columns(): array
->format(function (News $news) {
return view('backend.news.enabled-toggle', ['news' => $news]);
}),
- Column::make("Author")
+ Column::make("Author", "user.name")
->sortable()
->searchable(),
Column::make("Published at", "published_at")
@@ -52,7 +52,7 @@ public function query(): Builder
} elseif ($enabled === 0) {
$query->where('enabled', false);
}
- });
+ })->orderBy('published_at', 'desc');;
}
diff --git a/app/Http/Livewire/Backend/SemesterTable.php b/app/Http/Livewire/Backend/SemesterTable.php
new file mode 100644
index 0000000..4422099
--- /dev/null
+++ b/app/Http/Livewire/Backend/SemesterTable.php
@@ -0,0 +1,71 @@
+searchable()->sortable(),
+ Column::make("Curriculum", "version")
+ ->sortable(),
+ Column::make("Academic Program", "academic_program")
+ ->sortable(),
+ Column::make("Description", "description")
+ ->searchable(),
+ Column::make("URL", "url")
+ ->searchable(),
+ Column::make("Updated by", "created_by")
+ ->sortable(),
+ Column::make("Updated At", "updated_at")
+ ->sortable(),
+ Column::make("Actions")
+ ];
+ }
+
+ public function query(): Builder
+ {
+ return Semester::query()
+ ->when($this->getFilter('academic_program'), fn($query, $type) => $query->where('academic_program', $type))
+ ->when($this->getFilter('version'), fn($query, $version) => $query->where('version', $version));
+ }
+
+ public function filters(): array
+ {
+ $academicProgramOptions = ["" => "Any"];
+ foreach (Semester::getAcademicPrograms() as $key => $value) {
+ $academicProgramOptions[$key] = $value;
+ }
+ $versionOptions = ["" => "Any"];
+ foreach (Semester::getVersions() as $key => $value) {
+ $versionOptions[$key] = $value;
+ }
+
+
+ return [
+ 'academic_program' => Filter::make('Academic Program')
+ ->select($academicProgramOptions),
+ 'version' => Filter::make('Curriculum')
+ ->select($versionOptions),
+ ];
+ }
+
+ public function rowView(): string
+ {
+ return 'backend.semesters.index-table-row';
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Middleware/CustomRequirePassword.php b/app/Http/Middleware/CustomRequirePassword.php
new file mode 100644
index 0000000..05daee0
--- /dev/null
+++ b/app/Http/Middleware/CustomRequirePassword.php
@@ -0,0 +1,39 @@
+environment('testing')) {
+ return $next($request);
+ }
+
+ // Should ask only if user has password = not signed in with providers
+ $hasPassword = $this->hasPassword($request);
+ if ($hasPassword) {
+ return parent::handle($request, $next, $redirectToRoute);
+ }
+ return $next($request);
+ }
+
+ protected function hasPassword($request)
+ {
+ return (!in_array($request->user()->provider, ['google']));
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Resources/CourseResource.php b/app/Http/Resources/CourseResource.php
new file mode 100644
index 0000000..d29ef12
--- /dev/null
+++ b/app/Http/Resources/CourseResource.php
@@ -0,0 +1,39 @@
+ $this->id,
+ 'code' => $this->code,
+ 'name' => $this->name,
+ 'description' => $this->content,
+ 'credits' => $this->credits,
+ 'type' => $this->type,
+ 'semester_id' => $this->semester_id,
+ 'academic_program' => $this->academic_program,
+ 'version' => $this->version,
+ 'objectives' => $this->objectives,
+ 'time_allocation' => $this->time_allocation,
+ 'marks_allocation' => $this->marks_allocation,
+ 'ilos' => $this->ilos,
+ 'references' => $this->references,
+ 'created_by' => User::find($this->created_by)?->name,
+ 'updated_by' => User::find($this->updated_by)?->name,
+ 'created_at' => $this->created_at,
+ 'updated_at' => $this->updated_at,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Resources/SemesterResource.php b/app/Http/Resources/SemesterResource.php
new file mode 100644
index 0000000..a5c7360
--- /dev/null
+++ b/app/Http/Resources/SemesterResource.php
@@ -0,0 +1,31 @@
+ $this->id,
+ 'title' => $this->title,
+ 'version' => $this->version,
+ 'academic_program' => $this->academic_program,
+ 'description' => $this->description,
+ 'url' => $this->url,
+ 'created_by' => User::find($this->created_by)?->name,
+ 'updated_by' => User::find($this->updated_by)?->name,
+ 'created_at' => $this->created_at,
+ 'updated_at' => $this->updated_at,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 02922ef..1c87405 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -30,5 +30,6 @@ public function boot()
{
Paginator::useBootstrap();
Schema::defaultStringLength(191);
+ ini_set('max_execution_time', 120);
}
}
diff --git a/app/Rules/ValidateAsInternalEmail.php b/app/Rules/ValidateAsInternalEmail.php
new file mode 100644
index 0000000..a8a8aad
--- /dev/null
+++ b/app/Rules/ValidateAsInternalEmail.php
@@ -0,0 +1,44 @@
+environment() == 'testing') return true;
+ $api = new DepartmentDataService();
+ return $api->isInternalEmail($value);
+ }
+
+ /**
+ * Get the validation error message.
+ *
+ * @return string
+ */
+ public function message()
+ {
+ return "Only Department of Computer Engineering students/staff are allowed to register by themselves.";
+ }
+}
\ No newline at end of file
diff --git a/app/Services/DepartmentDataService.php b/app/Services/DepartmentDataService.php
new file mode 100644
index 0000000..70c91a5
--- /dev/null
+++ b/app/Services/DepartmentDataService.php
@@ -0,0 +1,64 @@
+getData('/people/v1/students/all/');
+ $student_emails = collect($students)->map(function ($user) {
+ $faculty_name = $user['emails']['faculty']['name'];
+ $faculty_domain = $user['emails']['faculty']['domain'];
+
+ $personal_name = $user['emails']['faculty']['name'];
+ $personal_domain = $user['emails']['faculty']['domain'];
+
+ if ($faculty_domain == 'eng.pdn.ac.lk' && $faculty_name != '' && $faculty_domain != '') {
+ // Faculty Email
+ return "$faculty_name@$faculty_domain";
+ } else if ($personal_domain == 'eng.pdn.ac.lk') {
+ // Personal Email
+ return "$personal_name@$personal_domain";
+ }
+ return null;
+ });
+
+ // Staff
+ $staff = $this->getData('/people/v1/staff/all/');
+ $staff_emails = collect($staff)->map(function ($user) {
+ return $user['email'];
+ });
+
+ return $student_emails->union($staff_emails)->filter()->values()->toArray();
+ }
+ );
+ return in_array($userEmail, $emails);
+ }
+
+ private function getData($endpoint)
+ {
+ $url = config('constants.department_data.base_url') . $endpoint;
+ $response = Http::get($url);
+
+ if ($response->successful()) {
+ return $response->json();
+ } else {
+ $statusCode = $response->status();
+ $errorMessage = $response->body();
+
+ Log::error('Error in getData: ' . $errorMessage);
+ return [];
+ }
+ }
+}
\ No newline at end of file
diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore
old mode 100755
new mode 100644
diff --git a/composer.json b/composer.json
index c994433..03347d7 100644
--- a/composer.json
+++ b/composer.json
@@ -25,6 +25,7 @@
"laravel/ui": "^3.0",
"laravelcollective/html": "^6.4",
"livewire/livewire": "^2.0",
+ "marvinlabs/laravel-discord-logger": "^1.4",
"rappasoft/laravel-livewire-tables": "^1.0",
"rappasoft/lockout": "^3.0",
"spatie/laravel-activitylog": "^3.14",
diff --git a/config/app.php b/config/app.php
index d572e37..1c12734 100644
--- a/config/app.php
+++ b/config/app.php
@@ -179,7 +179,8 @@
App\Providers\LocaleServiceProvider::class,
App\Providers\ObserverServiceProvider::class,
App\Providers\RouteServiceProvider::class,
-
+
+ MarvinLabs\DiscordLogger\ServiceProvider::class
],
/*
diff --git a/config/boilerplate.php b/config/boilerplate.php
index baff4ef..18ac83c 100644
--- a/config/boilerplate.php
+++ b/config/boilerplate.php
@@ -162,4 +162,4 @@
|
*/
'testing' => env('APP_TESTING', false),
-];
+];
\ No newline at end of file
diff --git a/config/constants.php b/config/constants.php
index c362453..067dc95 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -4,5 +4,9 @@
'frontend' => [
'dummy_thumb' => '/dummy/item_thumbnail.jpg',
],
- 'backend' => []
-];
+ 'backend' => [],
+ 'department_data' => [
+ 'base_url' => 'https://api.ce.pdn.ac.lk',
+ 'cache_duration' => 43200 // 6 hours
+ ]
+];
\ No newline at end of file
diff --git a/config/discord-logger.php b/config/discord-logger.php
new file mode 100644
index 0000000..da2cef5
--- /dev/null
+++ b/config/discord-logger.php
@@ -0,0 +1,61 @@
+ [
+ 'name' => env('APP_NAME', 'Discord Logger'),
+ 'avatar_url' => null,
+ ],
+
+ /**
+ * The converter to use to turn a log record into a discord message
+ *
+ * Bundled converters:
+ * - \MarvinLabs\DiscordLogger\Converters\SimpleRecordConverter::class
+ * - \MarvinLabs\DiscordLogger\Converters\RichRecordConverter::class
+ */
+ 'converter' => \MarvinLabs\DiscordLogger\Converters\RichRecordConverter::class,
+
+ /**
+ * If enabled, stacktraces will be attached as files. If not, stacktraces will be directly printed out in the
+ * message.
+ *
+ * Valid values are:
+ *
+ * - 'smart': when stacktrace is less than 2000 characters, it is inlined with the message, else attached as file
+ * - 'file': stacktrace is always attached as file
+ * - 'inline': stacktrace is always inlined with the message, truncated if necessary
+ */
+ 'stacktrace' => 'smart',
+
+ /*
+ * A set of colors to associate to the different log levels when using the `RichRecordConverter`
+ */
+ 'colors' => [
+ 'DEBUG' => 0x607d8b,
+ 'INFO' => 0x4caf50,
+ 'NOTICE' => 0x2196f3,
+ 'WARNING' => 0xff9800,
+ 'ERROR' => 0xf44336,
+ 'CRITICAL' => 0xe91e63,
+ 'ALERT' => 0x673ab7,
+ 'EMERGENCY' => 0x9c27b0,
+ ],
+
+ /*
+ * A set of emojis to associate to the different log levels. Set to null to disable an emoji for a given level
+ */
+ 'emojis' => [
+ 'DEBUG' => ':beetle:',
+ 'INFO' => ':bulb:',
+ 'NOTICE' => ':wink:',
+ 'WARNING' => ':flushed:',
+ 'ERROR' => ':poop:',
+ 'CRITICAL' => ':imp:',
+ 'ALERT' => ':japanese_ogre:',
+ 'EMERGENCY' => ':skull:',
+ ],
+];
\ No newline at end of file
diff --git a/config/logging.php b/config/logging.php
index 1aa06aa..1e68192 100644
--- a/config/logging.php
+++ b/config/logging.php
@@ -62,6 +62,13 @@
'level' => env('LOG_LEVEL', 'critical'),
],
+ 'discord' => [
+ 'driver' => 'custom',
+ 'via' => MarvinLabs\DiscordLogger\Logger::class,
+ 'level' => 'debug',
+ 'url' => env('LOG_DISCORD_WEBHOOK_URL'),
+ ],
+
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
diff --git a/database/factories/CourseFactory.php b/database/factories/CourseFactory.php
new file mode 100644
index 0000000..1d223f8
--- /dev/null
+++ b/database/factories/CourseFactory.php
@@ -0,0 +1,48 @@
+ $this->faker->unique()->regexify('[A-Z]{4}[0-9]{4}'),
+ 'semester_id' => $this->faker->numberBetween(1, 8),
+ 'academic_program' => $this->faker->randomElement(array_keys(Course::getAcademicPrograms())),
+ 'version' => $this->faker->randomElement([1, 2]),
+ 'name' => $this->faker->sentence(3),
+ 'credits' => $this->faker->numberBetween(1, 6),
+ 'type' => $this->faker->randomElement(array_keys(Course::getTypes())),
+ 'content' => $this->faker->paragraph(),
+ 'objectives' => json_encode([$this->faker->sentence(), $this->faker->sentence()]),
+ 'time_allocation' => json_encode(['lectures' => $this->faker->numberBetween(10, 50), 'practicals' => $this->faker->numberBetween(5, 20)]),
+ 'marks_allocation' => json_encode(['assignments' => $this->faker->numberBetween(10, 30), 'exams' => $this->faker->numberBetween(40, 60)]),
+ 'ilos' => json_encode([$this->faker->sentence(), $this->faker->sentence()]),
+ 'references' => json_encode([$this->faker->sentence(), $this->faker->sentence()]),
+ 'created_by' => User::inRandomOrder()->first()->id,
+ 'updated_by' => User::inRandomOrder()->first()->id,
+ 'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+ 'updated_at' => now(),
+ ];
+ }
+}
diff --git a/database/factories/SemesterFactory.php b/database/factories/SemesterFactory.php
new file mode 100644
index 0000000..026310b
--- /dev/null
+++ b/database/factories/SemesterFactory.php
@@ -0,0 +1,40 @@
+ $this->faker->sentence(3),
+ 'version' => $this->faker->randomElement(array_keys(Semester::getVersions())),
+ 'academic_program' => $this->faker->randomElement(array_keys(Semester::getAcademicPrograms())),
+ 'description' => $this->faker->paragraph,
+ 'url' => $this->faker->url,
+ 'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
+ 'updated_at' => now(),
+ 'created_by' => User::inRandomOrder()->first()->id,
+ 'updated_by' => User::inRandomOrder()->first()->id,
+ ];
+ }
+}
diff --git a/database/migrations/2024_08_23_165504_create_semesters_table.php b/database/migrations/2024_08_23_165504_create_semesters_table.php
new file mode 100644
index 0000000..c19095e
--- /dev/null
+++ b/database/migrations/2024_08_23_165504_create_semesters_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->string('title', 255);
+ $table->enum('version', array_keys(Semester::getVersions()));
+ $table->enum('academic_program', array_keys(Semester::getAcademicPrograms()));
+ $table->text('description')->nullable();
+ $table->string('url', 200)->unique();
+ $table->timestamps();
+ $table->foreignId('created_by')->constrained('users');
+ $table->foreignId('updated_by')->constrained('users');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('semesters');
+ }
+}
diff --git a/database/migrations/2024_09_03_102546_create_courses_table.php b/database/migrations/2024_09_03_102546_create_courses_table.php
new file mode 100644
index 0000000..4af42a2
--- /dev/null
+++ b/database/migrations/2024_09_03_102546_create_courses_table.php
@@ -0,0 +1,49 @@
+id(); // Primary key, auto-incrementing
+ $table->string('code', 16)->unique(); // Course code with a unique constraint
+ $table->foreignId('semester_id')->constrained('semesters')->on('semesters')
+ ->onDelete('cascade');; // Foreign key to semesters.id
+ $table->enum('academic_program', array_keys(Course::getAcademicPrograms())); // Enum for academic program
+ $table->enum('version', array_keys(Course::getVersions())); // Enum for version as numeric keys
+ $table->string('name', 255); // Course name
+ $table->integer('credits')->max(18)->min(0); // Credit hours
+ $table->enum('type', array_keys(Course::getTypes())); // Enum for course type
+ $table->text('content')->nullable(); // Course content, nullable
+ $table->json('objectives')->nullable(); // JSON for course objectives, nullable
+ $table->json('time_allocation')->nullable(); // JSON for time allocation, nullable
+ $table->json('marks_allocation')->nullable(); // JSON for marks allocation, nullable
+ $table->json('ilos')->nullable(); // JSON for intended learning outcomes, nullable
+ $table->json('references')->nullable(); // JSON for references, nullable
+ $table->string('faq_page', 191)->nullable();
+ $table->timestamps();
+ $table->foreignId('created_by')->constrained('users'); // Foreign key to users.id for created_by
+ $table->foreignId('updated_by')->constrained('users'); // Foreign key to users.id for updated_by
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('courses');
+ }
+}
diff --git a/database/migrations/2024_09_03_102824_create_course_modules_table.php b/database/migrations/2024_09_03_102824_create_course_modules_table.php
new file mode 100644
index 0000000..05ac4b8
--- /dev/null
+++ b/database/migrations/2024_09_03_102824_create_course_modules_table.php
@@ -0,0 +1,37 @@
+id(); // Primary key, auto-incrementing
+ $table->foreignId('course_id')->constrained('courses'); // Foreign key to courses.id
+ $table->string('topic', 255); // module topic
+ $table->text('description')->nullable(); // Course content, nullable
+ $table->json('time_allocation')->nullable(); // JSON for time allocation, nullable
+ $table->timestamps(); // Created_at and updated_at
+ $table->foreignId('created_by')->constrained('users'); // Foreign key to users.id for created_by
+ $table->foreignId('updated_by')->constrained('users'); // Foreign key to users.id for updated_by
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('course_modules');
+ }
+}
\ No newline at end of file
diff --git a/database/seeders/Auth/PermissionRoleSeeder.php b/database/seeders/Auth/PermissionRoleSeeder.php
index 1ff5f98..f077b58 100644
--- a/database/seeders/Auth/PermissionRoleSeeder.php
+++ b/database/seeders/Auth/PermissionRoleSeeder.php
@@ -26,17 +26,20 @@ public function run()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Create Roles
Role::create([
- 'id' => 1,
'type' => User::TYPE_ADMIN,
'name' => 'Administrator',
]);
Role::create([
- 'id' => 2,
'type' => User::TYPE_USER,
'name' => 'Editor',
]);
+ Role::create([
+ 'type' => User::TYPE_USER,
+ 'name' => 'Course Manager',
+ ]);
+
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Non Grouped Permissions
@@ -107,14 +110,37 @@ public function run()
])
]);
+ // Role: CourseManager
+ $courseManager = Permission::create([
+ 'type' => User::TYPE_USER,
+ 'name' => 'user.access.academic',
+ 'description' => 'All Course Manager Permissions',
+ ]);
+
+ $courseManager->children()->saveMany([
+ new Permission([
+ 'type' => User::TYPE_USER,
+ 'name' => 'user.access.academic.semesters',
+ 'description' => 'Semesters',
+ ]),
+ new Permission([
+ 'type' => User::TYPE_USER,
+ 'name' => 'user.access.academic.courses',
+ 'description' => 'Courses',
+ ]),
+ ]);
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Assign permissions to Roles
Role::findByName('Administrator')->givePermissionTo([
- 'admin.access.user', 'user.access.editor'
+ 'admin.access.user',
+ 'user.access.editor',
+ 'user.access.academic'
]);
+
Role::findByName('Editor')->givePermissionTo(['user.access.editor']);
+ Role::findByName('Course Manager')->givePermissionTo(['user.access.academic']);
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Assign Permissions to users
@@ -127,4 +153,4 @@ public function run()
$this->enableForeignKeys();
}
-}
\ No newline at end of file
+}
diff --git a/database/seeders/Auth/UserRoleSeeder.php b/database/seeders/Auth/UserRoleSeeder.php
index e2710dd..ada2a11 100644
--- a/database/seeders/Auth/UserRoleSeeder.php
+++ b/database/seeders/Auth/UserRoleSeeder.php
@@ -20,12 +20,16 @@ public function run()
{
$this->disableForeignKeys();
User::find(1)->assignRole(config('boilerplate.access.role.admin'), 'Editor');
+ User::find(1)->assignRole(config('boilerplate.access.role.admin'), 'Course Manager');
// Only for the local testings
if (app()->environment(['local', 'testing'])) {
User::find(2)->assignRole('Editor');
+ User::find(2)->assignRole('Course Manager');
+
+ User::find(5)->assignRole('Course Manager');
}
$this->enableForeignKeys();
}
-}
\ No newline at end of file
+}
diff --git a/database/seeders/Auth/UserSeeder.php b/database/seeders/Auth/UserSeeder.php
index 57f02e7..1505d99 100644
--- a/database/seeders/Auth/UserSeeder.php
+++ b/database/seeders/Auth/UserSeeder.php
@@ -34,7 +34,7 @@ public function run()
if (app()->environment(['local', 'testing'])) {
User::create([
'type' => User::TYPE_USER,
- 'name' => 'Test User',
+ 'name' => 'User',
'email' => env('SEED_USER_EMAIL', 'user@portal.ce.pdn.ac.lk'),
'password' => env('SEED_USER_PASSWORD', 'regular_user'),
'email_verified_at' => now(),
@@ -43,8 +43,8 @@ public function run()
User::create([
'type' => User::TYPE_USER,
- 'name' => 'NewsEditor1',
- 'email' => env('SEED_NEWS_EDITOR_EMAIL', 'news.editor@portal.ce.pdn.ac.lk'),
+ 'name' => 'News Editor',
+ 'email' => env('SEED_NEWS_EDITOR_EMAIL', 'user+news.editor@portal.ce.pdn.ac.lk'),
'password' => env('SEED_NEWS_EDITOR_PASSWORD', 'news'),
'email_verified_at' => now(),
'active' => true,
@@ -52,14 +52,23 @@ public function run()
User::create([
'type' => User::TYPE_USER,
- 'name' => 'EventEditor1',
- 'email' => env('SEED_EVENT_EDITOR_EMAIL', 'events.editor@portal.ce.pdn.ac.lk'),
+ 'name' => 'Event Editor',
+ 'email' => env('SEED_EVENT_EDITOR_EMAIL', 'user+events.editor@portal.ce.pdn.ac.lk'),
'password' => env('SEED_EVENT_EDITOR_PASSWORD', 'events'),
'email_verified_at' => now(),
'active' => true,
]);
+
+ User::create([
+ 'type' => User::TYPE_USER,
+ 'name' => 'CourseManager',
+ 'email' => env('SEED_COURSE_MANAGER_EMAIL', 'course_manager@portal.ce.pdn.ac.lk'),
+ 'password' => env('SEED_COURSE_MANAGER_PASSWORD', 'course_manager'),
+ 'email_verified_at' => now(),
+ 'active' => true,
+ ]);
}
$this->enableForeignKeys();
}
-}
\ No newline at end of file
+}
diff --git a/database/seeders/CourseSeeder.php b/database/seeders/CourseSeeder.php
new file mode 100644
index 0000000..8e59a42
--- /dev/null
+++ b/database/seeders/CourseSeeder.php
@@ -0,0 +1,231 @@
+ 'GP101',
+ 'name' => 'English I',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 1,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Language development, Communication through reading, Communication through listening, Communication through writing, Communication through speech',
+ 'objectives' => '',
+ 'ilos' => json_encode(['knowledge' => [], 'skills' => [], 'attitudes' => []]),
+ 'time_allocation' => json_encode(['lecture' => '20', 'assignment' => '50', 'tutorial' => '', 'practical' => '1']),
+ 'marks_allocation' => json_encode([
+ 'practicals' => '10',
+ 'project' => '',
+ 'mid_exam' => '30',
+ 'end_exam' => '60'
+ ]),
+ 'references' => json_encode([]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+
+ ]);
+
+ Course::create([
+ 'code' => 'GP109',
+ 'name' => 'Materials Science',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 1,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Introduction to the structure and properties of engineering materials, Principles underlying structure-property relationships, Phase equilibrium, Structure and properties of cement and timber...',
+ 'objectives' => 'Introduce the structure and properties of Engineering Materials',
+ 'ilos' => json_encode([
+ 'knowledge' => ['Describe materials in major classes of engineering materials'],
+ 'skills' => ['Use Equilibrium Phase diagrams...'],
+ 'attitudes' => ['Appreciate structure-property relationships...']
+ ]),
+ 'time_allocation' => json_encode(['lecture' => '38', 'assignment' => '1', 'tutorial' => '10', 'practical' => '1']),
+ 'marks_allocation' => json_encode([
+ 'practicals' => '10',
+ 'project' => '10',
+ 'mid_exam' => '30',
+ 'end_exam' => '50'
+ ]),
+ 'references' => json_encode([
+ 'Engineering Materials 1...',
+ 'The Science and Engineering of Materials...'
+ ]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+
+ ]);
+
+ Course::create([
+ 'code' => 'GP110',
+ 'name' => 'Engineering Mechanics',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 1,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Force systems, Analysis of simple structures, Work and energy methods, Inertial properties of plane and three-dimensional objects...',
+ 'objectives' => 'To introduce the state of rest or motion of bodies subjected to forces. Emphasis on applications to Engineering Designs.',
+ 'ilos' => json_encode([
+ 'knowledge' => ['Use scalar and vector methods for analyzing forces in structures.'],
+ 'skills' => ['Apply fundamental concepts of motion and identify parameters that define motion.'],
+ 'attitudes' => ['Use engineering mechanics for solving problems systematically.']
+ ]),
+ 'time_allocation' => json_encode(['lecture' => '28', 'assignment' => '11', 'tutorial' => '', 'practical' => '12']),
+ 'marks_allocation' => json_encode([
+ 'practicals' => '10',
+ 'project' => '10',
+ 'mid_exam' => '20',
+ 'end_exam' => '60'
+ ]),
+ 'references' => json_encode([
+ 'Hibbeler, R.C., Engineering Mechanics Statics and Dynamics...',
+ 'Douglas, J. F., Fluid Mechanics...'
+ ]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+ ]);
+
+ Course::create([
+ 'code' => 'GP115',
+ 'name' => 'Calculus I',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 1,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Real number system, Functions of a single variable, 2-D coordinate geometry, 3-D Euclidean geometry, Complex numbers...',
+ 'objectives' => '',
+ 'ilos' => json_encode([
+ 'knowledge' => ['Analyze problems in limits, continuity, differentiability, and integration.'],
+ 'skills' => ['Compute derivatives of complex functions, identify conic sections, and solve problems.'],
+ 'attitudes' => ['Determine the convergence of sequences and series, and find power series expansions.']
+ ]),
+ 'time_allocation' => json_encode(['lecture' => '36', 'assignment' => '18', 'tutorial' => '', 'practical' => '12']),
+ 'marks_allocation' => json_encode([
+ 'practicals' => '20',
+ 'project' => '',
+ 'mid_exam' => '30',
+ 'end_exam' => '50'
+ ]),
+ 'references' => json_encode([
+ 'James Stewart, Calculus...',
+ 'Watson Fulks, Advanced Calculus...'
+ ]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+ ]);
+
+ Course::create([
+ 'code' => 'GP112',
+ 'name' => 'Engineering Measurements',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 1,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Units and standards, Approximation errors and calibration, Measurement of physical parameters...',
+ 'objectives' => json_encode(['Understand different aspects of instrumentation and solve engineering problems through measurement and experimentation.']),
+ 'ilos' => json_encode([
+ 'knowledge' => ['Measure basic engineering quantities and present results using charts and tables.'],
+ 'skills' => ['Identify and minimize measurement errors, analyze time-dependent output of instruments.'],
+ 'attitudes' => ['Construct experiments to test hypotheses using statistical techniques.']
+ ]),
+ 'time_allocation' => json_encode(['lecture' => '21', 'assignment' => '', 'tutorial' => '4', 'practical' => '40']),
+ 'marks_allocation' => json_encode([
+ 'practicals' => '40',
+ 'project' => '20',
+ 'mid_exam' => '',
+ 'end_exam' => '40'
+ ]),
+ 'references' => json_encode([
+ 'Schofield, W., Engineering Surveying...',
+ 'Ghilani, Charles D., Elementary Surveying...'
+ ]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+ ]);
+
+ Course::create([
+ 'code' => 'GP113',
+ 'name' => 'Fundamentals of Manufacture',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 2,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Introduction to manufacturing industry, Machining, Casting, Welding, Metal forming, Manufacturing systems...',
+ 'objectives' =>
+ 'Provide fundamental knowledge of manufacturing engineering and design.Enable students to evaluate and manufacture products while satisfying consumer requirements.',
+ 'ilos' => json_encode([
+ 'knowledge' => ['Understand the core principles of manufacturing processes.'],
+ 'skills' => ['Evaluate manufacturing systems for optimizing efficiency.'],
+ 'attitudes' => ['Apply safety measures in engineering manufacturing processes.']
+ ]),
+ 'time_allocation' => json_encode(['lecture' => '20', 'assignment' => '36', 'tutorial' => '7', 'practical' => '40']),
+ 'marks_allocation' => json_encode([
+ 'practicals' => '30',
+ 'project' => '10',
+ 'mid_exam' => '20',
+ 'end_exam' => '40'
+ ]),
+ 'references' => json_encode([
+ 'Shop Theory by Anderson and Tatro...',
+ 'Workshop Technology by W.A.J. Chapman...'
+ ]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+ ]);
+
+ Course::create([
+ 'code' => 'CO221',
+ 'name' => 'Digital Design',
+ 'credits' => 3,
+ 'type' => 'Core',
+ 'semester_id' => 3,
+ 'academic_program' => 'undergraduate',
+ 'version' => 1,
+ 'content' => 'Introduction to digital logic, Number systems, Combinational logic circuits, Sequential logic circuits, Digital circuit design and implementation...',
+ 'objectives' =>
+ 'Introduce digital electronics with emphasis on practical design techniques for digital circuits.Teach how to design combinational and sequential circuits.',
+ 'ilos' => json_encode([
+ 'knowledge' => ['Perform Boolean manipulation and design digital circuits.'],
+ 'skills' => ['Design and implement basic combinational and sequential circuits.'],
+ 'attitudes' => ['Develop confidence in digital circuit design.']
+ ]),
+ 'time_allocation' => json_encode(['lecture' => '30', 'assignment' => '14', 'tutorial' => '10']),
+ 'marks_allocation' => json_encode([
+
+ 'practicals' => '10',
+ 'project' => '',
+ 'mid_exam' => '30',
+ 'end_exam' => '60'
+ ]),
+ 'references' => json_encode([
+ 'Digital Design by Morris Mano...',
+ 'Digital Design: A Systems Approach by William James Dally...'
+ ]),
+ 'created_by' => '1',
+ 'updated_by' => '1',
+ ]);
+ }
+}
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 49ed96a..eeeabdc 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -5,6 +5,7 @@
use Database\Seeders\Traits\TruncateTable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder;
+use Illuminate\Support\Facades\App;
/**
@@ -26,10 +27,15 @@ public function run()
'failed_jobs',
]);
- $this->call(AuthSeeder::class);
- $this->call(AnnouncementSeeder::class);
- $this->call(NewsSeeder::class);
- $this->call(EventSeeder::class);
+
+ if (App::environment('local', 'testing')) {
+ $this->call(AuthSeeder::class);
+ $this->call(AnnouncementSeeder::class);
+ $this->call(NewsSeeder::class);
+ $this->call(EventSeeder::class);
+ $this->call(SemesterSeeder::class);
+ $this->call(CourseSeeder::class);
+ }
Model::reguard();
}
diff --git a/database/seeders/EventSeeder.php b/database/seeders/EventSeeder.php
index 0798fb8..8f947f1 100644
--- a/database/seeders/EventSeeder.php
+++ b/database/seeders/EventSeeder.php
@@ -24,7 +24,7 @@ public function run()
[
"id" => 3,
"title" => "ESCAPE - 2020",
- "description" => "
EscaPe is the annual project symposium of the Department of Computer Engineering, University of Peradeniya. It will present the research projects of the undergraduates of the Department of Computer Engineering. ESCaPe 2020 is the 5th symposium that is organized by the department and this time the symposium is open for a broader audience and aims to build a platform for the undergraduates to present their research ideas to the industry and academic community.<\/span><\/p>",
+ "description" => '
EscaPe is the annual project symposium of the Department of Computer Engineering, University of Peradeniya. It will present the research projects of the undergraduates of the Department of Computer Engineering. ESCaPe 2020 is the 5th symposium that is organized by the department and this time the symposium is open for a broader audience and aims to build a platform for the undergraduates to present their research ideas to the industry and academic community.
Online social gathering of the Department of Computer Engineering, University of Peradeniya was held on Friday 12th of June, 2020 with the participation of the Students and Staff.<\/span><\/p>",
+ "description" => '
Online social gathering of Department of Computer Engineering, University of Peradeniya was held on Friday 12th of June, 2020 with the participation of the Students and Staff successfully.
An Online Webinar series organized by Hackers’ Club to introduce some of the tools that you must have up on your sleeve to be a successful Developer\/Engineer in the world of Computing. And also a chance to master some of them with the Developer Resources & Materials shared by Hackers’ Club.<\/p>
This Developer Series mainly focuses on front-end web development, and back-end development, for implementing a multi-platform solution for the real world problems. The Developer Series will be an invaluable chance for you to start the journey of mastering the Web Development world.<\/p>
<\/p>
Series Timeline=><\/p>
Introduction to Git – Nov 03<\/li>
Project collaboration with “GitHub” – Nov 17<\/li>
Introduction to Node.js – Nov 24<\/li>
MongoDB Express REST API with Node.js – Dec 01<\/li>
Organized by the Department of Computer Engineering.<\/p>",
+ "description" => '
Organized by the Department of Computer Engineering.
',
"url" => "gsoc-preparation-mentoring-program21",
"published_at" => "2024-08-27",
"image" => "1724778473.png",
diff --git a/database/seeders/NewsSeeder.php b/database/seeders/NewsSeeder.php
index 29dd4e4..f3fb019 100644
--- a/database/seeders/NewsSeeder.php
+++ b/database/seeders/NewsSeeder.php
@@ -23,7 +23,7 @@ public function run()
$news = [
[
"title" => "Third year undergraduate in the award winning team working on Cloud Atlas",
- "description" => "
Three third year undergraduates from the University of Peradeniya have invented a hundred percent Bio-Degradable Sanitizer bottle and Sanitary wipe package to combat this issue. Navodya Kumari Ekanayake, a third-year medical student; Shakthi Senarathne, a third-year medical student; Denuka Jayaweera, a third-year engineering undergraduate from the Department of Computer Engineering are working on this team CLOUD ATLAS.<\/p>
Their aims are to make an impact on environmental conservation, to contribute towards United Nations Sustainable development goals three six nine and twelve, to give a sustainable alternative for sanitary needs in the market, to reduce the usage of polythene and plastics and to take a value out of banana stem which is thrown away after harvesting.<\/p>
<\/p>
Plastic usage has been abused and adulterated by mankind, stepping towards the highest risks of pollution. The net increment of usage hasn’t been calculated for the year 2020, but undoubtedly an exponential rise can be predicted. Disposal and the 3R method seems to be in the air, but plastic landfill is the commonest problem well known, but solutions are not yet properly addressed by any responsible authority.<\/p>
<\/p>
Due to the covid pandemic, the usage of sanitiser bottles increased drastically. If five million [which is less than twenty-five percent of the population] people discard one sanitiser bottle each per two weeks, the number of sanitiser bottles discarded per month is approximately ten million bottles. If so the total amount of plastic landfill added by sanitiser bottles per month equals the total number of bottles discarded into the mean weight of one bottle which eventually add up to around three hundred thousand kilograms of plastics per month only by sanitiser bottles.<\/p>
<\/p>
They figured out this directly affected the usage of polythene and plastic in Sri Lanka. And to add to that normal bottle of sanitiser takes about four hundred and fifty to six hundred years to get decomposed, other than that there was no such biodegradable product in the market similar to this not only in Sri Lanka but in the global context as well.<\/p>
The prototyped product was the first bottle model and sanitary wipe in the world, to be made out of a banana stem as the main ingredient. All these products have a net zero percent environmental impact, Which decomposes completely within one month. They chose banana paper over normal recycled paper, to address this environmental impact issue. Even recycled paper demands an additional cost of cutting trees, which is harmful to the prevailing situation of the country. They have aimed at minimal deforestation and carbon footprint in the production process.<\/p>
<\/p>
Recently, bamboo came as a substitute for normal trees in China and East Asian countries. But in countries like Sri Lanka, bamboo cannot be used as a substitute ingredient for paper, as we don’t have considerable bamboo cultivation targeting production. Bamboo is highly resilient against soil erosion, especially in river beds. Banana has the same properties found in bamboo and is easily found in Sri Lanka. Add to that the fineness of a banana is better than bamboo (with an average fineness of two thousand four hundred Nano meters). Worldwide there is a good market for bamboo wipes due to their softness. Therefore banana would be better<\/p>
<\/p>
The team emerged as the ideation champions in Thinkwave 2.0 organized by the University of Moratuwa, Overall runners up in Thinkwave 2.0, champions of Pera Inventra 2021 and second runners up of YES Youth Entrepreneurship Summit organized by the University of Sri Jayawaradenapura.<\/p>
Their work has also being featured in local newspaper.<\/p>",
+ "description" => "
Three third year undergraduates from the University of Peradeniya have invented a hundred percent Bio-Degradable Sanitizer bottle and Sanitary wipe package to combat this issue. Navodya Kumari Ekanayake, a third-year medical student; Shakthi Senarathne, a third-year medical student; Denuka Jayaweera, a third-year engineering undergraduate from the Department of Computer Engineering are working on this team CLOUD ATLAS.
Their aims are to make an impact on environmental conservation, to contribute towards United Nations Sustainable development goals three six nine and twelve, to give a sustainable alternative for sanitary needs in the market, to reduce the usage of polythene and plastics and to take a value out of banana stem which is thrown away after harvesting.
Plastic usage has been abused and adulterated by mankind, stepping towards the highest risks of pollution. The net increment of usage hasn’t been calculated for the year 2020, but undoubtedly an exponential rise can be predicted. Disposal and the 3R method seems to be in the air, but plastic landfill is the commonest problem well known, but solutions are not yet properly addressed by any responsible authority.
Due to the covid pandemic, the usage of sanitiser bottles increased drastically. If five million [which is less than twenty-five percent of the population] people discard one sanitiser bottle each per two weeks, the number of sanitiser bottles discarded per month is approximately ten million bottles. If so the total amount of plastic landfill added by sanitiser bottles per month equals the total number of bottles discarded into the mean weight of one bottle which eventually add up to around three hundred thousand kilograms of plastics per month only by sanitiser bottles.
They figured out this directly affected the usage of polythene and plastic in Sri Lanka. And to add to that normal bottle of sanitiser takes about four hundred and fifty to six hundred years to get decomposed, other than that there was no such biodegradable product in the market similar to this not only in Sri Lanka but in the global context as well.
The prototyped product was the first bottle model and sanitary wipe in the world, to be made out of a banana stem as the main ingredient. All these products have a net zero percent environmental impact, Which decomposes completely within one month. They chose banana paper over normal recycled paper, to address this environmental impact issue. Even recycled paper demands an additional cost of cutting trees, which is harmful to the prevailing situation of the country. They have aimed at minimal deforestation and carbon footprint in the production process.
Recently, bamboo came as a substitute for normal trees in China and East Asian countries. But in countries like Sri Lanka, bamboo cannot be used as a substitute ingredient for paper, as we don’t have considerable bamboo cultivation targeting production. Bamboo is highly resilient against soil erosion, especially in river beds. Banana has the same properties found in bamboo and is easily found in Sri Lanka. Add to that the fineness of a banana is better than bamboo (with an average fineness of two thousand four hundred Nano meters). Worldwide there is a good market for bamboo wipes due to their softness. Therefore banana would be better
The team emerged as the ideation champions in Thinkwave 2.0 organized by the University of Moratuwa, Overall runners up in Thinkwave 2.0, champions of Pera Inventra 2021 and second runners up of YES Youth Entrepreneurship Summit organized by the University of Sri Jayawaradenapura.
Their work has also being featured in local newspaper.
",
"url" => "third-year-undergraduate-in-the-award-winning-team.html",
"image" => "1724778421.jpg",
"link_url" => null,
@@ -45,7 +45,7 @@ public function run()
],
[
"title" => "The annual general meeting of the Association of Computer Engineering Students",
- "description" => "
The annual general meeting of the Association of Computer Engineering Students (ACES) was successfully held on 28th October 2021 with the participation of students and lecturers of the department of Computer Engineering, University of Peradeniya. The outgoing president, Mr. Malitha Liyanage commenced the meeting by welcoming all the members and sharing a few memories of the events conducted during the past year. After presenting the meeting minutes of the last annual general meeting and the budget report for the last year by the secretary, Mr. Hashan Eranga, and the junior treasurer, Mr. Nipun Dewanarayane, the new office bearers of ACES for the term 2021\/22 were appointed.<\/p>
<\/p>
Accordingly, Mr. Randika Viraj was appointed as the President for the new term. Next up was the appointment for the role of Vice President and Mr. Thushara Weerasundara was appointed for it. Subsequently, Mr. Imesh Balasuriya and Mr. Ridma Jayasundara were appointed for the roles of Secretary and Assistant Secretary respectively. Afterwards, Dr. Isuru Nawinne, a Senior Lecturer of the department, was assigned as the Senior Treasurer followed by the appointment of Mr. Nadeesha Diwakara as the Junior Treasurer. Then, Miss Nanduni Gamage and Miss Isara Tillekaratne were appointed for the roles of Editor and Junior Editor respectively. Finally, eight committee members were assigned representing the batches E16, E17, and E18 as follows; Mr. Kavindu Hewamanage, Mr. Deshan Liyanaarachchi, and Mr. Heshan Dissanayake, representing the E16 batch, Mr. Pubudu Bandara and Mr. Adithya Gallage representing the E17 batch and Mr. Ishta Jayakody, Miss Roshila Sewwandi and Mr. Ruchira Tharaka, representing the E18 batch.<\/p>
<\/p>
After appointing the new members, Mr. Randika, the newly appointed president took a few moments to thank the past committee for their hard work. Also, he further added a brief introduction to the upcoming plans and asked for any opinions and suggestions regarding this endeavor. The latter part of the meeting comprised a discussion among the staff members and the students on the suggestions for the upcoming year. Prof. Roshan Ragel expressed his idea that the pandemic situation has brought forth the need for online events and that ACES could be at the forefront of organizing such events with novel concepts. It was highlighted that having two plans for the online mode as well as the physical mode could be beneficial to overcome the uncertainties and the AGM marked the conclusion with a bunch of experience-based advice and great congratulations to the new committee for the upcoming year.<\/p>
<\/p>
ACES is looking forward to bring more creative and productive programs in the coming year and invites everyone to stay tuned.<\/p>
<\/p>
Reported by Nanduni Gamage<\/p>",
+ "description" => "
The annual general meeting of the Association of Computer Engineering Students (ACES) was successfully held on 28th October 2021 with the participation of students and lecturers of the department of Computer Engineering, University of Peradeniya. The outgoing president, Mr. Malitha Liyanage commenced the meeting by welcoming all the members and sharing a few memories of the events conducted during the past year. After presenting the meeting minutes of the last annual general meeting and the budget report for the last year by the secretary, Mr. Hashan Eranga, and the junior treasurer, Mr. Nipun Dewanarayane, the new office bearers of ACES for the term 2021/22 were appointed.
Accordingly, Mr. Randika Viraj was appointed as the President for the new term. Next up was the appointment for the role of Vice President and Mr. Thushara Weerasundara was appointed for it. Subsequently, Mr. Imesh Balasuriya and Mr. Ridma Jayasundara were appointed for the roles of Secretary and Assistant Secretary respectively. Afterwards, Dr. Isuru Nawinne, a Senior Lecturer of the department, was assigned as the Senior Treasurer followed by the appointment of Mr. Nadeesha Diwakara as the Junior Treasurer. Then, Miss Nanduni Gamage and Miss Isara Tillekaratne were appointed for the roles of Editor and Junior Editor respectively. Finally, eight committee members were assigned representing the batches E16, E17, and E18 as follows; Mr. Kavindu Hewamanage, Mr. Deshan Liyanaarachchi, and Mr. Heshan Dissanayake, representing the E16 batch, Mr. Pubudu Bandara and Mr. Adithya Gallage representing the E17 batch and Mr. Ishta Jayakody, Miss Roshila Sewwandi and Mr. Ruchira Tharaka, representing the E18 batch.
After appointing the new members, Mr. Randika, the newly appointed president took a few moments to thank the past committee for their hard work. Also, he further added a brief introduction to the upcoming plans and asked for any opinions and suggestions regarding this endeavor. The latter part of the meeting comprised a discussion among the staff members and the students on the suggestions for the upcoming year. Prof. Roshan Ragel expressed his idea that the pandemic situation has brought forth the need for online events and that ACES could be at the forefront of organizing such events with novel concepts. It was highlighted that having two plans for the online mode as well as the physical mode could be beneficial to overcome the uncertainties and the AGM marked the conclusion with a bunch of experience-based advice and great congratulations to the new committee for the upcoming year.
ACES is looking forward to bring more creative and productive programs in the coming year and invites everyone to stay tuned.
Reported by Nanduni Gamage
",
"url" => "the-annual-general-meeting-of-the-association-of-computer-engineering-students",
"image" => "1724778359.jpg",
"link_url" => null,
@@ -56,7 +56,7 @@ public function run()
],
[
"title" => "Team IT crowd wins the championship in Yes Hub Bootcamp’21",
- "description" => "
The Team IT Crowd consisted of Sandun Kodagoda,Adithya Gallage and Denuka Jayaweera, undergraduates of Department computer Engineering became champions in the YES hub boot camp ’21 organized by the Yes Hub, Venture Frontier along with the US Embassy.<\/p>
<\/p>
This Entrepreneurial , Innovation and Pitching event had around 50 teams participating in the event after a preliminary qualification held offline. The competitors participated in the competition ranged from MSc Holders to Undergraduates. after the first round the team IT Crowd got selected to Top Six and after the second round they eventually went on to win the completion. The Event was graced by more than 15 national and international entrepreneurs businessmen who appreciated the effort thrown in by the team.<\/p>
<\/p>
The Idea Pitched by the Team IT crowd started as their second year project in the university which is about developing the concept of smart lockers.<\/p>",
+ "description" => "
The Team IT Crowd consisted of Sandun Kodagoda,Adithya Gallage and Denuka Jayaweera, undergraduates of Department computer Engineering became champions in the YES hub boot camp ’21 organized by the Yes Hub, Venture Frontier along with the US Embassy.
This Entrepreneurial , Innovation and Pitching event had around 50 teams participating in the event after a preliminary qualification held offline. The competitors participated in the competition ranged from MSc Holders to Undergraduates. after the first round the team IT Crowd got selected to Top Six and after the second round they eventually went on to win the completion. The Event was graced by more than 15 national and international entrepreneurs businessmen who appreciated the effort thrown in by the team.
The Idea Pitched by the Team IT crowd started as their second year project in the university which is about developing the concept of smart lockers.
",
"url" => "team-it-crowd-wins-the-championship",
"image" => "1724778320.jpg",
"link_url" => null,
@@ -67,7 +67,7 @@ public function run()
],
[
"title" => "Department Staff meets the Vice Chancellor on International Collaborations",
- "description" => "
Professor MD. Lamawansa, The Vice Chancellor of University of Peradeniya met with a group of academics from the Faculty of Engineering to discuss strengthening international collaborations. The group comprised of Dr. Udaya Dissanayake, the Dean, Faculty of Engineering, Prof. Roshan Ragel, Dr. Kamalanath Samarakoon, the Head of the Department of Computer Engineering, Director\/ETIC, Prof. CNRA. Alles, Director of International Research Office, Dr. Panduka Neluwala, Coordinator, International Relations of the Faculty of Engineering, Dr. Damayanthi Herath, Coordinator, International Relations of the Department of Computer of Engineering.<\/p>
<\/p>
Dr. Samarakoon shared insights covering expanding University teaching and learning activities, incubation, and collaborations between the faculties of the University with industry, and foreign universities with regard to product development with the experience gained from his recent visits to international universities.<\/p>
<\/p>
During this meeting, the Vice-Chancellor signed three MoUs to foster further collaborations between the University of Peradeniya and the Indian Institute of Technology Delhi (IITD), Indian Institute of Technology Roorkee (IITR), and Lovely Professional University (LPU) of India.<\/p>",
+ "description" => "
Professor MD. Lamawansa, The Vice Chancellor of University of Peradeniya met with a group of academics from the Faculty of Engineering to discuss strengthening international collaborations. The group comprised of Dr. Udaya Dissanayake, the Dean, Faculty of Engineering, Prof. Roshan Ragel, Dr. Kamalanath Samarakoon, the Head of the Department of Computer Engineering, Director/ETIC, Prof. CNRA. Alles, Director of International Research Office, Dr. Panduka Neluwala, Coordinator, International Relations of the Faculty of Engineering, Dr. Damayanthi Herath, Coordinator, International Relations of the Department of Computer of Engineering.
Dr. Samarakoon shared insights covering expanding University teaching and learning activities, incubation, and collaborations between the faculties of the University with industry, and foreign universities with regard to product development with the experience gained from his recent visits to international universities.
During this meeting, the Vice-Chancellor signed three MoUs to foster further collaborations between the University of Peradeniya and the Indian Institute of Technology Delhi (IITD), Indian Institute of Technology Roorkee (IITR), and Lovely Professional University (LPU) of India.
",
"url" => "department-staff-meets-the-vice-chancellor-on-international-collaborations",
"image" => "1724778089.png",
"link_url" => null,
@@ -78,7 +78,7 @@ public function run()
],
[
"title" => "ACES ties up with Nenathambara to make its largest coding competition on the island to the next level",
- "description" => "
The Department of Computer Engineering of the University of Peradeniya has developed over time to the point where it is today, delivering one of the most esteemed Computer Engineering degree programmes in Sri Lanka. The Association of Computer Engineering Students (ACES), the official student organization of the Department, guides undergraduate students through many initiatives, such as the ACES Hackathon, ACES Coders, Spark, and Nenathambara, which help them build their soft skills while bridging the gap between academia, industry, and schools.<\/p>
<\/p>
In the series of achievements, the day to witness the significant milestones of two remarkable programmes, ACES Coders v9.0, the island’s largest competitive programming challenge, and the distribution ceremony of Arduino kits for the worthwhile Project Nenathambara, arrived on the 17th of December 2022.<\/p>
<\/p>
After giving a brief introduction about the rules and regulations of the competition, competitors headed to their respective desks in Drawing Office 1 of the University of Peradeniya for their 12-hour coding mission. The competitors were expected to use their logical thinking and programming skills to come up with solutions to the competition’s realistic problems, which were conducted through the Hackerank platform. With the refreshments offered, all the teams worked all night long on coding energetically with a single goal in mind. However, the scoreboard rapidly changed, showing how extremely competitive the teams were. Moreover, it enhances the participants’ ability to work in a team and manage their time well because they must compete in the competition as a team within the allotted time frame.<\/p>
<\/p>
Finally, the ACES Coders v9.0 winners were announced and received cash prizes, and other participants who took part received certificates recognising their participation. After a 12-hour sleepless coding marathon and a lot of effort, Team BitFlippers from the University of Peradeniya took first place out of over 100 teams. Team BitLasagna from the University of Peradeniya were the runners-up, while Team DragonCoders from the University of Moratuwa became the second runners-up in this competition.<\/p>
<\/p>
Mr Ruchika Perera, Secretary of the Hackers’ Club at the University of Peradeniya, gave the closing remarks to the event, thanking all the helping hands behind the achievement. Giving school children exposure to the world of coding helps them to broaden and realign their perspective, and recognising events like ACES Coders can undoubtedly encourage young souls to explore the limitless opportunities of university life.<\/p>
<\/p>
The event was indeed a tremendous success, and all of this was made possible by the support provided in the form of sponsorship. This year, the competition was sponsored by Synopsys Sri Lanka (Platinum sponsor), Bitzify (Gold sponsor), GTN Technologies (Event Partner), Avtra (Silver sponsor), Clouda Inc. (Startup Partner), The Software Practice, Acentura, and Zincat (Supporting Partners), and Arteculate and Gauge (Media Partners). Speaking on behalf of the sponsors, Mr V. Kathircamalan from Synopsys addressed the audience, and Mr Farazy Fahmy, the director of research and development at Synopsys, mentioned the value of education and how responsible we should be to take care of the people who paid for it. Furthermore, souvenirs were given out to appreciate the sponsors for their enormous support.<\/p>",
+ "description" => "
The Department of Computer Engineering of the University of Peradeniya has developed over time to the point where it is today, delivering one of the most esteemed Computer Engineering degree programmes in Sri Lanka. The Association of Computer Engineering Students (ACES), the official student organization of the Department, guides undergraduate students through many initiatives, such as the ACES Hackathon, ACES Coders, Spark, and Nenathambara, which help them build their soft skills while bridging the gap between academia, industry, and schools.
In the series of achievements, the day to witness the significant milestones of two remarkable programmes, ACES Coders v9.0, the island’s largest competitive programming challenge, and the distribution ceremony of Arduino kits for the worthwhile Project Nenathambara, arrived on the 17th of December 2022.
After giving a brief introduction about the rules and regulations of the competition, competitors headed to their respective desks in Drawing Office 1 of the University of Peradeniya for their 12-hour coding mission. The competitors were expected to use their logical thinking and programming skills to come up with solutions to the competition’s realistic problems, which were conducted through the Hackerank platform. With the refreshments offered, all the teams worked all night long on coding energetically with a single goal in mind. However, the scoreboard rapidly changed, showing how extremely competitive the teams were. Moreover, it enhances the participants’ ability to work in a team and manage their time well because they must compete in the competition as a team within the allotted time frame.
Finally, the ACES Coders v9.0 winners were announced and received cash prizes, and other participants who took part received certificates recognising their participation. After a 12-hour sleepless coding marathon and a lot of effort, Team BitFlippers from the University of Peradeniya took first place out of over 100 teams. Team BitLasagna from the University of Peradeniya were the runners-up, while Team DragonCoders from the University of Moratuwa became the second runners-up in this competition.
Mr Ruchika Perera, Secretary of the Hackers’ Club at the University of Peradeniya, gave the closing remarks to the event, thanking all the helping hands behind the achievement. Giving school children exposure to the world of coding helps them to broaden and realign their perspective, and recognising events like ACES Coders can undoubtedly encourage young souls to explore the limitless opportunities of university life.
The event was indeed a tremendous success, and all of this was made possible by the support provided in the form of sponsorship. This year, the competition was sponsored by Synopsys Sri Lanka (Platinum sponsor), Bitzify (Gold sponsor), GTN Technologies (Event Partner), Avtra (Silver sponsor), Clouda Inc. (Startup Partner), The Software Practice, Acentura, and Zincat (Supporting Partners), and Arteculate and Gauge (Media Partners). Speaking on behalf of the sponsors, Mr V. Kathircamalan from Synopsys addressed the audience, and Mr Farazy Fahmy, the director of research and development at Synopsys, mentioned the value of education and how responsible we should be to take care of the people who paid for it. Furthermore, souvenirs were given out to appreciate the sponsors for their enormous support.
",
"url" => "aces-ties-up-with-nenathambara",
"image" => "1724778403.jpg",
"link_url" => null,
@@ -89,7 +89,7 @@ public function run()
],
[
"title" => "PeraCom wins a Gold at Global Robotics Games 2023 at Singapore",
- "description" => "
We are extremely proud to announce that a group of five third year undergraduates of Department of Computer Engineering, University of Peradeniya have become first in the senior university category at Global Robotics Games 2023. The three day robotics competition held in Singapore, began on the 15th of November 2023. The competition is part of the educational initiative led by WEFAA Robotics<\/em>, to engage and inspire students ranging from primary to undergraduate levels.<\/p>
<\/p>
Five undergraduate students from the Department of Computer Engineering at the University of Peradeniya, namely Adeepa Fernando, Hariharan Raveenthiran, Piumal Rathnayake, Saadia Jameel and Thamish Wanduragala<\/em>, presently engaged in their industrial training in Singapore, participated in the contest under the Senior University Category. Participants had to assemble their own reconfigurable, modular and programmable robots for a soccer competition.<\/p>
<\/p>
On day one, the students embraced the challenge of showcasing their technical prowess by assembling their version of a soccer- playing robot. On day two, the focus shifted to refining the robot’s skills and strategy in preparation for taking on opponents in a series of soccer matches. After having played all round- robin matches and winning the quarter finals on the second day, our team went on to winning the semi- final and final matches on the third day. They were thus crowned the winners of the Transformer Robot Soccer League in the Senior University Category.<\/p>
<\/p>
After the awards ceremony, was a cultural event where participants from all countries took to the stage to showcase their respective cultures through song, dance, or drama. Our team from Sri Lanka performed the song “Sri Lanka Matha” by the renowned Sri Lankan singers Bathiya and Santhush.<\/p>",
+ "description" => "
We are extremely proud to announce that a group of five third year undergraduates of Department of Computer Engineering, University of Peradeniya have become first in the senior university category at Global Robotics Games 2023. The three day robotics competition held in Singapore, began on the 15th of November 2023. The competition is part of the educational initiative led by WEFAA Robotics, to engage and inspire students ranging from primary to undergraduate levels.
Five undergraduate students from the Department of Computer Engineering at the University of Peradeniya, namely Adeepa Fernando, Hariharan Raveenthiran, Piumal Rathnayake, Saadia Jameel and Thamish Wanduragala, presently engaged in their industrial training in Singapore, participated in the contest under the Senior University Category. Participants had to assemble their own reconfigurable, modular and programmable robots for a soccer competition.
On day one, the students embraced the challenge of showcasing their technical prowess by assembling their version of a soccer- playing robot. On day two, the focus shifted to refining the robot’s skills and strategy in preparation for taking on opponents in a series of soccer matches. After having played all round- robin matches and winning the quarter finals on the second day, our team went on to winning the semi- final and final matches on the third day. They were thus crowned the winners of the Transformer Robot Soccer League in the Senior University Category.
After the awards ceremony, was a cultural event where participants from all countries took to the stage to showcase their respective cultures through song, dance, or drama. Our team from Sri Lanka performed the song “Sri Lanka Matha” by the renowned Sri Lankan singers Bathiya and Santhush.
+
\ No newline at end of file
diff --git a/resources/views/backend/includes/header.blade.php b/resources/views/backend/includes/header.blade.php
index bdfe9b5..79f7ad3 100644
--- a/resources/views/backend/includes/header.blade.php
+++ b/resources/views/backend/includes/header.blade.php
@@ -1,42 +1,36 @@
-