Skip to content

Comments

Feature: Greenlight3 import#2665

Open
pizkaz wants to merge 8 commits intoTHM-Health:developfrom
pizkaz:feat/greenlight3-import
Open

Feature: Greenlight3 import#2665
pizkaz wants to merge 8 commits intoTHM-Health:developfrom
pizkaz:feat/greenlight3-import

Conversation

@pizkaz
Copy link
Contributor

@pizkaz pizkaz commented Dec 11, 2025

Fixes #2664

Type

  • Bugfix
  • Feature
  • Documentation
  • Refactoring (e.g. Style updates, Test implementation, etc.)
  • Other (please describe):

Checklist

  • Code updated to current develop branch head
  • Passes CI checks
  • Is a part of an issue
  • Tests added for the bugfix or newly implemented feature, describe below why if not
  • Changelog is updated
  • Documentation of code and features exists

Changes

  • Added a new command import:greenlight-v3
  • Legacy room access codes can now be alphanumeric (because GL3 uses 6-chars-alphanumeric codes)
  • Added / adapted tests for new features and changed behavior (room access codes)

Other information

ToDo:

  • GL3 route handling
  • Documentation
  • e2e Test of a GL3 migration (manually)

Summary by CodeRabbit

  • New Features

    • Interactive console import for Greenlight v3 with transactional flow, prompts, progress and summaries.
  • Improvements

    • Support for legacy alphanumeric and numeric access codes in validation, display, input masks/placeholders, and moderator messages.
    • Room sharing UI and access-code formatting now handle non-numeric codes.
  • Bug Fixes

    • Access-code matching relaxed to accept non-numeric codes.
  • Tests

    • New backend unit tests and helpers for the import flow; updated/added frontend e2e tests (including duplicated block).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Greenlight v3 import Artisan command with tests/helpers, and extends access_code support to accept legacy alphanumeric codes across middleware, validation, model formatting, frontend inputs/display, and tests.

Changes

Cohort / File(s) Summary
Greenlight 3 Import Command
app/Console/Commands/ImportGreenlight3Command.php
New Artisan command to import users, rooms, and shared_accesses from a Greenlight v3 Postgres DB with interactive options and transactional commit/rollback.
Import Tests & Helpers
tests/Backend/Unit/Console/ImportGreenlight3Test.php, tests/Backend/Unit/Console/helper/Greenlight3User.php, tests/Backend/Unit/Console/helper/Greenlight3Room.php
New unit test covering the import flow and two helper classes modelling GL3 users/rooms; includes fake DB setup and comprehensive post-import assertions.
Access-code backend logic
app/Http/Middleware/RoomAuthenticate.php, app/Http/Requests/UpdateRoomSettings.php
Middleware no longer restricts access code to numeric-only; request validation updated to accept legacy 6-char alphanumeric codes or numeric codes (6/9 digits) depending on context.
Room model formatting
app/Models/Room.php
Moderator-only message now appends raw access_code when legacy_code is true; otherwise uses grouped/hyphenated formatting.
Frontend display & inputs
resources/js/components/RoomShareButton.vue, resources/js/views/RoomsView.vue
Share button and room settings input handle non-numeric access codes: raw string shown for non-numeric codes; input mask/placeholder switch to non-numeric pattern when legacy_code is true.
Backend & Frontend tests (access-code)
tests/Backend/Feature/api/v1/Room/RoomTest.php, tests/Frontend/e2e/RoomsViewGeneral.cy.js, tests/Frontend/e2e/RoomsViewSettings.cy.js
Tests updated/added to cover alphanumeric access codes (e.g., 012abc); new E2E test for GL3 access-code save payload added (duplicate test block present).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

tests, frontend

Suggested reviewers

  • SamuelWei
  • danielmachill
  • Sabr1n4W
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive While alphanumeric access code support aligns with GL3 migration, the full scope of changes affecting multiple files (RoomAuthenticate, UpdateRoomSettings, RoomShareButton, RoomsView, Room model) extends beyond the stated issue objective and warrants clarification.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the core objective of issue #2664: providing a semi-automated import command for Greenlight 3 via the new import:greenlight-v3 command for users and rooms.
Title check ✅ Passed The title 'Feature: Greenlight3 import' accurately summarizes the main change—a new import feature for Greenlight 3 data—and directly reflects the primary objective of the PR.
Description check ✅ Passed PR description follows the template structure with all required sections completed: type (Feature), checklist items marked, changes listed, and issue reference provided.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
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: 1

