Files
Investidor-V2/app/Http/Controllers/InvestmentWorkspaceController.php
T
nala 250ac35fd1
tests / ci (push) Canceled after 0s
first commit
2026-09-28 10:01:09 +02:00

160 lines
9.3 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\InvestmentSubmission;
use App\Models\User;
use App\Services\InvestmentWorkspace;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Inertia\Response;
class InvestmentWorkspaceController extends Controller
{
public function profile(Request $request, InvestmentWorkspace $workspace): Response|JsonResponse
{
$data = ['account' => $workspace->account($request->user()->id), 'companies' => DB::table('investment_company_declarations')
->where('user_id', $request->user()->id)->orderByDesc('created_at')->get(['id', 'version', 'payload'])->map(function ($company) {
$company->payload = json_decode(Crypt::decryptString($company->payload), true, 512, JSON_THROW_ON_ERROR);
return $company;
})];
return $request->expectsJson() ? response()->json($data) : Inertia::render('investment/profile', $data);
}
public function saveProfile(Request $request): RedirectResponse|JsonResponse
{
$data = $request->validate(['version' => ['required', 'integer', 'min:0'],
'payload' => ['required', 'array:phone,country,sector,experience,investment_capacity'],
'payload.phone' => ['nullable', 'string', 'max:30'], 'payload.country' => ['nullable', 'string', 'max:100'],
'payload.sector' => ['nullable', 'string', 'max:150'], 'payload.experience' => ['nullable', 'string', 'max:2000'],
'payload.investment_capacity' => ['nullable', 'string', 'max:1000']]);
$version = DB::transaction(function () use ($request, $data) {
DB::table('users')->where('id', $request->user()->id)->lockForUpdate()->firstOrFail();
$row = DB::table('investment_accounts')->where('user_id', $request->user()->id)->first();
abort_unless((int) ($row->version ?? 0) === (int) $data['version'], 409, 'Perfil alterado. Recarregue.');
$version = (int) $data['version'] + 1;
DB::table('investment_accounts')->updateOrInsert(['user_id' => $request->user()->id], ['version' => $version,
'payload' => Crypt::encryptString(json_encode($data['payload'], JSON_THROW_ON_ERROR)), 'created_at' => $row->created_at ?? now(), 'updated_at' => now()]);
$this->audit($request, 'profile_saved', 'investment_accounts', null, ['version' => $version]);
return $version;
});
return $request->expectsJson() ? response()->json(['version' => $version]) : back()->with('success', 'Perfil de investimento guardado.');
}
public function company(Request $request, ?string $id = null): RedirectResponse|JsonResponse
{
$data = $request->validate(['version' => ['required', 'integer', 'min:0'],
'payload' => ['required', 'array:name,country,registration,sector,role,description'],
'payload.name' => ['required', 'string', 'min:2', 'max:200'], 'payload.country' => ['required', 'string', 'max:100'],
'payload.registration' => ['nullable', 'string', 'max:100'], 'payload.sector' => ['nullable', 'string', 'max:150'],
'payload.role' => ['required', 'string', 'max:150'], 'payload.description' => ['nullable', 'string', 'max:2000']]);
$result = DB::transaction(function () use ($request, $id, $data) {
DB::table('users')->where('id', $request->user()->id)->lockForUpdate()->firstOrFail();
$row = $id ? DB::table('investment_company_declarations')->where('id', $id)->where('user_id', $request->user()->id)->first() : null;
abort_if($id && ! $row, 404);
abort_unless((int) ($row->version ?? 0) === (int) $data['version'], 409, 'Declaração alterada. Recarregue.');
abort_if(! $id && DB::table('investment_company_declarations')->where('user_id', $request->user()->id)->count() >= 20, 422, 'Máximo de vinte empresas declaradas.');
$id ??= (string) Str::uuid();
$version = (int) $data['version'] + 1;
DB::table('investment_company_declarations')->updateOrInsert(['id' => $id], ['user_id' => $request->user()->id, 'version' => $version,
'payload' => Crypt::encryptString(json_encode($data['payload'], JSON_THROW_ON_ERROR)), 'created_at' => $row->created_at ?? now(), 'updated_at' => now()]);
$this->audit($request, 'company_declared', 'investment_company_declarations', $id, ['version' => $version]);
return ['id' => $id, 'version' => $version];
});
return $request->expectsJson() ? response()->json($result) : back()->with('success', 'Declaração de empresa guardada.');
}
public function notifications(Request $request): Response|JsonResponse
{
$query = DB::table('investment_notifications')->where('user_id', $request->user()->id);
if (! $request->user()->can('manage-portal')) {
$query->whereIn('submission_id', InvestmentSubmission::where('user_id', $request->user()->id)->select('id'));
}
$data = ['unread' => (clone $query)->whereNull('read_at')->count(), 'notifications' => $query->orderByDesc('created_at')->paginate(20)->through(function ($row) {
$row->payload = json_decode(Crypt::decryptString($row->payload), true, 512, JSON_THROW_ON_ERROR);
return $row;
})];
return $request->expectsJson() ? response()->json($data) : Inertia::render('investment/notifications', $data);
}
public function readNotification(Request $request, string $id): RedirectResponse|JsonResponse
{
$row = DB::table('investment_notifications')->where('id', $id)->where('user_id', $request->user()->id)->first();
abort_unless($row !== null, 404);
$this->submission($request, $row->submission_id);
DB::table('investment_notifications')->where('id', $id)->whereNull('read_at')->update(['read_at' => now()]);
return $request->expectsJson() ? response()->json(['read' => true]) : back();
}
public function message(Request $request, string $id, InvestmentWorkspace $workspace): RedirectResponse|JsonResponse
{
$data = $request->validate(['client_key' => ['required', 'uuid'], 'body' => ['required', 'string', 'min:3', 'max:5000']]);
$messageId = DB::transaction(function () use ($request, $id, $data, $workspace) {
$submission = $this->submission($request, $id, true);
abort_unless($submission->status === 'submitted', 409, 'Submeta a manifestação antes de enviar mensagens.');
$key = ['submission_id' => $id, 'actor_id' => $request->user()->id, 'client_key' => $data['client_key']];
$existing = DB::table('investment_messages')->where($key)->first();
if ($existing) {
abort_unless(hash_equals(Crypt::decryptString($existing->body), $data['body']), 409, 'Chave já usada para outra mensagem.');
return $existing->id;
}
$isOwner = (int) $submission->user_id === $request->user()->id;
$messageId = (string) Str::uuid();
DB::table('investment_messages')->insert([...$key, 'id' => $messageId, 'sender_role' => $isOwner ? 'investor' : 'support',
'body' => Crypt::encryptString($data['body']), 'created_at' => now()]);
$recipients = $isOwner ? User::whereIn(DB::raw('lower(email)'), config('portal.managers', []))->whereNotNull('email_verified_at')->pluck('id')->all() : [$submission->user_id];
foreach ($recipients as $recipient) {
if ((int) $recipient !== $request->user()->id) {
$workspace->notify((int) $recipient, $submission, 'message', $messageId);
}
}
$this->audit($request, 'message_sent', 'investment_messages', $messageId);
return $messageId;
});
return $request->expectsJson() ? response()->json(['id' => $messageId]) : back()->with('success', 'Mensagem registada na conversa.');
}
public function revision(Request $request, string $id, int $version): JsonResponse
{
$this->submission($request, $id);
$revision = DB::table('investment_submission_revisions')->where('submission_id', $id)->where('version', $version)->first();
abort_unless($revision !== null, 404);
return response()->json(['version' => $version, 'snapshot' => json_decode(Crypt::decryptString($revision->snapshot), true, 512, JSON_THROW_ON_ERROR)]);
}
private function submission(Request $request, string $id, bool $lock = false): InvestmentSubmission
{
$query = InvestmentSubmission::whereKey($id);
if (! $request->user()->can('manage-portal')) {
$query->where('user_id', $request->user()->id);
}
return ($lock ? $query->lockForUpdate() : $query)->firstOrFail();
}
/** @param array<string,mixed> $metadata */
private function audit(Request $request, string $action, string $entity, ?string $id, array $metadata = []): void
{
DB::table('audit_events')->insert(['source_system' => 'investment-workspace', 'actor_user_id' => $request->user()->id,
'entity_type' => $entity, 'entity_id' => $id, 'action' => $action, 'after_data' => json_encode($metadata, JSON_THROW_ON_ERROR)]);
}
}