feat: add app and database modules

This commit is contained in:
2026-05-21 16:05:11 +07:00
parent 37b7e783f5
commit fad70d096b
212 changed files with 23901 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace App\Repositories;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
abstract class BaseRepository
{
/**
* @var Model
*/
protected $model;
/**
* BaseRepository constructor.
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* Get all records.
*/
public function all(array $columns = ['*']): Collection
{
return $this->model->all($columns);
}
/**
* Find a record by ID.
*/
public function find(int|string $id, array $columns = ['*']): ?Model
{
return $this->model->find($id, $columns);
}
/**
* Find a record by ID or throw an exception.
*/
public function findOrFail(int|string $id, array $columns = ['*']): Model
{
return $this->model->findOrFail($id, $columns);
}
/**
* Create a new record.
*/
public function create(array $data): Model
{
return $this->model->create($data);
}
/**
* Update an existing record.
*/
public function update(int|string $id, array $data): bool
{
$model = $this->find($id);
if ($model) {
return $model->update($data);
}
return false;
}
/**
* Delete a record by ID.
*/
public function delete(int|string $id): bool
{
$model = $this->find($id);
if ($model) {
return $model->delete();
}
return false;
}
/**
* Paginate records.
*/
public function paginate(int $perPage = 15, array $columns = ['*']): LengthAwarePaginator
{
return $this->model->paginate($perPage, $columns);
}
/**
* Get query builder.
*
* @return Builder
*/
public function query()
{
return $this->model->newQuery();
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
namespace App\Repositories;
use App\Models\Notification;
class NotificationRepository extends BaseRepository
{
public function __construct(Notification $model)
{
parent::__construct($model);
}
/**
* Get the list of recipient categories a user is authorized to see.
* Hierarchy: Developer > Administrator > User > all
*/
public function getAuthorizedRecipients($user)
{
$recipients = ['all'];
if ($user->hasRole('Developer')) {
return ['Developer', 'Administrator', 'User', 'all'];
}
if ($user->hasRole('Administrator')) {
return ['Administrator', 'User', 'all'];
}
// Default: only see notifications for 'User' and 'all'
return ['User', 'all'];
}
/**
* Get ALL notifications for a user based on their roles,
* BUT exclude those they have personally deleted/hidden.
*/
public function getActiveNotificationsForUser($user, $offset = 0, $limit = 10)
{
$query = $this->model->query();
// 1. Filter by roles (Broadcast targeting) hierarchical
$authorizedRecipients = $this->getAuthorizedRecipients($user);
$query->whereIn('recipient', $authorizedRecipients);
// 2. Left Join personal interactions
$query->leftJoin('system_notification_user', function ($join) use ($user) {
$join->on('system_notifications.id', '=', 'system_notification_user.notification_id')
->where('system_notification_user.user_id', '=', $user->id);
});
// 3. Exclude personally deleted ones
$query->whereNull('system_notification_user.deleted_at');
// 4. Select needed columns (avoiding ID collision with Join)
return $query->select('system_notifications.*', 'system_notification_user.read_at as personal_read_at')
->latest('system_notifications.created_at')
->skip($offset)
->take($limit)
->get();
}
/**
* Get personal unread count.
*/
public function getUnreadCount($user)
{
$query = $this->model->query();
// Target filtering hierarchical
$authorizedRecipients = $this->getAuthorizedRecipients($user);
$query->whereIn('recipient', $authorizedRecipients);
// Join to check read/deleted status
$query->leftJoin('system_notification_user', function ($join) use ($user) {
$join->on('system_notifications.id', '=', 'system_notification_user.notification_id')
->where('system_notification_user.user_id', '=', $user->id);
});
// Unread = (Global read_at is null AND Personal read_at is null) AND not deleted
$query->whereNull('system_notification_user.read_at')
->whereNull('system_notification_user.deleted_at');
return $query->count();
}
/**
* Personal Mark As Read
*/
public function markAsRead($notificationId, $userId)
{
\DB::table('system_notification_user')->updateOrInsert(
['notification_id' => $notificationId, 'user_id' => $userId],
['read_at' => now(), 'updated_at' => now()]
);
}
/**
* Personal Delete (Hide)
*/
public function personalDelete($notificationId, $userId)
{
\DB::table('system_notification_user')->updateOrInsert(
['notification_id' => $notificationId, 'user_id' => $userId],
['deleted_at' => now(), 'updated_at' => now()]
);
}
/**
* Mark ALL visible notifications as read for this user.
*/
public function markAllAsReadForUser($user)
{
$authorizedRecipients = $this->getAuthorizedRecipients($user);
// 1. Get IDs of notifications that are visible to user but either:
// - Have no entry in pivot (meaning unread)
// - Have an entry with read_at IS NULL and deleted_at IS NULL
$unreadIds = $this->model->query()
->whereIn('recipient', $authorizedRecipients)
->leftJoin('system_notification_user', function ($join) use ($user) {
$join->on('system_notifications.id', '=', 'system_notification_user.notification_id')
->where('system_notification_user.user_id', '=', $user->id);
})
->whereNull('system_notification_user.read_at')
->whereNull('system_notification_user.deleted_at')
->pluck('system_notifications.id');
if ($unreadIds->isEmpty()) {
return;
}
// 2. Perform batch updateOrInsert for each (or just loop if small, but let's stick to markAsRead helper)
foreach ($unreadIds as $id) {
$this->markAsRead($id, $user->id);
}
}
}
@@ -0,0 +1,49 @@
<?php
namespace App\Repositories;
use App\Models\SystemSetting;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Schema;
class SystemSettingRepository
{
public function tableExists(): bool
{
return Schema::hasTable('system_settings');
}
public function all(): Collection
{
return SystemSetting::query()->get();
}
public function allPublic(): Collection
{
return SystemSetting::query()->where('is_public', true)->get();
}
public function findByKey(string $key): ?SystemSetting
{
return SystemSetting::query()->where('key', $key)->first();
}
public function upsert(array $payload): SystemSetting
{
/** @var SystemSetting $setting */
$setting = SystemSetting::query()->updateOrCreate(
['key' => $payload['key']],
[
'value' => $payload['value'],
'type' => $payload['type'],
'group' => $payload['group'],
'is_public' => $payload['is_public'],
'description' => $payload['description'] ?? null,
'created_by' => $payload['created_by'] ?? null,
'updated_by' => $payload['updated_by'] ?? null,
]
);
return $setting;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Repositories;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
class UserRepository extends BaseRepository
{
public function __construct(User $model)
{
parent::__construct($model);
}
/**
* Get users by role.
*
* @return Collection
*/
public function getByRole(string $role)
{
return $this->model->role($role)->get();
}
/**
* Search users by name or email.
*
* @return Collection
*/
public function search(string $query)
{
return $this->model->where('name', 'like', "%{$query}%")
->orWhere('email', 'like', "%{$query}%")
->get();
}
}