🧹 Nitpick comments (9)
app/Http/Middleware/RoomAuthenticate.php (1)

86-99: Access code comparison now supports non‑numeric values; consider avoiding type juggling

Switching to $room->access_code == $accessCode correctly allows alphanumeric codes from headers. If access_code is stored as a string in the DB, consider normalizing both sides to string and using strict comparison to avoid subtle PHP type coercion (e.g. "012345" vs 12345):

$accessCode = (string) $request->header('Access-Code');

if ((string) $room->access_code === $accessCode) {
    $authenticated = true;
    // ...
}

Not required for correctness given your current tests, but would make the comparison behavior more explicit and future‑proof.

app/Models/Room.php (1)

383-388: Moderator invitation code formatting now depends on legacy_code; verify GL3 behavior

Conditionally formatting the code via $this->legacy_code ? implode('-', str_split(...)) : $this->access_code is a good step toward separating legacy vs non‑legacy display.

Two things to double‑check:

  • That legacy_code is true only for the legacy formats you intend (e.g. old 6‑digit numeric codes) and not for new 6‑character alphanumeric GL3 codes, otherwise those GL3 codes will still be split into xxx-xxx here while the share button shows them unformatted.
  • That this difference from the frontend behavior (which still groups all numeric codes into chunks) is intentional; if not, consider centralizing the formatting logic so moderator messages and the share popover stay in sync.
resources/js/components/RoomShareButton.vue (1)

117-123: Formatted access code now preserves non‑numeric GL3 codes

The formattedAccessCode computed correctly avoids chunking non‑numeric codes (e.g. GL3 012abc) while preserving the existing grouped format for numeric codes. This matches the new backend behavior and room tests.

If you ever want to tighten the numeric check, you could swap isNaN for a simple regex like /^\d+$/ for more predictable behavior, but it's not required here.

tests/Backend/Unit/Console/helper/Greenlight3User.php (1)

5-27: Test helper DTO looks fine; consider typed properties for clarity

The Greenlight3User helper cleanly captures the GL3 user shape and aligns with the existing Greenlight3Room helper.

Optionally, you could add property types for quicker static feedback in tests:

public string $id;
public string $name;
public string $email;
public ?string $external_id;
public ?string $password_digest;

Not necessary for functionality, but can improve IDE support and reduce test helper misuse.

app/Console/Commands/ImportGreenlight3Command.php (2)

104-158: User import logic is reasonable; be explicit about password and role semantics

The user import behaves as expected:

  • De‑duplicates by email, mapping multiple GL3 references to the same existing user.
  • Derives authenticator from external_id and preserves password_digest for local users, while generating a random password for external (OIDC) users.
  • Sets locale and timezone from your app settings and attaches the selected default role only for non‑external users.

Two small considerations:

  • Document somewhere (e.g., in command help) that existing local users are not modified (authenticator, external_id, roles), only linked, so operators know what to expect.
  • If your Role pivot uses an automatic flag, and you want these imported roles to behave like “automatic” assignments, you may prefer attach([$defaultRole => ['automatic' => true]]) instead of bare attach($defaultRole).

169-316: Room and shared access import mapping is clear; verify option mapping and ID collision policy

The room and shared access import functions are well structured:

  • Rooms:
    • Skip import when a room with the same friendly_id already exists, and intentionally don’t add it to $roomMap so shared accesses for that ID are skipped too.
    • Require a mapped owner in $userMap, collecting failures for reporting instead of throwing.
    • Copy key options (glAnyoneCanStart, glAnyoneJoinAsModerator, glRequireAuthentication, glViewerAccessCode, guestPolicy, muteOnStart, record) into the corresponding Room fields, plus attach the chosen RoomType.
  • Shared accesses:
    • Only add memberships when both user and room were successfully imported, assigning RoomUserRole::MODERATOR via syncWithoutDetaching, which is appropriate for GL’s shared access.

A few behavioral points worth confirming:

  • For guestPolicy, mapping only ASK_MODERATOR to RoomLobby::ENABLED and treating all other values as DISABLED is a design choice; if GL3 has multiple guest policies you care about, you may want a more granular mapping later.
  • The current ID‑collision behavior (skipping rooms whose friendly ID already exists and not importing their shared accesses) is safe, but it might surprise admins who expect shared accesses to be merged for matching rooms. If that merge is ever desired, you’d need to change the continue path to still populate $roomMap.

