add services logic
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user