90f9857b06
API CI/CD / Validate (composer + pint) (push) Successful in 3m7s
API CI/CD / Test (PHPUnit) (push) Failing after 5m46s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
246 lines
9.1 KiB
PHP
246 lines
9.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Messaging;
|
|
|
|
use App\Http\Controllers\Api\Core\BaseApiController;
|
|
use App\Http\Requests\Whatsapp\WhatsappInviteSendRequest;
|
|
use App\Http\Requests\Whatsapp\WhatsappLinkIndexRequest;
|
|
use App\Http\Requests\Whatsapp\WhatsappLinkStoreRequest;
|
|
use App\Http\Requests\Whatsapp\WhatsappLinkUpdateRequest;
|
|
use App\Http\Requests\Whatsapp\WhatsappMembershipUpdateRequest;
|
|
use App\Http\Requests\Whatsapp\WhatsappParentContactsByClassRequest;
|
|
use App\Http\Requests\Whatsapp\WhatsappParentContactsRequest;
|
|
use App\Http\Resources\Whatsapp\WhatsappClassSectionContactResource;
|
|
use App\Http\Resources\Whatsapp\WhatsappGroupLinkCollection;
|
|
use App\Http\Resources\Whatsapp\WhatsappGroupLinkResource;
|
|
use App\Http\Resources\Whatsapp\WhatsappParentContactResource;
|
|
use App\Services\Whatsapp\WhatsappContactService;
|
|
use App\Services\Whatsapp\WhatsappContextService;
|
|
use App\Services\Whatsapp\WhatsappInviteService;
|
|
use App\Services\Whatsapp\WhatsappLinkService;
|
|
use App\Services\Whatsapp\WhatsappMembershipService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class WhatsappController extends BaseApiController
|
|
{
|
|
public function __construct(
|
|
private WhatsappLinkService $linkService,
|
|
private WhatsappContactService $contactService,
|
|
private WhatsappInviteService $inviteService,
|
|
private WhatsappMembershipService $membershipService,
|
|
private WhatsappContextService $context
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
public function index(WhatsappLinkIndexRequest $request): JsonResponse
|
|
{
|
|
$payload = $request->validated();
|
|
$schoolYear = $this->context->schoolYear($payload['school_year'] ?? null);
|
|
$semester = array_key_exists('semester', $payload)
|
|
? (string) ($payload['semester'] ?? '')
|
|
: $this->context->semester();
|
|
|
|
$payload['school_year'] = $schoolYear;
|
|
$payload['semester'] = $semester;
|
|
$payload['per_page'] = max((int) ($payload['per_page'] ?? 200), 200);
|
|
|
|
$links = $this->linkService->paginate($payload);
|
|
$collection = new WhatsappGroupLinkCollection($links);
|
|
$linkRows = $collection->toArray($request);
|
|
|
|
$sectionsRaw = $this->contactService->listParentContactsByClass($schoolYear, $semester);
|
|
$sections = WhatsappClassSectionContactResource::collection($sectionsRaw)->resolve($request);
|
|
|
|
$linksBySection = [];
|
|
foreach ($linkRows as $link) {
|
|
$sid = (int) ($link['class_section_id'] ?? 0);
|
|
if ($sid > 0) {
|
|
$linksBySection[(string) $sid] = $link;
|
|
}
|
|
}
|
|
|
|
$parents = [];
|
|
foreach ($sections as $section) {
|
|
foreach (($section['parents'] ?? []) as $parent) {
|
|
$primaryId = (int) ($parent['primary_id'] ?? 0);
|
|
if ($primaryId > 0 && ! isset($parents['primary:'.$primaryId])) {
|
|
[$lastname, $firstname] = $this->splitDisplayName((string) ($parent['primary_name'] ?? ''));
|
|
$parents['primary:'.$primaryId] = [
|
|
'id' => $primaryId,
|
|
'firstname' => $firstname,
|
|
'lastname' => $lastname,
|
|
'email' => (string) ($parent['primary_email'] ?? ''),
|
|
'source' => 'primary',
|
|
];
|
|
}
|
|
|
|
$secondId = (int) ($parent['second_id'] ?? 0);
|
|
if ($secondId > 0 && ! isset($parents['second:'.$secondId])) {
|
|
[$lastname, $firstname] = $this->splitDisplayName((string) ($parent['second_name'] ?? ''));
|
|
$parents['second:'.$secondId] = [
|
|
'id' => $secondId,
|
|
'firstname' => $firstname,
|
|
'lastname' => $lastname,
|
|
'email' => (string) ($parent['second_email'] ?? ''),
|
|
'source' => 'parents',
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->success([
|
|
'schoolYear' => $schoolYear,
|
|
'semester' => $semester,
|
|
'links' => $linkRows,
|
|
'linksBySection' => $linksBySection,
|
|
'sections' => $sections,
|
|
'parents' => array_values($parents),
|
|
'meta' => $collection->with($request)['meta'],
|
|
]);
|
|
}
|
|
|
|
public function show(int $linkId): JsonResponse
|
|
{
|
|
$link = $this->linkService->findOrFail($linkId);
|
|
|
|
return $this->success([
|
|
'link' => new WhatsappGroupLinkResource($link),
|
|
]);
|
|
}
|
|
|
|
public function store(WhatsappLinkStoreRequest $request): JsonResponse
|
|
{
|
|
$link = $this->linkService->upsert($request->validated());
|
|
|
|
$code = $link->wasRecentlyCreated ? Response::HTTP_CREATED : Response::HTTP_OK;
|
|
|
|
return $this->success([
|
|
'link' => new WhatsappGroupLinkResource($link),
|
|
], 'WhatsApp group link saved.', $code);
|
|
}
|
|
|
|
public function update(WhatsappLinkUpdateRequest $request, int $linkId): JsonResponse
|
|
{
|
|
$link = $this->linkService->update($linkId, $request->validated());
|
|
|
|
return $this->success([
|
|
'link' => new WhatsappGroupLinkResource($link),
|
|
], 'WhatsApp group link updated.');
|
|
}
|
|
|
|
public function destroy(int $linkId): JsonResponse
|
|
{
|
|
$this->linkService->delete($linkId);
|
|
|
|
return $this->success(null, 'WhatsApp group link deleted.');
|
|
}
|
|
|
|
public function parentContacts(WhatsappParentContactsRequest $request): JsonResponse
|
|
{
|
|
$payload = $request->validated();
|
|
$schoolYear = $this->context->schoolYear($payload['school_year'] ?? null);
|
|
$semester = array_key_exists('semester', $payload)
|
|
? (string) ($payload['semester'] ?? '')
|
|
: $this->context->semester();
|
|
|
|
$contacts = $this->contactService->listParentContacts($schoolYear, $semester);
|
|
|
|
return $this->success([
|
|
'schoolYear' => $schoolYear,
|
|
'semester' => $semester,
|
|
'contacts' => WhatsappParentContactResource::collection($contacts),
|
|
]);
|
|
}
|
|
|
|
public function parentContactsByClass(WhatsappParentContactsByClassRequest $request): JsonResponse
|
|
{
|
|
$payload = $request->validated();
|
|
$schoolYear = $this->context->schoolYear($payload['school_year'] ?? null);
|
|
$semester = array_key_exists('semester', $payload)
|
|
? (string) ($payload['semester'] ?? '')
|
|
: $this->context->semester();
|
|
|
|
$sections = $this->contactService->listParentContactsByClass($schoolYear, $semester);
|
|
|
|
return $this->success([
|
|
'class_sections' => WhatsappClassSectionContactResource::collection($sections),
|
|
]);
|
|
}
|
|
|
|
public function sendInvites(WhatsappInviteSendRequest $request): JsonResponse
|
|
{
|
|
try {
|
|
$result = $this->inviteService->send($request->validated());
|
|
} catch (\Throwable $e) {
|
|
Log::error('WhatsApp invite send failed: '.$e->getMessage());
|
|
|
|
return $this->error('Invite processing failed.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
if (! ($result['ok'] ?? false)) {
|
|
return $this->error($result['message'] ?? 'Invite processing failed.', Response::HTTP_UNPROCESSABLE_ENTITY);
|
|
}
|
|
|
|
return $this->success([
|
|
'triggered' => (int) ($result['triggered'] ?? 0),
|
|
], 'Invite processing started.');
|
|
}
|
|
|
|
public function updateMembership(WhatsappMembershipUpdateRequest $request): JsonResponse
|
|
{
|
|
$userId = $this->authenticatedUserIdOrUnauthorized();
|
|
if ($userId instanceof JsonResponse) {
|
|
return $userId;
|
|
}
|
|
|
|
try {
|
|
$result = $this->membershipService->updateMembership($request->validated(), $userId);
|
|
} catch (\Throwable $e) {
|
|
Log::error('WhatsApp membership update failed: '.$e->getMessage());
|
|
|
|
return $this->error('Failed to save membership.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
if (! ($result['ok'] ?? false)) {
|
|
$code = str_contains((string) ($result['message'] ?? ''), 'missing')
|
|
? Response::HTTP_INTERNAL_SERVER_ERROR
|
|
: Response::HTTP_UNPROCESSABLE_ENTITY;
|
|
|
|
return $this->error($result['message'] ?? 'Failed to save membership.', $code);
|
|
}
|
|
|
|
return $this->success([
|
|
'saved' => $result['saved'] ?? [],
|
|
'term' => $result['term'] ?? [],
|
|
], 'Membership saved.');
|
|
}
|
|
|
|
private function splitDisplayName(string $name): array
|
|
{
|
|
$name = trim($name);
|
|
if ($name === '') {
|
|
return ['', ''];
|
|
}
|
|
|
|
if (str_contains($name, ',')) {
|
|
[$last, $first] = array_pad(array_map('trim', explode(',', $name, 2)), 2, '');
|
|
return [$last, $first];
|
|
}
|
|
|
|
return [$name, ''];
|
|
}
|
|
|
|
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
|
|
{
|
|
$userId = (int) (auth()->id() ?? 0);
|
|
if ($userId <= 0) {
|
|
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
return $userId;
|
|
}
|
|
}
|