Overall, nothing blocking, just trade‑offs to be aware of.

resources/js/views/RoomsView.vue (1)

118-136: Access-code mask/placeholder correctly handle legacy alphanumeric codes

Conditionally switching to a 6‑character mask and neutral placeholder for room.legacy_code cleanly enables GL3‑style alphanumeric access codes while preserving the existing 9‑digit format for non‑legacy rooms. The interaction with login() (dash stripping) also stays correct for both paths.

If this mask/placeholder logic ends up needed in more places (e.g. other components), consider extracting a small computed helper for reuse.

tests/Frontend/e2e/RoomsViewSettings.cy.js (1)

1239-1304: GL3 access-code settings flow is well covered

This new test exercises loading a non‑numeric "fck4fd" access code, editing settings, and asserting that the PUT payload preserves that value along with the expected flags. That gives good coverage for the GL3 access‑code scenario.

Optionally, you could add a post‑save assertion on #room-setting-access_code to confirm the UI still shows "fck4fd" after the update, mirroring the payload check.

tests/Backend/Unit/Console/helper/Greenlight3Room.php (1)

5-27: DTO is fine; consider typed properties for clarity

The helper does its job as a simple value object for mocked rows. If you want slightly stronger guarantees and better static analysis, you could add typed properties, e.g.:

class Greenlight3Room
{
    public string $id;
    public string $friendly_id;
    public string $user_id;
    public string $name;
    public bool $deleted;

    public function __construct(string $id, string $friendly_id, string $user_id, string $name, bool $deleted = false)
    {
        $this->id = $id;
        $this->friendly_id = $friendly_id;
        $this->user_id = $user_id;
        $this->name = $name;
        $this->deleted = $deleted;
    }
}

Purely a nicety; not blocking.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fcc7c0 and 2e670e0.

📒 Files selected for processing (12)
  • app/Console/Commands/ImportGreenlight3Command.php (1 hunks)
  • app/Http/Middleware/RoomAuthenticate.php (1 hunks)
  • app/Http/Requests/UpdateRoomSettings.php (1 hunks)
  • app/Models/Room.php (1 hunks)
  • resources/js/components/RoomShareButton.vue (1 hunks)
  • resources/js/views/RoomsView.vue (1 hunks)
  • tests/Backend/Feature/api/v1/Room/RoomTest.php (1 hunks)
  • tests/Backend/Unit/Console/ImportGreenlight3Test.php (1 hunks)
  • tests/Backend/Unit/Console/helper/Greenlight3Room.php (1 hunks)
  • tests/Backend/Unit/Console/helper/Greenlight3User.php (1 hunks)
  • tests/Frontend/e2e/RoomsViewGeneral.cy.js (2 hunks)
  • tests/Frontend/e2e/RoomsViewSettings.cy.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
tests/Backend/Unit/Console/ImportGreenlight3Test.php (5)
tests/Backend/Unit/Console/helper/Greenlight3Room.php (1)
  • Greenlight3Room (5-28)
tests/Backend/Unit/Console/helper/Greenlight3User.php (1)
  • Greenlight3User (5-28)
tests/Backend/Unit/Console/helper/GreenlightSharedAccess.php (1)
  • GreenlightSharedAccess (5-22)
app/Models/RoomType.php (1)
  • rooms (37-40)
app/Models/Room.php (2)
  • owner (214-217)
  • members (263-266)
tests/Backend/Unit/Console/helper/Greenlight3User.php (1)
tests/Backend/Unit/Console/helper/Greenlight3Room.php (1)
  • __construct (20-27)
app/Console/Commands/ImportGreenlight3Command.php (5)
app/Models/Role.php (2)
  • Role (15-112)
  • users (35-38)
app/Settings/GeneralSettings.php (1)
  • GeneralSettings (7-29)
app/Models/RoomType.php (1)
  • rooms (37-40)
app/Enums/RoomUserRole.php (1)
  • label (16-25)
app/Models/Room.php (2)
  • owner (214-217)
  • members (263-266)
