Skip to content

Dev#505

Merged
dbarzin merged 9 commits intomainfrom
dev
Aug 14, 2025
Merged

Dev#505
dbarzin merged 9 commits intomainfrom
dev

Conversation

@dbarzin
Copy link
Copy Markdown
Owner

@dbarzin dbarzin commented Aug 14, 2025

Summary by CodeRabbit

  • New Features

    • DKIM support and SMTP settings now managed via app configuration.
    • Improved social login handling for role and language assignment.
    • Action types show translated, color‑coded labels in the Close view.
    • Performance improvement: measures are loaded more efficiently for notifications.
  • Bug Fixes

    • Corrected default values in actions migration.
  • Chores

    • Removed legacy User Group Excel export endpoint.
    • Application version updated to 2025.08.13.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Switches SMTP/DKIM sourcing to config(), updates Control API to sync actions/documents/users/groups (removing owners), adds/clarified type hints and return types, tightens Socialite role/lang handling and signatures, fixes migration typos, switches seeders to getenv(), converts route controllers to strings, removes user-group export, and bumps versions.

Changes

Cohort / File(s) Summary
Mail config and SMTP/DKIM sourcing
app/Console/Commands/SendNotifications.php, app/Http/Controllers/ConfigurationController.php, config/mail.php
SMTP and DKIM settings now read from Laravel config() keys (mail.mailers.smtp.*, mail.dkim.*); new SMTP options (auth, secure, auto_tls) and DKIM block added; default SMTP host changed to localhost.
Control API relation syncing
app/Http/Controllers/API/ControlController.php
Replaced syncing of owners with conditional syncing of actions, documents, users, and groups in store() and update(); measures syncing preserved.
Social authentication updates
app/Http/Controllers/SocialiteController.php
Added imports and provider config handling (role_claim, default_role); several methods gained return types (create_user: User, get_user_login: string, get_user_role: string, get_user_langage: string); role and language logic refactored.
Middleware typing and docs cleanup
app/Http/Middleware/Authenticate.php, app/Http/Kernel.php, app/Http/Middleware/CheckForMaintenanceMode.php, app/Http/Middleware/EncryptCookies.php, app/Http/Middleware/TrimStrings.php, app/Http/Middleware/TrustProxies.php, app/Http/Middleware/VerifyCsrfToken.php
Removed various PHPDoc blocks; Authenticate::redirectTo signature now typed (`\Illuminate\Http\Request $request): string
Model typing and minor style
app/Models/Measure.php, app/Models/Action.php, app/Models/Document.php
Measure adds BelongsTo import and return types (domain(): BelongsTo, isActive(): bool, isDisabled(): bool); Action::history() tightened comparisons and safer key update; Document formatting tweaks.
Exports adjustments
app/Exports/MeasuresExport.php, app/Exports/DomainsExport.php
MeasuresExport::query() now declares : Builder; removed unused Domain import from DomainsExport.
Controllers minor edits & formatting
app/Http/Controllers/ActionController.php, app/Http/Controllers/ControlController.php, app/Http/Controllers/DocumentController.php, app/Http/Controllers/DomainController.php, app/Http/Controllers/MeasureController.php, app/Http/Controllers/MeasureImportController.php
Mostly formatting, doc/comment changes, minor import removals; MeasureImportController adds file_exists() check before unlink(); core logic preserved.
User groups export removal
app/Http/Controllers/UserGroupController.php
Removed Excel export method and Excel import; export endpoint removed.
Routes controller references
routes/api.php
Converted Route::apiResource controller references from class constants to controller names as strings within the auth:api group; removed explicit Route import.
Seeders env retrieval and CSV indexing
database/seeders/AttributeSeeder.php, database/seeders/DomainSeeder.php, database/seeders/MeasureSeeder.php
Replaced env('LANG', ...) with getenv('LANG') (and normalized locale in DomainSeeder); switched CSV access from string indices ($data['0']) to numeric indices ($data[0]); DomainSeeder inserts via DB::table rather than Eloquent.
Migration fix
database/migrations/2024_11_06_123808_add_actions.php
Fixed typo dafault(0)default(0) for criticity and status columns.
Providers typing
app/Providers/AuthServiceProvider.php, app/Providers/EventServiceProvider.php
boot() methods now declare : void; docblocks removed.
View label rendering
resources/views/actions/close.blade.php
Replaced raw numeric action.type output with translated, color-styled labels for types 1–4.
Versioning/metadata
package.json, version.txt
Version updated from 2025.08.11 to 2025.08.13.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Provider as OAuth Provider
  participant App as SocialiteController
  participant DB as Database
  User->>App: OAuth callback
  App->>Provider: Retrieve profile
  Provider-->>App: Profile (email, claims)
  App->>App: Resolve role via role_claim/default_role (config)
  App->>DB: Find or create user (create_user returns User)
  DB-->>App: User record
  App->>User: Login or redirect
Loading
sequenceDiagram
  actor Client
  participant API as ControlController (API)
  participant DB as Database
  Client->>API: POST/PUT control payload
  API->>DB: Create/Update control
  API->>DB: Sync measures
  alt payload contains relations
    API->>DB: Conditionally sync actions/documents/users/groups
  end
  DB-->>API: Relations persisted
  API-->>Client: JSON response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I twitch my whiskers at configs moved,
DKIM tucked in, SMTP approved.
Controls now pair with groups and more,
Social roles mapped from provider core.
Seeds sniff LANG where breezes drift—
A tiny bump, a version lift. 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between cbf49f1 and de3734b.

📒 Files selected for processing (5)
  • app/Console/Commands/SendNotifications.php (3 hunks)
  • app/Http/Controllers/ConfigurationController.php (2 hunks)
  • app/Http/Controllers/MeasureImportController.php (1 hunks)
  • app/Models/Action.php (1 hunks)
  • database/seeders/DomainSeeder.php (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🔭 Outside diff range comments (13)
app/Http/Controllers/SocialiteController.php (3)

126-150: create_user() declares User return type but returns null on failure; also store a hashed placeholder password.

  • Returning null violates the declared return type and will raise a TypeError. Bubble the exception instead (it’s already caught in callback()).
  • Passwords should be hashed even if not used for auth; storing a random plaintext token is a security smell.
-        // TODO allow null password
-        $user->password = bin2hex(random_bytes(32));
+        // TODO allow null password
+        // Store a non-guessable, hashed placeholder since the account is OIDC-managed
+        $user->password = Hash::make(bin2hex(random_bytes(32)));
@@
-        } catch(QueryException $exception) {
-            Log::debug($exception->getMessage());
-            Log::error('Unable to create user');
-            return null;
-        }
+        } catch (QueryException $exception) {
+            Log::error('Unable to create user', ['exception' => $exception]);
+            throw $exception;
+        }

Additionally required import (outside this hunk):

use Illuminate\Support\Facades\Hash;

If you want, I can follow up to implement the “allow null password” TODO end-to-end (DB default, model casts/validation).


196-203: Guarantee string return type in get_user_login().

Cast the values to string to avoid potential TypeError if the provider returns a non-string id/claim.

-        if ($socialite_user->offsetExists('preferred_username')) {
-            return $socialite_user->offsetGet('preferred_username');
-        }
-        return $socialite_user->id;
+        if ($socialite_user->offsetExists('preferred_username')) {
+            return (string) $socialite_user->offsetGet('preferred_username');
+        }
+        return (string) $socialite_user->id;

210-226: get_user_role() declares string but may return null; also no validation of default_role. Normalize and ensure a valid mapping is always returned.

Current code returns null when no valid role/default is provided, which contradicts the string return type and will explode at runtime. It also doesn’t validate that default_role exists in ROLES_MAP.

Refactor to:

  • Normalize role values (trim/lowercase).
  • Validate both the claim-derived role and default_role.
  • Fall back to a safe default ('user') when needed.
  • Keep the string return type contract.
-    private function get_user_role(SocialiteUser $socialite_user, string $role_claim, string $default_role): string
-    {
-        $role_name = '';
-        if (! empty($role_claim)) {
-            $role_name = $this->get_claim_value($socialite_user, $role_claim);
-            Log::debug("Provided claim '{$role_claim}'='{$role_name}'");
-        }
-        if (! array_key_exists($role_name, self::ROLES_MAP)) {
-            if (! empty($default_role)) {
-                $role_name = $default_role;
-            } else {
-                Log::error("No default role set! A valid role must be provided. role='{$role_name}'");
-                return null;
-            }
-        }
-        return self::ROLES_MAP[$role_name];
-    }
+    private function get_user_role(SocialiteUser $socialite_user, string $role_claim, string $default_role): string
+    {
+        $role_name = '';
+        if (!empty($role_claim)) {
+            $value = $this->get_claim_value($socialite_user, $role_claim);
+            $role_name = is_string($value) ? strtolower(trim($value)) : '';
+            Log::debug("Provided claim '{$role_claim}'='{$role_name}'");
+        }
+
+        $candidate = $role_name;
+        if (!array_key_exists($candidate, self::ROLES_MAP)) {
+            $candidate = !empty($default_role) ? strtolower(trim($default_role)) : 'user';
+            if (!array_key_exists($candidate, self::ROLES_MAP)) {
+                Log::warning("Invalid default role '{$default_role}', falling back to 'user'");
+                $candidate = 'user';
+            }
+        }
+
+        return self::ROLES_MAP[$candidate];
+    }

Also, the docblock above still mentions returning null; please update it to match the non-null contract.

app/Http/Controllers/MeasureController.php (2)

482-507: Avoid sorting/unique inside the loop; compute once after building the array.

Sorting and uniquing per attribute iteration is O(n^2) and unnecessary. Do it once after the loop to reduce complexity and CPU.

Apply this diff:

-        foreach ($attributes as $attribute) {
-            foreach (explode(' ', $attribute->values) as $value) {
-                if (strlen($value) > 0) {
-                    array_push($values, $value);
-                }
-            }
-            sort($values);
-            $values = array_unique($values);
-        }
+        foreach ($attributes as $attribute) {
+            foreach (explode(' ', $attribute->values) as $value) {
+                if (strlen($value) > 0) {
+                    $values[] = $value;
+                }
+            }
+        }
+        $values = array_values(array_unique($values));
+        sort($values);

596-602: Fix: incorrect status filter, possible null dereference, and outdated relation usage.

  • where('status', [0,1]) is incorrect; use whereIn.
  • ->first()->id will throw on null; use value(...) or safe navigation.
  • Query filters by controls.measure_id, which is inconsistent with the many-to-many pivot used elsewhere (control_measure). Aligning to the pivot avoids silently returning null.

Apply this diff:

-        $control_id = DB::table('controls')
-            ->select('id')
-            ->where('measure_id', '=', $request->id)
-            ->where('status', [0,1])
-            ->first()
-            ->id;
+        $control_id = Control::query()
+            ->join('control_measure', 'controls.id', '=', 'control_measure.control_id')
+            ->where('control_measure.measure_id', $request->id)
+            ->whereIn('controls.status', [0, 1])
+            ->value('controls.id');

Optional: if multiple active controls are possible for a measure, consider handling the full set (pluck('controls.id')).

database/seeders/AttributeSeeder.php (1)

21-27: Harden language detection: handle unset LANG, locale variants (fr_FR), and avoid TypeError in strtolower.

getenv('LANG') may be false/null; strtolower expects string. Also LANG commonly includes region/encoding (e.g., fr_FR.UTF-8), so the current strict 'fr' comparison will pick EN incorrectly.

Apply this diff:

-        // get language
-        $lang = getenv('LANG');
-
-        // get filename
-        if (strtolower($lang)==="fr")
-            $filename="database/data/attributes.fr.csv";
-        else
-            $filename="database/data/attributes.en.csv";
+        // get language (fallback to app.locale, default to 'en'); accept locale variants like 'fr_FR'
+        $lang = strtolower((string) (getenv('LANG') ?: config('app.locale') ?: 'en'));
+        // get filename
+        if (str_starts_with($lang, 'fr')) {
+            $filename = "database/data/attributes.fr.csv";
+        } else {
+            $filename = "database/data/attributes.en.csv";
+        }

Optional: add an existence check and fail early if the CSV is missing:

if (! is_file(base_path($filename))) {
    throw new \RuntimeException("Seed file not found: {$filename}");
}
app/Models/Measure.php (2)

52-59: Use relationship for existence checks (clearer and consistent with Eloquent)

Avoid raw DB::table here; leverage the controls() relationship for readability and consistency.

-    public function isActive(): bool
-    {
-        return DB::table('controls')
-            ->where('measure_id', $this->id)
-            ->whereNull('realisation_date')
-            ->exists();
-    }
+    public function isActive(): bool
+    {
+        return $this->controls()
+            ->whereNull('realisation_date')
+            ->exists();
+    }

61-72: Clarify intent and use relationship for the disabled state

The comment doesn’t match the implementation, and using the relation reads better. This preserves behavior: true if there are controls and none are “empty” (i.e., pending).

-    // check if there is an empty control associated with this measure
+    // Check if the measure is disabled (has controls but none pending/empty)
     public function isDisabled(): bool
     {
-        return DB::table('controls')
-            ->where('measure_id', $this->id)
-            ->exists()
-        &&
-            ! DB::table('controls')
-                ->where('measure_id', $this->id)
-                ->whereNull('realisation_date')
-                ->exists();
+        return $this->controls()->exists()
+            && ! $this->controls()
+                ->whereNull('realisation_date')
+                ->exists();
     }
database/seeders/MeasureSeeder.php (1)

22-28: LANG detection: add fallback and normalize locale codes

Same concern as in DomainSeeder: handle values like fr_BE.UTF-8 and provide a fallback when LANG is unset.

-        $lang = getenv('LANG');
+        $lang = getenv('LANG') ?: config('app.locale', 'en');
+        $lang = strtolower(substr($lang, 0, 2));
app/Exports/MeasuresExport.php (1)

73-90: Flatten the array returned by map()

The WithMapping concern requires map() to return a single-dimensional array for each row. Currently, you’re returning an extra nested array, which will introduce unwanted structure in the exported sheet.

Please update the method in app/Exports/MeasuresExport.php to remove the outer array:

• File: app/Exports/MeasuresExport.php
• Method: map()

Proposed change:

 public function map($measure): array
 {
-    return [
-        [
-            $measure->domain->framework,
-            $measure->domain->title,
-            $measure->domain->description,
-            $measure->clause,
-            $measure->name,
-            $measure->objective,
-            $measure->attributes,
-            $measure->input,
-            $measure->model,
-            $measure->indicator,
-            $measure->action_plan,
-        ],
-    ];
+    return [
+        $measure->domain->framework,
+        $measure->domain->title,
+        $measure->domain->description,
+        $measure->clause,
+        $measure->name,
+        $measure->objective,
+        $measure->attributes,
+        $measure->input,
+        $measure->model,
+        $measure->indicator,
+        $measure->action_plan,
+    ];
 }
app/Http/Controllers/ActionController.php (3)

94-98: Critical: Wrong join/alias when filtering actions for auditees.

You’re joining action_user but referencing control_user in the join condition, and joining via controls.id. This will either error or silently mis-filter results. You likely want to filter actions assigned to the user via the action_user pivot.

Apply this diff:

-        if (Auth::User()->role === 5) {
-            $actions = $actions
-                ->leftjoin('action_user', 'controls.id', '=', 'control_user.control_id')
-                ->where('action_user.user_id', '=', Auth::User()->id);
-        }
+        if (Auth::user()->role === 5) {
+            $actions = $actions
+                ->join('action_user', 'actions.id', '=', 'action_user.action_id')
+                ->where('action_user.user_id', '=', Auth::id());
+        }

Optionally, also qualify the earlier join as ->leftJoin('controls', 'actions.control_id', '=', 'controls.id') for clarity.


289-293: Likely wrong source for measures in edit(): using action id as a control_id.

This query fetches measures by control_id = $id, but $id is the action id. Use the existing relation on the Action model to get measure IDs.

Apply this diff:

-        $measures = DB::table('control_measure')
-            ->select('measure_id')
-            ->where('control_id', $id)
-            ->pluck('measure_id')
-            ->toArray();
+        $measures = $action->measures()->pluck('measures.id')->toArray();

If you truly need control-based measures, first resolve $controlId = $action->control_id and use that explicitly.


534-537: Bug: reading 'end' date from 'start' request key.

The end date should come from the 'end' parameter.

Apply this diff:

-        $end = $request->get('start');
+        $end = $request->get('end');
🧹 Nitpick comments (18)
app/Http/Controllers/SocialiteController.php (2)

76-79: Initialize role config vars as empty strings to align with typed signatures.

These are later passed to methods typed as string. Initializing to '' instead of null avoids accidental type mismatches if control flow changes in the future.

-        $role_claim = null;
-        $default_role = null;
+        $role_claim = '';
+        $default_role = '';

232-241: Language selection looks good; minor hardening and typos.

  • Use strict in_array and cast to string to avoid surprises from provider data.
  • Nit: “langage” (method name) and “dertermine” in the doc comment are typos; consider renaming to get_user_language and fixing comments/logs (non-blocking).
-        if ($socialite_user->offsetExists('locale')) {
-            $locale = explode('-', $socialite_user->offsetGet('locale'))[0];
-            if (in_array($locale, self::LOCALES)) {
+        if ($socialite_user->offsetExists('locale')) {
+            $locale = (string) explode('-', (string) $socialite_user->offsetGet('locale'))[0];
+            if (in_array($locale, self::LOCALES, true)) {
                 return $locale;
             }
         }
         return self::LOCALES[0];

If you opt to rename the method, remember to update its two call sites in create_user() and update_user().

app/Http/Controllers/ControlController.php (1)

1359-1361: Minor readability improvement: use Carbon’s toDateString().

Functionally identical, slightly clearer API usage.

-                    ->addMonthsNoOverflow($control->periodicity)
-                    ->format('Y-m-d');
+                    ->addMonthsNoOverflow($control->periodicity)
+                    ->toDateString();
app/Http/Controllers/MeasureController.php (1)

298-301: Formatting-only change is fine; consider more robust tokenization of attributes.

Current split on a single space will leave empty tokens if there are multiple spaces. Minor, but easy to harden.

Apply this diff for resilient splitting and filtering:

-        $selectedAttributes = array_filter(
-            explode(' ', $measure->attributes ?? ''),
-            fn ($val) => strlen($val) > 0
-        );
+        $selectedAttributes = array_values(array_filter(
+            preg_split('/\s+/', trim((string) ($measure->attributes ?? ''))),
+            'strlen'
+        ));
app/Providers/AuthServiceProvider.php (1)

12-14: Type the $policies property for stricter static analysis.

Adding a native type improves clarity and tooling support.

Apply this diff:

-    protected $policies = [
+    protected array $policies = [
         // 'App\Model' => 'App\Policies\ModelPolicy',
     ];
resources/views/actions/close.blade.php (1)

33-49: Simplify with Blade @switch and add a safe default for unknown types.

Current if/elseif chain is fine but verbose and lacks a default fallback. A switch improves readability and ensures something is rendered for unexpected values.

Apply this diff:

-                    @if ($action->type==1)
-                    <p class="fg-red text-bold">
-                    {{ trans('cruds.action.types.major') }}
-                    </p>
-                    @elseif ($action->type==2)
-                    <p class="fg-orange text-bold">
-                    {{ trans('cruds.action.types.minor') }}
-                    </p>
-                    @elseif ($action->type==3)
-                    <p class="fg-yellow text-bold">
-                    {{ trans('cruds.action.types.observation') }}
-                    </p>
-                    @elseif ($action->type==4)
-                    <p class="fg-green text-bold">
-                    {{ trans('cruds.action.types.opportunity') }}
-                    </p>
-                    @endif
+                    @switch($action->type)
+                        @case(1)
+                            <p class="fg-red text-bold">{{ trans('cruds.action.types.major') }}</p>
+                            @break
+                        @case(2)
+                            <p class="fg-orange text-bold">{{ trans('cruds.action.types.minor') }}</p>
+                            @break
+                        @case(3)
+                            <p class="fg-yellow text-bold">{{ trans('cruds.action.types.observation') }}</p>
+                            @break
+                        @case(4)
+                            <p class="fg-green text-bold">{{ trans('cruds.action.types.opportunity') }}</p>
+                            @break
+                        @default
+                            <p class="text-bold">{{ $action->type }}</p>
+                    @endswitch

Optional: verify that translation keys cruds.action.types.* exist for all cases. If not, add them to avoid rendering the raw type.

app/Providers/EventServiceProvider.php (1)

14-14: Type the $listen property for better static checks.

Native typing enhances maintainability without behavior change.

Apply this diff:

-    protected $listen = [
+    protected array $listen = [
app/Http/Middleware/Authenticate.php (1)

12-17: Confirm parent signature compatibility; prefer short nullable syntax

Overriding the signature is fine in recent Laravel/PHP versions, but please ensure it remains compatible with the parent Illuminate\Auth\Middleware\Authenticate::redirectTo. Prefer the short nullable syntax for readability.

-    protected function redirectTo(\Illuminate\Http\Request $request): string|null
+    protected function redirectTo(\Illuminate\Http\Request $request): ?string

If your project is on an older Laravel/PHP version, verify that adding a parameter type and return type doesn't trigger a signature compatibility error with the parent.

database/seeders/MeasureSeeder.php (1)

35-37: Remove noisy debug logging in seeding

Log::Debug($data); will spam logs during seeding and may impact performance. Remove or guard under a verbosity flag.

app/Http/Controllers/ActionController.php (3)

6-9: Nit: Misgrouped imports (models vs exports).

The import comments don’t match the actual imports, which is confusing during maintenance. Group Action under Models and ActionsExport under Export.

Apply this diff:

-// Models
-use App\Exports\ActionsExport;
-// Export
-use App\Models\Action;
+// Models
+use App\Models\Action;
+// Export
+use App\Exports\ActionsExport;

30-34: Simplify role check with in_array and strict comparison.

Current OR-chain is verbose. Using in_array with strict mode improves readability and avoids subtle type coercions.

Apply this diff:

-            ! ((Auth::User()->role === 1) ||
-                (Auth::User()->role === 2) ||
-                (Auth::User()->role === 3) ||
-                (Auth::User()->role === 5)),
+            ! in_array(Auth::user()->role, [1, 2, 3, 5], true),

If you like this style, consider applying the same pattern across this controller for consistency.


600-601: Nit: Typo in comment.

"scrope" → "scope".

Apply this diff:

-        // Get Actions in scrope
+        // Get Actions in scope
app/Http/Controllers/DocumentController.php (1)

115-121: Stream or download the file instead of loading it fully into memory.

For large files, file_get_contents + strlen loads the entire file in memory. Prefer response()->download(...) or a streamed response to reduce memory footprint and improve performance.

Example (outside the shown lines):

$headers = [
    'Cache-Control'           => 'no-cache private',
    'Content-Description'     => 'File Transfer',
    'Content-Type'            => $document->mimetype,
    'Content-Disposition'     => 'attachment; filename="'.$document->filename.'"',
    'Content-Transfer-Encoding'=> 'binary',
];
return response()->download($path, $document->filename, $headers);
app/Http/Controllers/DomainController.php (2)

170-175: Prefer PHPDoc-style docblock (/) for export() to aid tooling.**

Switching to a plain block comment may cause some IDEs/static analyzers to ignore it.

Apply this diff:

-    /*
+    /**
      * Export the Domains in Excel
      *
      * @return \Illuminate\Http\RedirectResponse
      */

22-23: Redundant assignment to $domains (immediately overwritten).

The first query result is discarded by the second assignment. Remove the unused query for clarity.

Apply this diff:

-        $domains = DB::table('domains')->orderBy('id')->get();
-
app/Http/Controllers/API/ControlController.php (2)

31-42: Consider wrapping create + relation syncs in a transaction.

If any of the syncs fail, you can end up with a partially-created entity with inconsistent relations. Wrap the create + sync block in a DB transaction to ensure atomicity.

Example (outside this hunk):

use Illuminate\Support\Facades\DB;

DB::transaction(function () use ($request) {
    $control = Control::create($request->all());
    // ... sync measures/actions/users/groups/documents as needed
});

63-74: Deduplicate relation-sync logic across store() and update().

The four new sync blocks are duplicated in both methods. Extract into a private helper method to keep it DRY and less error-prone.

Example (outside this hunk):

private function syncRelations(Control $control, Request $request): void
{
    foreach (['measures','actions','users','groups'] as $rel) {
        if ($request->has($rel)) {
            $control->{$rel}()->sync($request->input($rel, []));
        }
    }

    if ($request->has('documents')) {
        $docIds = $request->input('documents', []);
        if (empty($docIds)) {
            $control->documents()->update(['control_id' => null]);
        } else {
            $control->documents()->whereNotIn('id', $docIds)->update(['control_id' => null]);
            \App\Models\Document::whereIn('id', $docIds)->update(['control_id' => $control->id]);
        }
    }
}

Then call syncRelations($control, $request) from both methods.

config/mail.php (1)

45-48: New keys (auth, secure, auto_tls): ensure alignment with consumers.

Your consumers should read these under mail.mailers.smtp.*. Current code in two places used mail.smtp.auth and mail.mailer.smtp.* (singular), which won’t resolve.

Either:

  • Keep these keys and ensure all code uses mail.mailers.smtp.* (what I suggested in diffs), or
  • Drop secure and rely on the existing encryption key to avoid two sources of truth.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 752060b and 37fe3aa.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • app/Console/Commands/SendNotifications.php (3 hunks)
  • app/Exceptions/Handler.php (0 hunks)
  • app/Exports/DomainsExport.php (0 hunks)
  • app/Exports/MeasuresExport.php (2 hunks)
  • app/Http/Controllers/API/ControlController.php (2 hunks)
  • app/Http/Controllers/ActionController.php (2 hunks)
  • app/Http/Controllers/ConfigurationController.php (2 hunks)
  • app/Http/Controllers/ControlController.php (1 hunks)
  • app/Http/Controllers/DocumentController.php (1 hunks)
  • app/Http/Controllers/DomainController.php (1 hunks)
  • app/Http/Controllers/MeasureController.php (1 hunks)
  • app/Http/Controllers/MeasureImportController.php (1 hunks)
  • app/Http/Controllers/SocialiteController.php (6 hunks)
  • app/Http/Controllers/UserGroupController.php (0 hunks)
  • app/Http/Kernel.php (0 hunks)
  • app/Http/Middleware/Authenticate.php (1 hunks)
  • app/Http/Middleware/CheckForMaintenanceMode.php (0 hunks)
  • app/Http/Middleware/EncryptCookies.php (0 hunks)
  • app/Http/Middleware/TrimStrings.php (0 hunks)
  • app/Http/Middleware/TrustProxies.php (0 hunks)
  • app/Http/Middleware/VerifyCsrfToken.php (0 hunks)
  • app/Models/Action.php (1 hunks)
  • app/Models/Document.php (1 hunks)
  • app/Models/Measure.php (4 hunks)
  • app/Providers/AuthServiceProvider.php (1 hunks)
  • app/Providers/EventServiceProvider.php (1 hunks)
  • config/mail.php (2 hunks)
  • database/migrations/2024_11_06_123808_add_actions.php (1 hunks)
  • database/seeders/AttributeSeeder.php (2 hunks)
  • database/seeders/DomainSeeder.php (1 hunks)
  • database/seeders/MeasureSeeder.php (2 hunks)
  • package.json (1 hunks)
  • resources/views/actions/close.blade.php (1 hunks)
  • routes/api.php (1 hunks)
  • version.txt (1 hunks)
💤 Files with no reviewable changes (9)
  • app/Http/Middleware/CheckForMaintenanceMode.php
  • app/Http/Middleware/VerifyCsrfToken.php
  • app/Http/Middleware/EncryptCookies.php
  • app/Http/Middleware/TrustProxies.php
  • app/Http/Middleware/TrimStrings.php
  • app/Exports/DomainsExport.php
  • app/Http/Kernel.php
  • app/Exceptions/Handler.php
  • app/Http/Controllers/UserGroupController.php
🧰 Additional context used
🧬 Code Graph Analysis (8)
app/Providers/EventServiceProvider.php (3)
app/Providers/AuthServiceProvider.php (1)
  • boot (19-22)
app/Providers/AppServiceProvider.php (1)
  • boot (29-63)
app/Providers/RouteServiceProvider.php (1)
  • boot (20-34)
app/Http/Controllers/API/ControlController.php (1)
app/Models/Document.php (1)
  • control (9-12)
app/Providers/AuthServiceProvider.php (3)
app/Providers/EventServiceProvider.php (1)
  • boot (23-26)
app/Providers/AppServiceProvider.php (1)
  • boot (29-63)
app/Providers/RouteServiceProvider.php (1)
  • boot (20-34)
app/Console/Commands/SendNotifications.php (1)
app/Models/Control.php (1)
  • measures (54-58)
app/Http/Controllers/ActionController.php (1)
app/Models/User.php (1)
  • User (11-80)
app/Models/Action.php (1)
app/Http/Controllers/AuditLogsController.php (1)
  • history (58-108)
database/seeders/DomainSeeder.php (1)
app/Models/Domain.php (1)
  • Domain (7-26)
app/Http/Controllers/SocialiteController.php (1)
app/Models/User.php (1)
  • User (11-80)
🔇 Additional comments (17)
app/Http/Controllers/SocialiteController.php (1)

6-6: Importing Exception is appropriate for top-level callback error handling.

No issues with this addition.

version.txt (1)

1-1: Version bump aligns with release metadata. LGTM.

The version is updated to 2025.08.13 and matches package.json. No issues.

package.json (1)

2-2: Package version updated. LGTM.

Version set to 2025.08.13, consistent with version.txt.

app/Http/Controllers/MeasureController.php (1)

303-306: Readable multi-line view return — looks good.

app/Providers/AuthServiceProvider.php (1)

19-21: Explicit void return on boot() — good alignment with other providers.

database/seeders/AttributeSeeder.php (1)

37-38: LGTM on numeric CSV indexing.

Using numeric indices with fgetcsv is idiomatic and clear.

app/Providers/EventServiceProvider.php (1)

23-26: Explicit void return on boot() — consistent with other providers.

app/Models/Measure.php (1)

7-7: Good move: explicit relation type import

Importing and using BelongsTo improves IDE support and static analysis accuracy. LGTM.

database/seeders/MeasureSeeder.php (1)

22-28: Cross-seeder ordering and integrity check

Ensure DomainSeeder runs before MeasureSeeder and that domain IDs in the CSV match actual IDs in the DB after seeding (especially if you switch to truncate()).

app/Exports/MeasuresExport.php (2)

6-6: Good: explicit Builder import for accurate return typing

This aligns with the move to explicit return types and improves static analysis.


92-95: LGTM: typed query() returning Eloquent Builder with eager-loaded relation

The explicit Builder return type and with('domain') avoid N+1s in map. All good.

app/Models/Action.php (1)

66-69: LGTM: Query builder chaining/readability

The where/orderBy/get chain is clear and idiomatic. No issues spotted here.

app/Models/Document.php (1)

7-7: LGTM: Formatting only.

No behavioral change; relation remains correct within the same namespace.

app/Http/Controllers/ConfigurationController.php (1)

117-122: Ignore the DKIM path change; the config uses mail.dkim.* at top level
The config/mail.php file defines the DKIM settings under the top-level 'dkim' key (line 76), not under mailers. Your current calls to config('mail.dkim.*') are correct—no updates needed.

Likely an incorrect or invalid review comment.

config/mail.php (2)

39-39: Changing default SMTP host to localhost may break existing deployments.

Defaulting to localhost instead of a real SMTP host (e.g., Mailgun) can silently misroute mail on environments without a local MTA.

Confirm this is intentional for your deployment profile. If not, keep the default to a well-known provider or require explicit MAIL_HOST.


76-81: DKIM config is under mail.mailers.dkim; make sure all code references the same path.

Both ConfigurationController and SendNotifications read mail.dkim.*. I proposed diffs to switch them to mail.mailers.dkim.* to match this structure.

If you’d rather keep mail.dkim.*, move this block to the top level.

routes/api.php (1)

19-24: LGTM: Controller strings under a namespaced group are valid and consistent.

Given the explicit Route::namespace('App\Http\Controllers\API'), using string controller names is fine and keeps routes concise.

If you use route caching in production, run php artisan route:cache locally to ensure no namespace resolution issues.

Comment thread app/Console/Commands/SendNotifications.php Outdated
Comment thread app/Console/Commands/SendNotifications.php Outdated
Comment thread app/Console/Commands/SendNotifications.php
Comment on lines +31 to +36
if ($request->has('actions')) {
$control->actions()->sync($request->input('actions', []));
}
if ($request->has('documents')) {
$control->documents()->sync($request->input('documents', []));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

documents()->sync() is invalid for hasMany; will throw. Replace with explicit reassignment logic.

Document.php defines Document::control() as belongsTo(Control::class), which implies Control::documents() is a hasMany. The sync() method is only available on many-to-many relations and will error here.

Replace the documents()->sync(...) with explicit updates to set/unset control_id:

-        if ($request->has('documents')) {
-            $control->documents()->sync($request->input('documents', []));
-        }
+        if ($request->has('documents')) {
+            $docIds = $request->input('documents', []);
+            if (empty($docIds)) {
+                // Detach all existing documents
+                $control->documents()->update(['control_id' => null]);
+            } else {
+                // Detach documents no longer linked
+                $control->documents()->whereNotIn('id', $docIds)->update(['control_id' => null]);
+                // Attach selected documents
+                \App\Models\Document::whereIn('id', $docIds)->update(['control_id' => $control->id]);
+            }
+        }

Do you want me to follow up with a patch that also updates destroy() to detach documents (set control_id to null) and parity across update()?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($request->has('actions')) {
$control->actions()->sync($request->input('actions', []));
}
if ($request->has('documents')) {
$control->documents()->sync($request->input('documents', []));
}
if ($request->has('actions')) {
$control->actions()->sync($request->input('actions', []));
}
if ($request->has('documents')) {
$docIds = $request->input('documents', []);
if (empty($docIds)) {
// Detach all existing documents
$control->documents()->update(['control_id' => null]);
} else {
// Detach documents no longer linked
$control->documents()->whereNotIn('id', $docIds)->update(['control_id' => null]);
// Attach selected documents
\App\Models\Document::whereIn('id', $docIds)->update(['control_id' => $control->id]);
}
}

Comment on lines +63 to 74
if ($request->has('actions')) {
$control->actions()->sync($request->input('actions', []));
}
if ($request->has('documents')) {
$control->documents()->sync($request->input('documents', []));
}
if ($request->has('users')) {
$control->users()->sync($request->input('users', []));
}
if ($request->has('groups')) {
$control->groups()->sync($request->input('groups', []));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Repeat of documents()->sync() bug in update(); use the same reassignment pattern.

Same reasoning as in store(): sync() on hasMany will fail. Use explicit updates to manage control_id.

-        if ($request->has('documents')) {
-            $control->documents()->sync($request->input('documents', []));
-        }
+        if ($request->has('documents')) {
+            $docIds = $request->input('documents', []);
+            if (empty($docIds)) {
+                $control->documents()->update(['control_id' => null]);
+            } else {
+                $control->documents()->whereNotIn('id', $docIds)->update(['control_id' => null]);
+                \App\Models\Document::whereIn('id', $docIds)->update(['control_id' => $control->id]);
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($request->has('actions')) {
$control->actions()->sync($request->input('actions', []));
}
if ($request->has('documents')) {
$control->documents()->sync($request->input('documents', []));
}
if ($request->has('users')) {
$control->users()->sync($request->input('users', []));
}
if ($request->has('groups')) {
$control->groups()->sync($request->input('groups', []));
}
if ($request->has('actions')) {
$control->actions()->sync($request->input('actions', []));
}
if ($request->has('documents')) {
$docIds = $request->input('documents', []);
if (empty($docIds)) {
$control->documents()->update(['control_id' => null]);
} else {
$control->documents()
->whereNotIn('id', $docIds)
->update(['control_id' => null]);
\App\Models\Document::whereIn('id', $docIds)
->update(['control_id' => $control->id]);
}
}
if ($request->has('users')) {
$control->users()->sync($request->input('users', []));
}
if ($request->has('groups')) {
$control->groups()->sync($request->input('groups', []));
}
🤖 Prompt for AI Agents
In app/Http/Controllers/API/ControlController.php around lines 63-74, the
update() method incorrectly calls $control->documents()->sync(...), but
documents is a hasMany so sync will fail; replace the sync call with the same
reassignment pattern used in store(): when $request->has('documents') fetch the
incoming document IDs, update any existing documents not in that list to set
control_id = null (or remove association), and update the documents in the
incoming list to set control_id = $control->id (use a single query for each
update set rather than sync). Keep the conditional ($request->has('documents'))
and ensure you operate on IDs from $request->input('documents', []).

Comment thread app/Models/Action.php Outdated
Comment on lines +27 to +28
$table->integer('criticity')->default(0);
$table->integer('status')->default(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Fixed typo in migration defaults. LGTM, but verify rollout strategy.

The switch from dafault(0) to default(0) fixes a migration breakage. Note: editing an existing migration will not affect databases where this migration has already run. If this has been applied anywhere, create a new migration to alter the columns’ defaults instead.

Would you like me to generate an additional migration (e.g., alter table to set default 0 on actions.criticity and actions.status) to safely apply this in already-migrated environments?


Ensure safe rollout of migration default fixes
Editing the original migration only applies to new deployments. For databases that have already run 2024_11_06_123808_add_actions.php, you must provide a follow-up migration to update the column defaults without data loss.

• Location to review:
– database/migrations/2024_11_06_123808_add_actions.php (lines 27–28)
• Suggested follow-up migration snippet:

Schema::table('actions', function (Blueprint $table) {
    $table->integer('criticity')->default(0)->change();
    $table->integer('status')->default(0)->change();
});

• Confirm and document your deployment strategy to apply this change in already-migrated environments.

🤖 Prompt for AI Agents
In database/migrations/2024_11_06_123808_add_actions.php around lines 27 to 28,
you only changed defaults in the original migration which affects new installs
only; create a new follow-up migration that uses Schema::table to change() the
existing columns to default(0) for both criticity and status so existing
databases are updated without data loss, and include in the PR a short
deployment plan (run migrations during maintenance window, backup DB, and verify
changes) so already-migrated environments receive the default-change safely.

Comment thread database/seeders/DomainSeeder.php Outdated
Comment thread database/seeders/DomainSeeder.php Outdated
Comment on lines +39 to +47
"domain_id" => $data[0],
"clause" => $data[1],
"name" => $data[2],
"objective" => str_replace("\\n","\n",$data[3]),
"attributes" => str_replace("\\n","\n",$data[4]),
"input" => str_replace("\\n","\n",$data[5]),
"model" => str_replace("\\n","\n",$data[6]),
"indicator" => str_replace("\\n","\n",$data[7]),
"action_plan" => str_replace("\\n","\n",$data[8]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

indicator/action_plan likely dropped due to mass-assignment constraints

App\Models\Measure fillable doesn't include indicator or action_plan, so these attributes won’t be persisted via Measure::create([...]). You’ll silently lose data.

Recommended fixes (pick one):

  • Preferred: Add missing attributes to $fillable in app/Models/Measure.php:
// app/Models/Measure.php
protected $fillable = [
    'name',
    'clause',
    'objective',
    'input',
    'attributes',
    'model',
    'indicator',
    'action_plan',
];
  • Or: Use DB::table('measures')->insert([...]) for seeding to bypass fillable.
  • Or: Temporarily Model::unguard() during seeding.

Also verify FK integrity: since domain_id is taken from CSV (column 0), it assumes DomainSeeder preserved IDs. If Domain IDs are not preserved (e.g., due to mass-assignment issues or not truncating), this will point to non-existent domains.


🏁 Script executed:

#!/bin/bash
grep -R "protected \$fillable" -n app/Models/Measure.php -A10

Length of output: 334


🏁 Script executed:

#!/bin/bash
grep -R -n -C3 "Measure::create" database/seeders/MeasureSeeder.php

Length of output: 379


🏁 Script executed:

#!/bin/bash
grep -R -n -C5 "class DomainSeeder" -n database/seeders/DomainSeeder.php

Length of output: 296


🏁 Script executed:

#!/bin/bash
# Print the contents of DomainSeeder to inspect how domains are inserted
sed -n '1,200p' database/seeders/DomainSeeder.php

Length of output: 1145


Ensure domain_id, indicator, and action_plan are mass-assignable in Measure

The MeasureSeeder uses Measure::create([...]) but the model’s $fillable only lists name, clause, objective, input, attributes, and model. As a result, domain_id, indicator, and action_plan will be ignored and never persisted.

Please update your model (app/Models/Measure.php) as follows:

// app/Models/Measure.php
 protected $fillable = [
-    'name',
-    'clause',
-    'objective',
-    'input',
-    'attributes',
-    'model',
+    'domain_id',
+    'clause',
+    'name',
+    'objective',
+    'input',
+    'attributes',
+    'model',
+    'indicator',
+    'action_plan',
 ];

Locations to review:

  • app/Models/Measure.php (lines 30–37)
  • database/seeders/MeasureSeeder.php (lines 39–47)

Alternative approaches:

  • Use DB::table('measures')->insert([...]) in the seeder to bypass $fillable.
  • Wrap your seeding in Model::unguard()/Model::reguard().
  • Verify that your DomainSeeder actually preserves IDs (consider using DB::table('domains')->truncate() or ensuring App\Models\Domain allows mass-assignment of id) so that domain_id values in the CSV resolve correctly.
🤖 Prompt for AI Agents
In database/seeders/MeasureSeeder.php around lines 39–47, the seeder writes
domain_id, indicator, and action_plan but app/Models/Measure.php (lines 30–37)
currently omits them from $fillable so those fields are ignored; update
Measure.php to include "domain_id", "indicator", and "action_plan" in the
model’s $fillable array (or alternatively change the seeder to
DB::table('measures')->insert([...]) or wrap the seeding with
Model::unguard()/Model::reguard()), and also verify your DomainSeeder preserves
expected IDs (or allow mass-assignment of Domain id) so domain_id values from
the CSV resolve correctly.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
app/Console/Commands/SendNotifications.php (2)

93-97: Eliminate N+1 queries; iterate the already-loaded relation.

Calling $control->measures()->get() inside the loop causes an N+1. Iterate the relation collection instead, and eager load it in the controls query to fetch all measures in one go.

Apply this diff to the loop:

-                        foreach ($control->measures()->get() as $measure) {
+                        foreach ($control->measures as $measure) {

Additionally (outside this hunk), update the $controls query to select distinct controls and eager load measures to avoid duplicates caused by joins:

$controls = Control::where('status', 0)
    ->leftJoin('control_user', 'controls.id', '=', 'control_user.control_id')
    ->leftJoin('control_user_group', 'controls.id', '=', 'control_user_group.control_id')
    ->leftJoin('user_user_group', 'control_user_group.user_group_id', '=', 'user_user_group.user_group_id')
    ->where(function ($query) use ($user) {
        $query->where('control_user.user_id', '=', $user->id)
              ->orWhere('user_user_group.user_id', '=', $user->id);
    })
    ->where('plan_date', '<=', Carbon::today()
        ->addDays((int) config('deming.notification.expire-delay'))->toDateString())
    ->select('controls.*')
    ->distinct()
    ->orderBy('plan_date')
    ->with('measures') // eager load to avoid N+1
    ->get();

144-147: DKIM config keys point to mail.dkim.; switch to mail.mailers.dkim. (and gate on presence).

These keys won’t resolve if config has been moved under mail.mailers.dkim.* as in the rest of the PR; result: DKIM silently not applied. Also set these only if all required DKIM values are present to avoid partial config.

Apply this diff:

-                        $mail->DKIM_domain = config('mail.dkim.domain');
-                        $mail->DKIM_private = config('mail.dkim.private');
-                        $mail->DKIM_selector = config('mail.dkim.selector');
-                        $mail->DKIM_passphrase = config('mail.dkim.passphrase');
-                        $mail->DKIM_identity = $mail->From;
+                        $dkimDomain = config('mail.mailers.dkim.domain');
+                        $dkimPrivate = config('mail.mailers.dkim.private');
+                        $dkimSelector = config('mail.mailers.dkim.selector');
+                        $dkimPassphrase = config('mail.mailers.dkim.passphrase');
+                        if ($dkimDomain && $dkimPrivate && $dkimSelector) {
+                            $mail->DKIM_domain = $dkimDomain;
+                            $mail->DKIM_private = $dkimPrivate;
+                            $mail->DKIM_selector = $dkimSelector;
+                            $mail->DKIM_passphrase = $dkimPassphrase;
+                            $mail->DKIM_identity = $mail->From;
+                        }

Run this to verify DKIM keys used across the repo and ensure the config path matches:

#!/bin/bash
# Check occurrences of DKIM config keys and where they're defined
echo "== DKIM config usages =="
rg -n -A2 -i $'config\\([\'"]mail\\.(mailers\\.)?dkim\\.' 

echo -e "\n== mail config DKIM definitions =="
rg -n -A4 -i 'dkim' config
🧹 Nitpick comments (1)
app/Console/Commands/SendNotifications.php (1)

119-120: Map encryption to PHPMailer constants; avoid overriding SMTPAutoTLS when unset.

Small hardening: use PHPMailer’s ENCRYPTION_* constants for clarity, and only override SMTPAutoTLS if the config key is present—leaving PHPMailer’s default (true) otherwise.

-                        $mail->SMTPSecure = config('mail.mailers.smtp.encryption'); // 'tls', 'ssl', null/false
-                        $mail->SMTPAutoTLS = (bool) config('mail.mailers.smtp.auto_tls', false);
+                        $encryption = config('mail.mailers.smtp.encryption'); // 'tls' | 'ssl' | null/false
+                        $mail->SMTPSecure = $encryption === 'tls'
+                            ? PHPMailer::ENCRYPTION_STARTTLS
+                            : ($encryption === 'ssl' ? PHPMailer::ENCRYPTION_SMTPS : false);
+                        $autoTls = config('mail.mailers.smtp.auto_tls');
+                        if ($autoTls !== null) {
+                            $mail->SMTPAutoTLS = (bool) $autoTls;
+                        }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 37fe3aa and cbf49f1.

📒 Files selected for processing (1)
  • app/Console/Commands/SendNotifications.php (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/Console/Commands/SendNotifications.php (2)
app/Models/Document.php (1)
  • control (9-12)
app/Models/Control.php (1)
  • measures (54-58)
🔇 Additional comments (1)
app/Console/Commands/SendNotifications.php (1)

109-120: SMTP config read via config() with correct keys and types looks good.

Using config('mail.mailers.smtp.*') with explicit int/bool casts aligns with the PR’s move away from env() and PHPMailer’s expectations.

@dbarzin dbarzin merged commit 5614db5 into main Aug 14, 2025
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant