add services logic

This commit is contained in:
root
2026-03-11 01:38:20 -04:00
parent 182036cc41
commit 3e6c577085
47 changed files with 918 additions and 1772 deletions
@@ -0,0 +1,81 @@
<?php
namespace App\Services\Attendance;
use App\Models\AttendanceCommentTemplate;
class AttendanceCommentService
{
private ?array $templates = null;
public function commentFromScore($score, string $firstName = ''): ?string
{
if ($score === null || $score === '') {
return null;
}
$score = (float) $score;
$template = $this->templateForScore($score);
if ($template === null) {
return null;
}
$text = trim((string) ($template['template_text'] ?? ''));
if ($text === '') {
return null;
}
$name = trim($firstName) !== '' ? trim($firstName) : 'This student';
$lastChar = $name !== '' ? substr($name, -1) : '';
$namePossessive = ($name === 'This student')
? "This student's"
: ($lastChar === 's' || $lastChar === 'S' ? ($name . "'") : ($name . "'s"));
if (strpos($text, '{name}') !== false) {
return str_replace('{name}', $namePossessive, $text);
}
return $namePossessive . ' ' . $text;
}
public function templateForScore(float $score): ?array
{
$templates = $this->loadTemplates();
foreach ($templates as $template) {
$min = isset($template['min_score']) ? (float) $template['min_score'] : 0.0;
$max = isset($template['max_score']) ? (float) $template['max_score'] : 100.0;
if ($score >= $min && $score <= $max) {
return $template;
}
}
return null;
}
public function clearCache(): void
{
$this->templates = null;
}
private function loadTemplates(): array
{
if ($this->templates !== null) {
return $this->templates;
}
try {
$this->templates = AttendanceCommentTemplate::query()
->where('is_active', 1)
->orderByDesc('min_score')
->get()
->map(fn ($row) => $row->toArray())
->all();
} catch (\Throwable $e) {
$this->templates = [];
}
return $this->templates;
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Services\Auth;
use Illuminate\Support\Facades\DB;
class PermissionCheckService
{
public function hasPermission(int $userId, string $permissionName): bool
{
$permissionId = DB::table('permissions')
->where('name', $permissionName)
->value('id');
if (!$permissionId) {
return false;
}
return DB::table('user_roles')
->join('role_permissions', 'role_permissions.role_id', '=', 'user_roles.role_id')
->where('user_roles.user_id', $userId)
->whereNull('user_roles.deleted_at')
->where('role_permissions.permission_id', $permissionId)
->exists();
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Services\School;
use App\Models\Configuration;
class SemesterSelectionService
{
public function selectedTeacherSemester(): string
{
$selected = trim((string) (session()->get('teacher_scores_selected_semester') ?? ''));
if ($selected !== '') {
return $this->normalize($selected);
}
$fallback = trim((string) (session()->get('semester') ?? ''));
if ($fallback !== '') {
return $this->normalize($fallback);
}
$configSemester = trim((string) (Configuration::getConfig('semester') ?? ''));
if ($configSemester !== '') {
return $this->normalize($configSemester);
}
return 'Fall';
}
private function normalize(string $value): string
{
return ucfirst(strtolower($value));
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Services\Security;
class Pbkdf2Hasher
{
public function hash(string $password, string $algo = 'sha256', int $iterations = 100000, int $length = 64): string
{
$salt = random_bytes(16);
$saltHex = bin2hex($salt);
$derived = hash_pbkdf2($algo, $password, $salt, $iterations, $length, true);
$derivedHex = bin2hex($derived);
return implode('$', [$algo, $iterations, $saltHex, $derivedHex]);
}
public function verify(string $password, string $storedHash): bool
{
$parts = explode('$', $storedHash);
if (count($parts) !== 4) {
return false;
}
[$algo, $iterations, $saltHex, $derivedHex] = $parts;
if (strlen($saltHex) % 2 !== 0 || strlen($derivedHex) % 2 !== 0) {
return false;
}
$salt = hex2bin($saltHex);
$derived = hex2bin($derivedHex);
if ($salt === false || $derived === false) {
return false;
}
$length = strlen($derived);
$calc = hash_pbkdf2($algo, $password, $salt, (int) $iterations, $length, true);
return hash_equals($derived, $calc);
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Services\System;
use App\Models\Configuration;
class GlobalConfigService
{
public function getSemester(): ?string
{
return Configuration::getConfig('semester');
}
public function getSchoolYear(): ?string
{
return Configuration::getConfig('school_year');
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace App\Services\System;
use Carbon\CarbonImmutable;
use DateTimeInterface;
class TimeService
{
public function userTimezone(): string
{
$schoolConfig = config('School');
if (is_object($schoolConfig) && isset($schoolConfig->attendance['timezone'])) {
return (string) $schoolConfig->attendance['timezone'];
}
if (is_array($schoolConfig) && isset($schoolConfig['attendance']['timezone'])) {
return (string) $schoolConfig['attendance']['timezone'];
}
return (string) (config('app.timezone') ?: 'UTC');
}
public function serverTimezone(): string
{
return (string) (config('app.timezone') ?: 'UTC');
}
public function nowUtc(): CarbonImmutable
{
return CarbonImmutable::now('UTC');
}
public function nowLocal(?string $tz = null): CarbonImmutable
{
return CarbonImmutable::now($tz ?: $this->userTimezone());
}
public function toUtc($value, ?string $fromTz = null, string $format = 'Y-m-d H:i:s'): ?string
{
$dt = $this->parse($value, $fromTz);
if (!$dt) {
return null;
}
return $dt->setTimezone('UTC')->format($format);
}
public function toLocal($value, ?string $sourceTz = null, ?string $targetTz = null, string $format = 'Y-m-d H:i:s'): ?string
{
$dt = $this->parse($value, $sourceTz);
if (!$dt) {
return null;
}
return $dt->setTimezone($targetTz ?: $this->userTimezone())->format($format);
}
public function formatLocal($value, string $format = 'Y-m-d', ?string $sourceTz = null): string
{
$dt = $this->parse($value, $sourceTz);
if (!$dt) {
return '';
}
return $dt->setTimezone($this->userTimezone())->format($format);
}
private function parse($value, ?string $tz = null): ?CarbonImmutable
{
if ($value instanceof DateTimeInterface) {
return CarbonImmutable::instance($value);
}
$raw = trim((string) $value);
if ($raw === '') {
return null;
}
try {
return CarbonImmutable::parse($raw, $tz ?: $this->serverTimezone());
} catch (\Throwable $e) {
return null;
}
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Services\Users;
use App\Models\User;
use App\Services\School\AccountEventService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class DeleteUnverifiedUserService
{
public function __construct(private AccountEventService $accountEvents)
{
}
public function deleteAfterTimeout(int $userId, int $timeoutSeconds = 86400): array
{
$user = User::query()->find($userId);
if (!$user) {
return ['ok' => false, 'reason' => 'not_found'];
}
if ((int) ($user->is_verified ?? 0) === 1) {
return ['ok' => false, 'reason' => 'verified'];
}
$lastActivity = $user->updated_at ?? $user->created_at;
if (!$lastActivity) {
return ['ok' => false, 'reason' => 'no_timestamp'];
}
$cutoff = now()->subSeconds($timeoutSeconds);
if ($lastActivity > $cutoff) {
return ['ok' => false, 'reason' => 'not_expired'];
}
$userPayload = $user->toArray();
DB::transaction(function () use ($userId, $user): void {
DB::table('user_roles')->where('user_id', $userId)->delete();
$user->delete();
});
$this->accountEvents->deleteUnverifiedUser($userPayload);
Log::info('Deleted unverified user after timeout.', ['user_id' => $userId]);
return ['ok' => true, 'deleted' => true, 'user_id' => $userId];
}
}