tests/Backend/Unit/Console/helper/Greenlight3Room.php (1)
tests/Backend/Unit/Console/helper/Greenlight3User.php (1)
  • __construct (20-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Docker Build
  • GitHub Check: Backend
🔇 Additional comments (4)
tests/Backend/Feature/api/v1/Room/RoomTest.php (1)

561-566: Good coverage for legacy alphanumeric access code

The extra assertion for a 6‑character alphanumeric legacy access code (012abc) nicely exercises the new backend behavior for guests; no issues spotted.

app/Console/Commands/ImportGreenlight3Command.php (1)

35-95: Overall command flow and transaction handling look solid

The command wiring is coherent:

  • Dynamically registers a greenlight pgsql connection and reads users, rooms, and shared accesses from it.
  • Prompts for room type, optional name prefix, and default role using Laravel\Prompts.
  • Wraps all local DB writes (users, rooms, memberships) in a single transaction with a final confirm() gate, so the operator can review counts before committing.

This is a good balance between safety (rollback on failure or cancel) and usability for a one‑shot migration command.

app/Http/Requests/UpdateRoomSettings.php (1)

45-56: Verify GL3 code case handling during import and in validation

The access code rules correctly distinguish legacy 6-character vs new 9-digit codes. However, there's a potential issue with imported GL3 codes:

GL3 viewer access codes are imported without case normalization (stored as-is from the source database). When users later update room settings without changing the access code, if that code contains uppercase letters, the validation will fail because the alphanumeric legacy case enforces lowercase validation rule (line 54).

If GL3 instances ever store codes in non-lowercase format, either:

  • Normalize to lowercase during import in ImportGreenlight3Command, or
  • Apply case-insensitive comparison when detecting legacy codes to preserve and remove the lowercase rule for GL3 imports
tests/Frontend/e2e/RoomsViewGeneral.cy.js (1)

386-408: Legacy access-code E2E updated consistently to alphanumeric

Using "012abc" both for the input and the access-code header expectation matches the new legacy alphanumeric behaviour and keeps this test meaningful for GL3 imports. No issues from my side here.

@codecov
Copy link

codecov bot commented Dec 11, 2025

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.19%. Comparing base (6b6ddbe) to head (cd320d3).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
resources/js/components/RoomShareButton.vue 66.66% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #2665      +/-   ##
=============================================
+ Coverage      96.77%   97.19%   +0.41%     
=============================================
  Files            444      179     -265     
  Lines          12579     6302    -6277     
  Branches        2063     2068       +5     
=============================================
- Hits           12173     6125    -6048     
+ Misses           406      177     -229     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pizkaz pizkaz force-pushed the feat/greenlight3-import branch from 2e670e0 to c38131b Compare December 12, 2025 14:05
Copy link
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

🧹 Nitpick comments (1)
resources/js/components/RoomShareButton.vue (1)

117-123: Consider using an explicit check for all-numeric strings.

The isNaN() function performs type coercion before checking, which can lead to unexpected behavior with edge cases (e.g., empty strings, null). While the current guards prevent these cases, using an explicit regex test would be more robust and clearer.

Apply this diff to use a regex test:

 const formattedAccessCode = computed(() => {
-  return isNaN(props.room.access_code)
-    ? props.room.access_code
-    : String(props.room.access_code)
+  const code = String(props.room.access_code);
+  return /^\d+$/.test(code)
+    ? code
         .match(/.{1,3}/g)
-        .join("-");
+        .join("-")
+    : code;
 });

This explicitly checks whether the code contains only digits, making the intent clearer and avoiding type coercion quirks.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e670e0 and c38131b.

📒 Files selected for processing (8)
  • app/Http/Middleware/RoomAuthenticate.php (1 hunks)
  • app/Http/Requests/UpdateRoomSettings.php (1 hunks)
  • app/Models/Room.php (1 hunks)
  • resources/js/components/RoomShareButton.vue (1 hunks)
  • resources/js/views/RoomsView.vue (1 hunks)
  • tests/Backend/Feature/api/v1/Room/RoomTest.php (1 hunks)
  • tests/Frontend/e2e/RoomsViewGeneral.cy.js (2 hunks)
  • tests/Frontend/e2e/RoomsViewSettings.cy.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • tests/Backend/Feature/api/v1/Room/RoomTest.php
  • app/Http/Middleware/RoomAuthenticate.php
  • app/Models/Room.php
  • resources/js/views/RoomsView.vue
  • tests/Frontend/e2e/RoomsViewSettings.cy.js
  • tests/Frontend/e2e/RoomsViewGeneral.cy.js
  • app/Http/Requests/UpdateRoomSettings.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Backend
  • GitHub Check: Docker Build

Copy link
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: 1

🤖 Fix all issues with AI agents
In `@app/Console/Commands/ImportGreenlight3Command.php`:
- Around line 26-31: The command currently exposes the database password via the
$signature in ImportGreenlight3Command; remove the {password ...} argument from
the $signature and instead obtain the password inside handle() using Laravel's
interactive password prompt (password(...)) or fallback to an environment
variable (e.g., config or env('GREENLIGHT_DB_PASSWORD')) if present; ensure you
update any references to the removed argument in handle() to use the
prompted/fallback value and adjust the command description/help text
accordingly.
🧹 Nitpick comments (8)
app/Console/Commands/ImportGreenlight3Command.php (8)

12-17: Consider using fully qualified facade imports.

Using facade aliases (Config, DB, Hash, Str) works but importing full namespaces (e.g., Illuminate\Support\Facades\DB) improves IDE support and code clarity. This is a minor style preference.


51-53: Potential memory issue when importing large datasets.

All users, rooms, and shared accesses are loaded into memory at once. For large Greenlight installations, consider using chunked queries or lazy collections to reduce memory footprint.

♻️ Example using lazy() for memory efficiency
-        $users = DB::connection('greenlight')->table('users')->where('provider', 'greenlight')->get(['id', 'name', 'email', 'external_id', 'password_digest']);
-        $rooms = DB::connection('greenlight')->table('rooms')->get(['id', 'friendly_id', 'user_id', 'name']);
-        $sharedAccesses = DB::connection('greenlight')->table('shared_accesses')->get(['room_id', 'user_id']);
+        $users = DB::connection('greenlight')->table('users')->where('provider', 'greenlight')->lazy();
+        $rooms = DB::connection('greenlight')->table('rooms')->lazy();
+        $sharedAccesses = DB::connection('greenlight')->table('shared_accesses')->lazy();

Note: If using lazy(), you'll need to call count() separately for progress bars, or switch to indeterminate progress indicators.


37-49: Missing connection validation before querying.

If the database connection fails, the error will only surface when querying. Consider testing the connection first to provide a clearer error message.

♻️ Add connection validation
         Config::set('database.connections.greenlight', [
             'driver' => 'pgsql',
             // ... config ...
         ]);
 
+        try {
+            DB::connection('greenlight')->getPdo();
+        } catch (\Exception $e) {
+            $this->error('Failed to connect to Greenlight database: ' . $e->getMessage());
+            return 1;
+        }
+
         $users = DB::connection('greenlight')->table('users')...

212-239: N+1 query pattern: room options fetched inside loop.

Each room iteration issues a separate query to fetch room_meeting_options. For many rooms, this causes significant database overhead.

♻️ Prefetch all room options before the loop
     protected function importRooms(Collection $rooms, int $roomType, array $userMap, ?string $prefix): array
     {
         $this->line('Importing rooms');
+
+        // Prefetch all room options
+        $allRoomOptions = DB::connection('greenlight')
+            ->table('room_meeting_options')
+            ->join('meeting_options', 'meeting_option_id', '=', 'meeting_options.id')
+            ->whereIn('room_id', $rooms->pluck('id'))
+            ->get(['room_id', 'name', 'value'])
+            ->groupBy('room_id');
 
         // ... in the foreach loop:
-            $roomOptions = DB::connection('greenlight')->table('room_meeting_options')->join('meeting_options', 'meeting_option_id', '=', 'meeting_options.id')->where('room_id', $room->id)->get(['name', 'value']);
+            $roomOptions = $allRoomOptions->get($room->id, collect());

306-308: Shared access may add room owner as a member.

If a room owner also has a shared_access entry in Greenlight, they'll be added as a MODERATOR member of their own room. While not breaking, this creates redundant data since owners already have full access.

♻️ Skip if user is the room owner
             // find room object and add user as moderator to the room
             $dbRoom = Room::find($roomMap[$room]);
+            
+            // Skip if user is the room owner
+            if ($dbRoom->user_id === $userMap[$user]) {
+                $bar->advance();
+                continue;
+            }
+            
             $dbRoom->members()->syncWithoutDetaching([$userMap[$user] => ['role' => RoomUserRole::MODERATOR]]);

280-280: Missing return type declaration.

The method has no return type. Since it doesn't return anything, add : void for clarity.

-    protected function importSharedAccesses(Collection $sharedAccesses, array $roomMap, array $userMap)
+    protected function importSharedAccesses(Collection $sharedAccesses, array $roomMap, array $userMap): void

35-95: Missing return value from handle() method.

Laravel Artisan commands should return an integer exit code (0 for success, non-zero for failure). This helps when running commands in scripts or CI pipelines.

♻️ Add return codes
     public function handle()
+    public function handle(): int
     {
         // ... existing code ...
 
             if (confirm('Do you wish to commit the import?')) {
                 DB::commit();
                 $this->info('Import completed');
+                return 0;
             } else {
                 DB::rollBack();
                 $this->warn('Import canceled; nothing was imported');
+                return 1;
             }
         } catch (\Exception $e) {
             DB::rollBack();
             $this->error('Import failed: '.$e->getMessage());
+            return 1;
         }
     }

109-110: Inconsistent progress bar implementation.

importUsers() uses Laravel Prompts' progress() while importRooms() and importSharedAccesses() use $this->output->createProgressBar(). Consider using the same approach throughout for consistency.

Also applies to: 173-174

@pizkaz pizkaz force-pushed the feat/greenlight3-import branch from 29d66c5 to 43039e4 Compare January 20, 2026 14:39
Copy link
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: 1

🤖 Fix all issues with AI agents
In `@app/Console/Commands/ImportGreenlight3Command.php`:
- Around line 61-83: The code in ImportGreenlight3Command misuses options() and
has a typo: replace calls like this->options('room-type') and
this->options('prefix')/('default-role') with this->option('room-type'),
this->option('room-prefix') and this->option('default-role'); fix the typo
firsthere to firstWhere (or use ->value('id') to get an int id) when resolving
RoomType/Role, and ensure you pass integer IDs (e.g., $roomTypeId and $roleId)
into importRooms() and importUsers() instead of Eloquent models; add null guards
after resolving names so you throw/exit or prompt if RoomType or Role lookup
returns null to avoid TypeErrors.
♻️ Duplicate comments (1)
app/Console/Commands/ImportGreenlight3Command.php (1)

26-48: Avoid passing DB passwords via CLI arguments.

Command-line args are visible via ps/shell history. Use an interactive prompt or env/config fallback instead.

🔒 Suggested direction (prompt or env fallback)
+use function Laravel\Prompts\password;
+
 class ImportGreenlight3Command extends Command
 {
     protected $signature = 'import:greenlight-v3
                              {host : ip or hostname of postgres database server}
                              {port : port of postgres database server}
                              {database : greenlight database name, see greenlight .env variable DB_NAME}
-                             {username : greenlight database username, see greenlight .env variable DB_USERNAME}
-                             {password : greenlight database password, see greenlight .env variable DB_PASSWORD}
+                             {username : greenlight database username, see greenlight .env variable DB_USERNAME}
                              {--no-confirm : do not ask if the import should be committed}
                              {--default-role= : default role for imported external users}
                              {--room-prefix= : room name prefix for imported rooms}
                              {--room-type= : room type for imported rooms}
                              ';
...
-        Config::set('database.connections.greenlight', [
+        $dbPassword = env('GREENLIGHT_DB_PASSWORD') ?? password(
+            label: 'Enter the Greenlight database password',
+            required: true
+        );
+
+        Config::set('database.connections.greenlight', [
             'driver' => 'pgsql',
             'host' => $this->argument('host'),
             'database' => $this->argument('database'),
             'username' => $this->argument('username'),
-            'password' => $this->argument('password'),
+            'password' => $dbPassword,
             'port' => $this->argument('port'),

@pizkaz pizkaz force-pushed the feat/greenlight3-import branch 2 times, most recently from 01e4fae to bdaf483 Compare January 22, 2026 21:00
Copy link
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: 1

🤖 Fix all issues with AI agents
In `@app/Console/Commands/ImportGreenlight3Command.php`:
- Around line 26-35: Add a new CLI option to the command signature and use it
when no-confirm is set: update the protected $signature to include
"{--room-prefix= : prefix to prepend to imported room names}" and then modify
the prefix resolution in handle() (where the command currently prompts for a
prefix) to first check $this->option('room-prefix') and use that if provided; if
not provided and $this->option('no-confirm') is true, keep the existing behavior
(skip the interactive prompt and use an empty/default prefix), otherwise
continue prompting the user for the prefix as before.
♻️ Duplicate comments (3)
app/Console/Commands/ImportGreenlight3Command.php (2)

26-31: Avoid passing DB passwords as CLI args.
Command-line args are visible in process listings and shell history; prefer an interactive prompt or env/config fallback.


59-82: Guard against invalid --room-type / --default-role values.
firstWhere(...)->id will throw when the name isn’t found; add a null guard and exit or re-prompt.

🐛 Suggested guard
-        $roomType = $this->option('room-type') != null
-            ? RoomType::firstWhere('name', $this->option('room-type'))->id
+        $roomType = $this->option('room-type') !== null
+            ? RoomType::where('name', $this->option('room-type'))->value('id')
             : select(
                 label: 'What room type should the rooms be assigned to?',
                 options: RoomType::pluck('name', 'id'),
                 scroll: 10
             );
+        if ($roomType === null) {
+            $this->error('Unknown room type: '.$this->option('room-type'));
+            return self::FAILURE;
+        }
...
-        $defaultRole = $this->option('default-role') != null
-            ? Role::firstWhere('name', $this->option('default-role'))->id
+        $defaultRole = $this->option('default-role') !== null
+            ? Role::where('name', $this->option('default-role'))->value('id')
             : select(
                 'Please select the default role for new imported non-ldap users',
                 options: Role::pluck('name', 'id'),
                 scroll: 10
             );
+        if ($defaultRole === null) {
+            $this->error('Unknown role: '.$this->option('default-role'));
+            return self::FAILURE;
+        }
tests/Backend/Unit/Console/ImportGreenlight3Test.php (1)

159-296: Fix Greenlight3Room ctor arg type for room id 7.
The 5th parameter is bool $deleted; passing "012345abcd" coerces to true and the string is ignored.

🧪 Suggested fix
-        $rooms[] = new Greenlight3Room(7, 'hij-klm-xyz-456', 99, 'Test Room 9', '012345abcd');
+        $rooms[] = new Greenlight3Room(7, 'hij-klm-xyz-456', 99, 'Test Room 9');

@pizkaz pizkaz force-pushed the feat/greenlight3-import branch from bdaf483 to 7aedc53 Compare January 27, 2026 14:54
Copy link
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: 1

🤖 Fix all issues with AI agents
In `@tests/Frontend/e2e/RoomsViewSettings.cy.js`:
- Around line 1239-1299: Replace the profanity-like access code "fck4fd" used in
the test with a neutral sample (e.g. "abcd1234") wherever it is set or asserted:
update the assignment to roomSettings.data.access_code in the GET fixture
intercept and the PUT fixture intercept, update the expected interception
request body access_code value, and keep the existing checks against
"#room-setting-access_code" and the intercept aliases
("roomSettingsRequest"/"roomSettingsSaveRequest") intact so the test logic
remains unchanged.

Copy link
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: 3

🤖 Fix all issues with AI agents
In `@app/Console/Commands/ImportGreenlight3Command.php`:
- Around line 55-57: The rooms query in ImportGreenlight3Command.php is fetching
all rooms including soft-deleted ones; add a where('deleted', false) filter to
the $rooms query (DB::connection('greenlight')->table('rooms')) to exclude
deleted rows. Also apply the same where('deleted', false) filter to the $users
query (DB::connection('greenlight')->table('users')) to match the Greenlight2
behavior and the Greenlight3Room helper's deleted column handling.
- Around line 222-247: Room GL3 boolean options are strings and must be
explicitly converted before assignment; update the loop over $roomOptions in
ImportGreenlight3Command to parse $option->value as a boolean (e.g., use strict
comparison $option->value === 'true' or PHP's filter_var($option->value,
FILTER_VALIDATE_BOOLEAN)) before assigning to $dbRoom properties so values like
everyone_can_start, mute_on_start, allow_guests (invert logic accordingly),
record and the boolean used for default_role are correct; locate the switch on
$option->name and replace direct assignments from $option->value with the parsed
boolean (and invert when setting allow_guests) or call a small helper
parseGl3Bool($option->value) to keep code clean.

In `@tests/Backend/Unit/Console/ImportGreenlight3Test.php`:
- Around line 55-57: Update the rooms query in ImportGreenlight3Command to
exclude soft-deleted rooms by adding ->where('deleted', false) to the
DB::connection(...)->table('rooms')->get([...]) chain (i.e.,
DB::connection('greenlight')->table('rooms')->where('deleted',
false)->get(['id','friendly_id','user_id','name'])). Also update the unit test
ImportGreenlight3Test so the DB mock expects the where('deleted', false) call on
the rooms table mock (adjust the DB::shouldReceive('connection')/table('rooms')
chain in the test to include ->where('deleted', false) before ->get([...]) and
ensure the connection argument matches the command's
DB::connection('greenlight') usage).
🧹 Nitpick comments (1)
app/Http/Middleware/RoomAuthenticate.php (1)

88-88: Consider using strict equality (===) for access code comparison.

Both $room->access_code (DB string column) and $accessCode (HTTP header string) should always be strings, so == works correctly here. However, === is the safer PHP convention for security-sensitive comparisons, guarding against any edge case where types might differ.

♻️ Suggested change
-            if ($room->access_code == $accessCode) {
+            if ($room->access_code === $accessCode) {

@pizkaz pizkaz force-pushed the feat/greenlight3-import branch 4 times, most recently from d35f42c to b839499 Compare February 18, 2026 17:21
@pizkaz pizkaz force-pushed the feat/greenlight3-import branch from b839499 to 7923ce5 Compare February 20, 2026 10:54
@pizkaz pizkaz force-pushed the feat/greenlight3-import branch from 2f23eb6 to cd320d3 Compare February 20, 2026 12:45
@pizkaz pizkaz changed the title WIP: Feature: Greenlight3 import Feature: Greenlight3 import Feb 20, 2026
@pizkaz
Copy link
Contributor Author

pizkaz commented Feb 20, 2026

I think this is ready to merge.


// ask user what room type the imported rooms should get
$roomType = $this->option('room-type') != null
? RoomType::firstWhere('name', $this->option('room-type'))->id
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this case sensitive? What happens if the name is invalid (no room type with this name)?

);

// ask user to add prefix to room names
$prefix = $this->option('no-confirm') ? null : text(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does "do not ask if the import should be committed" effect the prefix ? Maybe add an option to set the prefix ?

);

// ask user for presentation path
$presentationPath = $this->option('presentation-path')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we validate if the path exists and throw an error if the path is not a directory ?

DB::commit();
$this->info('Import completed');
} else {
DB::rollBack();
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about files that we copied/moved ?

// we have to import it as firstname and ask the users or admins to correct it later if desired
$dbUser->firstname = $user->name;
$dbUser->lastname = '';
$dbUser->password = $user->external_id ? Hash::make(Str::random()) : $user->password_digest;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have you checked if GL 3 still using bcrypt ?

$failed = 0;

foreach ($roomMap as $gl3RoomId => $pilosRoomId) {
$attachments = DB::connection('greenlight')->table('active_storage_attachments')->where('record_id', $gl3RoomId)->get('blob_id');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can be do a join here instead of n + 1 queries ?


## Running migration command

The command will output the process of the import and imforms about failed user, room and shared access import.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The command will output the process of the import and imforms about failed user, room and shared access import.
The command will output the process of the import and informs about failed user, room and shared access import.

Comment on lines +29 to +30
If you want to import Room presentations, copy Greenlight's active storage directory to the PILOS app storage at `/storage/app/migration/presentations` and specify `--presentation-path=migration/presentations` at the command line.
Successfully imported presentation files will be moved to a different location.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is currently only possible for GL3 import. Move to GL3 section until added to GL2 import


Options:
--no-confirm do not ask if the import should be committed
--default-role[=DEFAULT-ROLE] default role for imported external users
Copy link
Collaborator

Choose a reason for hiding this comment

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

name of the default role ....

Options:
--no-confirm do not ask if the import should be committed
--default-role[=DEFAULT-ROLE] default role for imported external users
--room-type[=ROOM-TYPE] room type for imported rooms
Copy link
Collaborator

Choose a reason for hiding this comment

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

name of the room type ....

--no-confirm do not ask if the import should be committed
--default-role[=DEFAULT-ROLE] default role for imported external users
--room-type[=ROOM-TYPE] room type for imported rooms
--presentation-path[=PRESENTATION-PATH] path to room presentations
Copy link
Collaborator

Choose a reason for hiding this comment

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

relative to /storage/app/

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.

Greenlight 3 import command

2 participants