Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2527969
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
63 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php b/config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
deleted file mode 100644
index 4896bd01..00000000
--- a/config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-use Illuminate\Support\Facades\Schema;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Database\Migrations\Migration;
-
-// phpcs:ignore
-class ChangeSkuDescriptions extends Migration
-{
- /**
- * Run the migrations.
- *
- * @return void
- */
- public function up()
- {
- $beta_sku = \App\Sku::where('title', 'beta')->first();
-
- if ($beta_sku) {
- $beta_sku->name = 'Private Beta (invitation only)';
- $beta_sku->description = 'Access to the private beta program subscriptions';
- $beta_sku->save();
- }
-
- $meet_sku = \App\Sku::where('title', 'meet')->first();
-
- if ($meet_sku) {
- $meet_sku->name = 'Voice & Video Conferencing (public beta)';
- $meet_sku->handler_class = 'App\Handlers\Meet';
- $meet_sku->save();
- }
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- }
-}
diff --git a/config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php b/config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php
deleted file mode 100644
index 19277aa2..00000000
--- a/config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Support\Facades\DB;
-
-// phpcs:ignore
-class TransactionAmountFix extends Migration
-{
- /**
- * Run the migrations.
- *
- * @return void
- */
- public function up()
- {
- $negatives = [
- \App\Transaction::WALLET_CHARGEBACK,
- \App\Transaction::WALLET_DEBIT,
- \App\Transaction::WALLET_PENALTY,
- \App\Transaction::WALLET_REFUND,
- ];
-
- $query = "UPDATE transactions SET amount = amount * -1"
- . " WHERE type IN (" . implode(',', array_fill(0, count($negatives), '?')) . ")";
-
- DB::select($query, $negatives);
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- DB::select("UPDATE transactions SET amount = amount * -1 WHERE amount < 0");
- }
-}
diff --git a/config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php b/config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php
deleted file mode 100644
index a4c733d0..00000000
--- a/config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-use Illuminate\Support\Facades\Schema;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Database\Migrations\Migration;
-
-// phpcs:ignore
-class RenameBetaSkus extends Migration
-{
- /**
- * Run the migrations.
- *
- * @return void
- */
- public function up()
- {
- \App\Sku::where('title', 'distlist')->get()->each(function ($sku) {
- $sku->title = 'beta-distlists';
- $sku->handler_class = 'App\Handlers\Beta\Distlists';
- $sku->save();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- \App\Sku::where('title', 'beta-distlists')->get()->each(function ($sku) {
- $sku->title = 'distlist';
- $sku->handler_class = 'App\Handlers\Distlist';
- $sku->save();
- });
- }
-}
diff --git a/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php b/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php
deleted file mode 100644
index 0cabaf4f..00000000
--- a/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
- /**
- * Run the migrations.
- */
- public function up(): void
- {
- // Create the new SKUs
- \App\Sku::create([
- 'title' => 'group-room',
- 'name' => 'Group conference room',
- 'description' => 'Shareable audio & video conference room',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\GroupRoom',
- 'active' => true,
- ]);
-
- \App\Sku::create([
- 'title' => 'room',
- 'name' => 'Standard conference room',
- 'description' => 'Audio & video conference room',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Room',
- 'active' => true,
- ]);
- }
-
- /**
- * Reverse the migrations.
- */
- public function down(): void
- {
- \App\Sku::where('title', 'room')->delete();
- \App\Sku::where('title', 'group-room')->delete();
-
- \App\Sku::create([
- 'title' => 'meet',
- 'name' => 'Voice & Video Conferencing (public beta)',
- 'description' => 'Video conferencing tool',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Meet',
- 'active' => true,
- ]);
- }
-};
diff --git a/config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php b/config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php
deleted file mode 100644
index b044f93e..00000000
--- a/config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
- /**
- * Run the migrations.
- */
- public function up(): void
- {
- \App\Sku::where('title', 'group')->get()->each(function ($sku) {
- $sku->name = 'Distribution list';
- $sku->description = 'Mail distribution list';
- $sku->save();
- });
- }
-
- /**
- * Reverse the migrations.
- */
- public function down(): void
- {
- \App\Sku::where('title', 'group')->get()->each(function ($sku) {
- $sku->name = 'Group';
- $sku->description = 'Distribution list';
- $sku->save();
- });
- }
-};
diff --git a/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php b/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php
deleted file mode 100644
index 4453990d..00000000
--- a/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Support\Facades\DB;
-
-return new class extends Migration
-{
- /**
- * Run the migrations.
- *
- * @return void
- */
- public function up()
- {
- DB::table('plans')->update(['free_months' => 1]);
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- }
-};
diff --git a/config.prod/src/database/migrations b/config.prod/src/database/migrations
deleted file mode 120000
index dae12a41..00000000
--- a/config.prod/src/database/migrations
+++ /dev/null
@@ -1 +0,0 @@
-../../../config.demo/src/database/migrations
\ No newline at end of file
diff --git a/config.prod/src/database/seeds/AdminSeeder.php b/config.prod/src/database/seeds/AdminSeeder.php
index 35e0758b..9c291119 100644
--- a/config.prod/src/database/seeds/AdminSeeder.php
+++ b/config.prod/src/database/seeds/AdminSeeder.php
@@ -1,86 +1,218 @@
<?php
namespace Database\Seeds;
-use App\Sku;
-use App\Package;
use App\Domain;
use App\User;
-use Laravel\Passport\Passport;
+use App\Sku;
+use App\Package;
use Illuminate\Database\Seeder;
-use Illuminate\Encryption\Encrypter;
class AdminSeeder extends Seeder
{
/**
* Run the database seeds.
*
* Create a default user with dependencies
*
* @return void
*/
public function run()
{
- //Create required packages
+ $skus = [
+ [
+ 'title' => 'mailbox',
+ 'name' => 'User Mailbox',
+ 'description' => 'Just a mailbox',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Mailbox',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'domain',
+ 'name' => 'Hosted Domain',
+ 'description' => 'Somewhere to place a mailbox',
+ 'cost' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Domain',
+ 'active' => false,
+ ],
+ [
+ 'title' => 'domain-hosting',
+ 'name' => 'External Domain',
+ 'description' => 'Host a domain that is externally registered',
+ 'cost' => 0,
+ 'units_free' => 1,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\DomainHosting',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'storage',
+ 'name' => 'Storage Quota',
+ 'description' => 'Some wiggle room',
+ 'cost' => 0,
+ 'units_free' => 5,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Storage',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'groupware',
+ 'name' => 'Groupware Features',
+ 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Groupware',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'resource',
+ 'name' => 'Resource',
+ 'description' => 'Reservation taker',
+ 'cost' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Resource',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'shared-folder',
+ 'name' => 'Shared Folder',
+ 'description' => 'A shared folder',
+ 'cost' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\SharedFolder',
+ 'active' => true,
+ ],
+ [
+ 'title' => '2fa',
+ 'name' => '2-Factor Authentication',
+ 'description' => 'Two factor authentication for webmail and administration panel',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Auth2F',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'activesync',
+ 'name' => 'Activesync',
+ 'description' => 'Mobile synchronization',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Activesync',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'group',
+ 'name' => 'Distribution list',
+ 'description' => 'Mail distribution list',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Group',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'group-room',
+ 'name' => 'Group conference room',
+ 'description' => 'Shareable audio & video conference room',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\GroupRoom',
+ 'active' => true,
+ ],
+ [
+ 'title' => 'room',
+ 'name' => 'Standard conference room',
+ 'description' => 'Audio & video conference room',
+ 'cost' => 0,
+ 'units_free' => 0,
+ 'period' => 'monthly',
+ 'handler_class' => 'App\Handlers\Room',
+ 'active' => true,
+ ],
+ ];
+
+ foreach ($skus as $sku) {
+ // Check existence because migration might have added this already
+ if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) {
+ Sku::create($sku);
+ }
+ }
+
$skuDomain = Sku::where(['title' => 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first();
$skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => \config('app.tenant_id')])->first();
$skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => \config('app.tenant_id')])->first();
$skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => \config('app.tenant_id')])->first();
+ // $skuGroup = Sku::where(['title' => 'group', 'tenant_id' => \config('app.tenant_id')])->first();
- $packageKolab = Package::create(
+ $userPackage = Package::create(
[
'title' => 'kolab',
'name' => 'Groupware Account',
'description' => 'A fully functional groupware account.',
'discount_rate' => 0,
]
);
- $packageKolab->skus()->saveMany([
+
+ $userPackage->skus()->saveMany([
$skuMailbox,
$skuGroupware,
$skuStorage
]);
-
- $packageDomain = Package::create(
- [
- 'title' => 'domain-hosting',
- 'name' => 'Domain Hosting',
- 'description' => 'Use your own, existing domain.',
- 'discount_rate' => 0,
- ]
+ // This package contains 2 units of the storage SKU, which just so happens to also
+ // be the number of SKU free units.
+ $userPackage->skus()->updateExistingPivot(
+ $skuStorage,
+ ['qty' => 5],
+ false
);
- $packageDomain->skus()->saveMany([
- $skuDomain
- ]);
-
- //Create primary domain
- $appDomain = Domain::create(
- [
- 'namespace' => \config('app.domain'),
- 'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
- 'type' => Domain::TYPE_PUBLIC,
- ]
- );
//Create admin user
$admin = User::create(
[
'email' => 'admin@' . \config('app.domain'),
'password' => \App\Utils::generatePassphrase()
]
);
$admin->setSettings(
[
'first_name' => 'Admin',
]
);
- $appDomain->assignPackage($packageDomain, $admin);
- $admin->assignPackage($packageKolab);
+ $admin->assignPackage($userPackage);
+
+
+ //Create primary domain
+ $domain = Domain::create(
+ [
+ 'namespace' => \config('app.domain'),
+ 'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
+ 'type' => Domain::TYPE_EXTERNAL,
+ ]
+ );
+
+ $domainPackage = Package::create(
+ [
+ 'title' => 'domain',
+ 'name' => 'Domain',
+ 'description' => 'Domain.',
+ 'discount_rate' => 0,
+ ]
+ );
+ $domainPackage->skus()->saveMany([$skuDomain]);
+
+ $domain->assignPackage($domainPackage, $admin);
}
}
-
diff --git a/config.prod/src/database/seeds/DatabaseSeeder.php b/config.prod/src/database/seeds/DatabaseSeeder.php
index 9388bf56..fe7dbe0b 100644
--- a/config.prod/src/database/seeds/DatabaseSeeder.php
+++ b/config.prod/src/database/seeds/DatabaseSeeder.php
@@ -1,23 +1,23 @@
<?php
use Illuminate\Database\Seeder;
use Database\Seeds;
// phpcs:ignore
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call([
Seeds\PassportSeeder::class,
Seeds\PowerDNSSeeder::class,
- Seeds\SkuSeeder::class,
Seeds\AdminSeeder::class,
+ Seeds\ImapAdminSeeder::class,
]);
}
}
diff --git a/config.prod/src/database/seeds/ImapAdminSeeder.php b/config.prod/src/database/seeds/ImapAdminSeeder.php
new file mode 100644
index 00000000..4f18d203
--- /dev/null
+++ b/config.prod/src/database/seeds/ImapAdminSeeder.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Database\Seeds;
+
+use App\User;
+use Illuminate\Database\Seeder;
+
+class ImapAdminSeeder extends Seeder
+{
+ /**
+ * Run the database seeds.
+ *
+ * Create imap admin service account, which is required for sasl httpauth to work
+ *
+ * @return void
+ */
+ public function run()
+ {
+ User::create(
+ [
+ 'email' => \config('imap.admin_login'),
+ 'password' => \config('imap.admin_password')
+ ]
+ );
+ }
+}
diff --git a/config.prod/src/database/seeds/SkuSeeder.php b/config.prod/src/database/seeds/SkuSeeder.php
deleted file mode 100644
index cceae31d..00000000
--- a/config.prod/src/database/seeds/SkuSeeder.php
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-
-namespace Database\Seeds;
-
-use App\Sku;
-use Illuminate\Database\Seeder;
-
-class SkuSeeder extends Seeder
-{
- /**
- * Run the database seeds.
- *
- * @return void
- */
- public function run()
- {
- $skus = [
- [
- 'title' => 'mailbox',
- 'name' => 'User Mailbox',
- 'description' => 'Just a mailbox',
- 'cost' => 500,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Mailbox',
- 'active' => true,
- ],
- [
- 'title' => 'domain',
- 'name' => 'Hosted Domain',
- 'description' => 'Somewhere to place a mailbox',
- 'cost' => 100,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Domain',
- 'active' => false,
- ],
- [
- 'title' => 'domain-registration',
- 'name' => 'Domain Registration',
- 'description' => 'Register a domain with us',
- 'cost' => 101,
- 'period' => 'yearly',
- 'handler_class' => 'App\Handlers\DomainRegistration',
- 'active' => false,
- ],
- [
- 'title' => 'domain-hosting',
- 'name' => 'External Domain',
- 'description' => 'Host a domain that is externally registered',
- 'cost' => 100,
- 'units_free' => 1,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\DomainHosting',
- 'active' => true,
- ],
- [
- 'title' => 'domain-relay',
- 'name' => 'Domain Relay',
- 'description' => 'A domain you host at home, for which we relay email',
- 'cost' => 103,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\DomainRelay',
- 'active' => false,
- ],
- [
- 'title' => 'storage',
- 'name' => 'Storage Quota',
- 'description' => 'Some wiggle room',
- 'cost' => 25,
- 'units_free' => 5,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Storage',
- 'active' => true,
- ],
- [
- 'title' => 'groupware',
- 'name' => 'Groupware Features',
- 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.',
- 'cost' => 490,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Groupware',
- 'active' => true,
- ],
- [
- 'title' => 'resource',
- 'name' => 'Resource',
- 'description' => 'Reservation taker',
- 'cost' => 101,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Resource',
- 'active' => true,
- ],
- [
- 'title' => 'shared-folder',
- 'name' => 'Shared Folder',
- 'description' => 'A shared folder',
- 'cost' => 89,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\SharedFolder',
- 'active' => true,
- ],
- [
- 'title' => '2fa',
- 'name' => '2-Factor Authentication',
- 'description' => 'Two factor authentication for webmail and administration panel',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Auth2F',
- 'active' => true,
- ],
- [
- 'title' => 'activesync',
- 'name' => 'Activesync',
- 'description' => 'Mobile synchronization',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Activesync',
- 'active' => true,
- ],
- [
- 'title' => 'beta',
- 'name' => 'Private Beta (invitation only)',
- 'description' => 'Access to the private beta program features',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta',
- 'active' => false,
- ],
- [
- 'title' => 'group',
- 'name' => 'Distribution list',
- 'description' => 'Mail distribution list',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Group',
- 'active' => true,
- ],
- [
- 'title' => 'group-room',
- 'name' => 'Group conference room',
- 'description' => 'Shareable audio & video conference room',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\GroupRoom',
- 'active' => true,
- ],
- [
- 'title' => 'room',
- 'name' => 'Standard conference room',
- 'description' => 'Audio & video conference room',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Room',
- 'active' => true,
- ],
- ];
-
- foreach ($skus as $sku) {
- // Check existence because migration might have added this already
- if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) {
- Sku::create($sku);
- }
- }
- }
-}
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
index 71fbc606..559bb167 100644
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -1,710 +1,711 @@
<?php
namespace App\Http\Controllers\API\V4;
use App\Http\Controllers\RelationController;
use App\Domain;
use App\Plan;
use App\Rules\Password;
use App\Rules\UserEmailDomain;
use App\Rules\UserEmailLocal;
use App\Sku;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class UsersController extends RelationController
{
/** @const array List of user setting keys available for modification in UI */
public const USER_SETTINGS = [
'billing_address',
'country',
'currency',
'external_email',
'first_name',
'last_name',
'organization',
'phone',
];
/**
* On user create it is filled with a user or group object to force-delete
* before the creation of a new user record is possible.
*
* @var \App\User|\App\Group|null
*/
protected $deleteBeforeCreate;
/** @var string Resource localization label */
protected $label = 'user';
/** @var string Resource model name */
protected $model = User::class;
/** @var array Common object properties in the API response */
protected $objectProps = ['email'];
/** @var ?\App\VerificationCode Password reset code to activate on user create/update */
protected $passCode;
/**
* Listing of users.
*
* The user-entitlements billed to the current user wallet(s)
*
* @return \Illuminate\Http\JsonResponse
*/
public function index()
{
$user = $this->guard()->user();
$search = trim(request()->input('search'));
$page = intval(request()->input('page')) ?: 1;
$pageSize = 20;
$hasMore = false;
$result = $user->users();
// Search by user email, alias or name
if (strlen($search) > 0) {
// thanks to cloning we skip some extra queries in $user->users()
$allUsers1 = clone $result;
$allUsers2 = clone $result;
$result->whereLike('email', $search)
->union(
$allUsers1->join('user_aliases', 'users.id', '=', 'user_aliases.user_id')
->whereLike('alias', $search)
)
->union(
$allUsers2->join('user_settings', 'users.id', '=', 'user_settings.user_id')
->whereLike('value', $search)
->whereIn('key', ['first_name', 'last_name'])
);
}
$result = $result->orderBy('email')
->limit($pageSize + 1)
->offset($pageSize * ($page - 1))
->get();
if (count($result) > $pageSize) {
$result->pop();
$hasMore = true;
}
// Process the result
$result = $result->map(
function ($user) {
return $this->objectToClient($user);
}
);
$result = [
'list' => $result,
'count' => count($result),
'hasMore' => $hasMore,
];
return response()->json($result);
}
/**
* Display information on the user account specified by $id.
*
* @param string $id The account to show information for.
*
* @return \Illuminate\Http\JsonResponse
*/
public function show($id)
{
$user = User::find($id);
if (!$this->checkTenant($user)) {
return $this->errorResponse(404);
}
if (!$this->guard()->user()->canRead($user)) {
return $this->errorResponse(403);
}
$response = $this->userResponse($user);
$response['skus'] = \App\Entitlement::objectEntitlementsSummary($user);
$response['config'] = $user->getConfig();
$response['aliases'] = $user->aliases()->pluck('alias')->all();
$code = $user->verificationcodes()->where('active', true)
->where('expires_at', '>', \Carbon\Carbon::now())
->first();
if ($code) {
$response['passwordLinkCode'] = $code->short_code . '-' . $code->code;
}
return response()->json($response);
}
/**
* User status (extended) information
*
* @param \App\User $user User object
*
* @return array Status information
*/
public static function statusInfo($user): array
{
$process = self::processStateInfo(
$user,
[
'user-new' => true,
'user-ldap-ready' => $user->isLdapReady(),
'user-imap-ready' => $user->isImapReady(),
]
);
// Check if the user is a controller of his wallet
$isController = $user->canDelete($user);
$isDegraded = $user->isDegraded();
$hasMeet = !$isDegraded && Sku::withObjectTenantContext($user)->where('title', 'room')->exists();
+ // Enable all features if there are no skus for domain-hosting
$hasCustomDomain = $user->wallet()->entitlements()
->where('entitleable_type', Domain::class)
- ->count() > 0;
+ ->count() > 0 || !Sku::withObjectTenantContext($user)->where('title', 'domain-hosting')->exists();
// Get user's entitlements titles
$skus = $user->entitlements()->select('skus.title')
->join('skus', 'skus.id', '=', 'entitlements.sku_id')
->get()
->pluck('title')
->sort()
->unique()
->values()
->all();
- $hasBeta = in_array('beta', $skus);
+ $hasBeta = in_array('beta', $skus) || !Sku::withObjectTenantContext($user)->where('title', 'beta')->exists();
$plan = $isController ? $user->wallet()->plan() : null;
$result = [
'skus' => $skus,
- 'enableBeta' => in_array('beta', $skus),
+ 'enableBeta' => $hasBeta,
// TODO: This will change when we enable all users to create domains
'enableDomains' => $isController && $hasCustomDomain,
// TODO: Make 'enableDistlists' working for wallet controllers that aren't account owners
'enableDistlists' => $isController && $hasCustomDomain && $hasBeta,
'enableFiles' => !$isDegraded && $hasBeta && \config('app.with_files'),
// TODO: Make 'enableFolders' working for wallet controllers that aren't account owners
'enableFolders' => $isController && $hasCustomDomain && $hasBeta,
// TODO: Make 'enableResources' working for wallet controllers that aren't account owners
'enableResources' => $isController && $hasCustomDomain && $hasBeta,
'enableRooms' => $hasMeet,
'enableSettings' => $isController,
'enableUsers' => $isController,
'enableWallets' => $isController,
'enableWalletMandates' => $isController,
'enableWalletPayments' => $isController && (!$plan || $plan->mode != Plan::MODE_MANDATE),
'enableCompanionapps' => $hasBeta,
];
return array_merge($process, $result);
}
/**
* Create a new user record.
*
* @param \Illuminate\Http\Request $request The API request.
*
* @return \Illuminate\Http\JsonResponse The response
*/
public function store(Request $request)
{
$current_user = $this->guard()->user();
$owner = $current_user->walletOwner();
if ($owner->id != $current_user->id) {
return $this->errorResponse(403);
}
$this->deleteBeforeCreate = null;
if ($error_response = $this->validateUserRequest($request, null, $settings)) {
return $error_response;
}
if (empty($request->package) || !($package = \App\Package::withEnvTenantContext()->find($request->package))) {
$errors = ['package' => \trans('validation.packagerequired')];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
if ($package->isDomain()) {
$errors = ['package' => \trans('validation.packageinvalid')];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
DB::beginTransaction();
// @phpstan-ignore-next-line
if ($this->deleteBeforeCreate) {
$this->deleteBeforeCreate->forceDelete();
}
// Create user record
$user = User::create([
'email' => $request->email,
'password' => $request->password,
'status' => $owner->isRestricted() ? User::STATUS_RESTRICTED : 0,
]);
$this->activatePassCode($user);
$owner->assignPackage($package, $user);
if (!empty($settings)) {
$user->setSettings($settings);
}
if (!empty($request->aliases)) {
$user->setAliases($request->aliases);
}
DB::commit();
return response()->json([
'status' => 'success',
'message' => \trans('app.user-create-success'),
]);
}
/**
* Update user data.
*
* @param \Illuminate\Http\Request $request The API request.
* @param string $id User identifier
*
* @return \Illuminate\Http\JsonResponse The response
*/
public function update(Request $request, $id)
{
$user = User::withEnvTenantContext()->find($id);
if (empty($user)) {
return $this->errorResponse(404);
}
$current_user = $this->guard()->user();
// TODO: Decide what attributes a user can change on his own profile
if (!$current_user->canUpdate($user)) {
return $this->errorResponse(403);
}
if ($error_response = $this->validateUserRequest($request, $user, $settings)) {
return $error_response;
}
// Entitlements, only controller can do that
if ($request->skus !== null && !$current_user->canDelete($user)) {
return $this->errorResponse(422, "You have no permission to change entitlements");
}
DB::beginTransaction();
SkusController::updateEntitlements($user, $request->skus);
if (!empty($settings)) {
$user->setSettings($settings);
}
if (!empty($request->password)) {
$user->password = $request->password;
$user->save();
}
$this->activatePassCode($user);
if (isset($request->aliases)) {
$user->setAliases($request->aliases);
}
DB::commit();
$response = [
'status' => 'success',
'message' => \trans('app.user-update-success'),
];
// For self-update refresh the statusInfo in the UI
if ($user->id == $current_user->id) {
$response['statusInfo'] = self::statusInfo($user);
}
return response()->json($response);
}
/**
* Create a response data array for specified user.
*
* @param \App\User $user User object
*
* @return array Response data
*/
public static function userResponse(User $user): array
{
$response = array_merge($user->toArray(), self::objectState($user));
$wallet = $user->wallet();
// IsLocked flag to lock the user to the Wallet page only
$response['isLocked'] = (!$user->isActive() && ($plan = $wallet->plan()) && $plan->mode == Plan::MODE_MANDATE);
// Settings
$response['settings'] = [];
foreach ($user->settings()->whereIn('key', self::USER_SETTINGS)->get() as $item) {
$response['settings'][$item->key] = $item->value;
}
// Status info
$response['statusInfo'] = self::statusInfo($user);
// Add more info to the wallet object output
$map_func = function ($wallet) use ($user) {
$result = $wallet->toArray();
if ($wallet->discount) {
$result['discount'] = $wallet->discount->discount;
$result['discount_description'] = $wallet->discount->description;
}
if ($wallet->user_id != $user->id) {
$result['user_email'] = $wallet->owner->email;
}
$provider = \App\Providers\PaymentProvider::factory($wallet);
$result['provider'] = $provider->name();
return $result;
};
// Information about wallets and accounts for access checks
$response['wallets'] = $user->wallets->map($map_func)->toArray();
$response['accounts'] = $user->accounts->map($map_func)->toArray();
$response['wallet'] = $map_func($wallet);
return $response;
}
/**
* Prepare user statuses for the UI
*
* @param \App\User $user User object
*
* @return array Statuses array
*/
protected static function objectState($user): array
{
$state = parent::objectState($user);
$state['isAccountDegraded'] = $user->isDegraded(true);
return $state;
}
/**
* Validate user input
*
* @param \Illuminate\Http\Request $request The API request.
* @param \App\User|null $user User identifier
* @param array $settings User settings (from the request)
*
* @return \Illuminate\Http\JsonResponse|null The error response on error
*/
protected function validateUserRequest(Request $request, $user, &$settings = [])
{
$rules = [
'external_email' => 'nullable|email',
'phone' => 'string|nullable|max:64|regex:/^[0-9+() -]+$/',
'first_name' => 'string|nullable|max:128',
'last_name' => 'string|nullable|max:128',
'organization' => 'string|nullable|max:512',
'billing_address' => 'string|nullable|max:1024',
'country' => 'string|nullable|alpha|size:2',
'currency' => 'string|nullable|alpha|size:3',
'aliases' => 'array|nullable',
];
$controller = ($user ?: $this->guard()->user())->walletOwner();
// Handle generated password reset code
if ($code = $request->input('passwordLinkCode')) {
// Accept <short-code>-<code> input
if (strpos($code, '-')) {
$code = explode('-', $code)[1];
}
$this->passCode = $this->guard()->user()->verificationcodes()
->where('code', $code)->where('active', false)->first();
// Generate a password for a new user with password reset link
// FIXME: Should/can we have a user with no password set?
if ($this->passCode && empty($user)) {
$request->password = $request->password_confirmation = Str::random(16);
$ignorePassword = true;
}
}
if (empty($user) || !empty($request->password) || !empty($request->password_confirmation)) {
if (empty($ignorePassword)) {
$rules['password'] = ['required', 'confirmed', new Password($controller)];
}
}
$errors = [];
// Validate input
$v = Validator::make($request->all(), $rules);
if ($v->fails()) {
$errors = $v->errors()->toArray();
}
// For new user validate email address
if (empty($user)) {
$email = $request->email;
if (empty($email)) {
$errors['email'] = \trans('validation.required', ['attribute' => 'email']);
} elseif ($error = self::validateEmail($email, $controller, $this->deleteBeforeCreate)) {
$errors['email'] = $error;
}
}
// Validate aliases input
if (isset($request->aliases)) {
$aliases = [];
$existing_aliases = $user ? $user->aliases()->get()->pluck('alias')->toArray() : [];
foreach ($request->aliases as $idx => $alias) {
if (is_string($alias) && !empty($alias)) {
// Alias cannot be the same as the email address (new user)
if (!empty($email) && Str::lower($alias) == Str::lower($email)) {
continue;
}
// validate new aliases
if (
!in_array($alias, $existing_aliases)
&& ($error = self::validateAlias($alias, $controller))
) {
if (!isset($errors['aliases'])) {
$errors['aliases'] = [];
}
$errors['aliases'][$idx] = $error;
continue;
}
$aliases[] = $alias;
}
}
$request->aliases = $aliases;
}
if (!empty($errors)) {
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
// Update user settings
$settings = $request->only(array_keys($rules));
unset($settings['password'], $settings['aliases'], $settings['email']);
return null;
}
/**
* Execute (synchronously) specified step in a user setup process.
*
* @param \App\User $user User object
* @param string $step Step identifier (as in self::statusInfo())
*
* @return bool|null True if the execution succeeded, False if not, Null when
* the job has been sent to the worker (result unknown)
*/
public static function execProcessStep(User $user, string $step): ?bool
{
try {
if (strpos($step, 'domain-') === 0) {
return DomainsController::execProcessStep($user->domain(), $step);
}
switch ($step) {
case 'user-ldap-ready':
case 'user-imap-ready':
// Use worker to do the job, frontend might not have the IMAP admin credentials
\App\Jobs\User\CreateJob::dispatch($user->id);
return null;
}
} catch (\Exception $e) {
\Log::error($e);
}
return false;
}
/**
* Email address validation for use as a user mailbox (login).
*
* @param string $email Email address
* @param \App\User $user The account owner
* @param null|\App\User|\App\Group $deleted Filled with an instance of a deleted user or group
* with the specified email address, if exists
*
* @return ?string Error message on validation error
*/
public static function validateEmail(string $email, \App\User $user, &$deleted = null): ?string
{
$deleted = null;
if (strpos($email, '@') === false) {
return \trans('validation.entryinvalid', ['attribute' => 'email']);
}
list($login, $domain) = explode('@', Str::lower($email));
if (strlen($login) === 0 || strlen($domain) === 0) {
return \trans('validation.entryinvalid', ['attribute' => 'email']);
}
// Check if domain exists
$domain = Domain::withObjectTenantContext($user)->where('namespace', $domain)->first();
if (empty($domain)) {
return \trans('validation.domaininvalid');
}
// Validate login part alone
$v = Validator::make(
['email' => $login],
['email' => ['required', new UserEmailLocal(!$domain->isPublic())]]
);
if ($v->fails()) {
return $v->errors()->toArray()['email'][0];
}
// Check if it is one of domains available to the user
if (!$domain->isPublic() && $user->id != $domain->walletOwner()->id) {
return \trans('validation.entryexists', ['attribute' => 'domain']);
}
// Check if a user/group/resource/shared folder with specified address already exists
if (
($existing = User::emailExists($email, true))
|| ($existing = \App\Group::emailExists($email, true))
|| ($existing = \App\Resource::emailExists($email, true))
|| ($existing = \App\SharedFolder::emailExists($email, true))
) {
// If this is a deleted user/group/resource/folder in the same custom domain
// we'll force delete it before creating the target user
if (!$domain->isPublic() && $existing->trashed()) {
$deleted = $existing;
} else {
return \trans('validation.entryexists', ['attribute' => 'email']);
}
}
// Check if an alias with specified address already exists.
if (User::aliasExists($email) || \App\SharedFolder::aliasExists($email)) {
return \trans('validation.entryexists', ['attribute' => 'email']);
}
return null;
}
/**
* Email address validation for use as an alias.
*
* @param string $email Email address
* @param \App\User $user The account owner
*
* @return ?string Error message on validation error
*/
public static function validateAlias(string $email, \App\User $user): ?string
{
if (strpos($email, '@') === false) {
return \trans('validation.entryinvalid', ['attribute' => 'alias']);
}
list($login, $domain) = explode('@', Str::lower($email));
if (strlen($login) === 0 || strlen($domain) === 0) {
return \trans('validation.entryinvalid', ['attribute' => 'alias']);
}
// Check if domain exists
$domain = Domain::withObjectTenantContext($user)->where('namespace', $domain)->first();
if (empty($domain)) {
return \trans('validation.domaininvalid');
}
// Validate login part alone
$v = Validator::make(
['alias' => $login],
['alias' => ['required', new UserEmailLocal(!$domain->isPublic())]]
);
if ($v->fails()) {
return $v->errors()->toArray()['alias'][0];
}
// Check if it is one of domains available to the user
if (!$domain->isPublic() && $user->id != $domain->walletOwner()->id) {
return \trans('validation.entryexists', ['attribute' => 'domain']);
}
// Check if a user with specified address already exists
if ($existing_user = User::emailExists($email, true)) {
// Allow an alias in a custom domain to an address that was a user before
if ($domain->isPublic() || !$existing_user->trashed()) {
return \trans('validation.entryexists', ['attribute' => 'alias']);
}
}
// Check if a group/resource/shared folder with specified address already exists
if (
\App\Group::emailExists($email)
|| \App\Resource::emailExists($email)
|| \App\SharedFolder::emailExists($email)
) {
return \trans('validation.entryexists', ['attribute' => 'alias']);
}
// Check if an alias with specified address already exists
if (User::aliasExists($email) || \App\SharedFolder::aliasExists($email)) {
// Allow assigning the same alias to a user in the same group account,
// but only for non-public domains
if ($domain->isPublic()) {
return \trans('validation.entryexists', ['attribute' => 'alias']);
}
}
return null;
}
/**
* Activate password reset code (if set), and assign it to a user.
*
* @param \App\User $user The user
*/
protected function activatePassCode(User $user): void
{
// Activate the password reset code
if ($this->passCode) {
$this->passCode->user_id = $user->id;
$this->passCode->active = true;
$this->passCode->save();
}
}
}
diff --git a/src/database/migrations/2020_10_29_100000_add_beta_skus.php b/src/database/migrations/2020_10_29_100000_add_beta_skus.php
deleted file mode 100644
index 3cf3ed12..00000000
--- a/src/database/migrations/2020_10_29_100000_add_beta_skus.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-use Illuminate\Support\Facades\Schema;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Database\Migrations\Migration;
-
-// phpcs:ignore
-class AddBetaSkus extends Migration
-{
- /**
- * Run the migrations.
- *
- * @return void
- */
- public function up()
- {
- if (!\App\Sku::where('title', 'beta')->first()) {
- \App\Sku::create([
- 'title' => 'beta',
- 'name' => 'Beta program',
- 'description' => 'Access to beta program subscriptions',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta',
- 'active' => false,
- ]);
- }
-
- if (!\App\Sku::where('title', 'meet')->first()) {
- \App\Sku::create([
- 'title' => 'meet',
- 'name' => 'Video chat',
- 'description' => 'Video conferencing tool',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\Meet',
- 'active' => true,
- ]);
- }
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- // there's no need to remove these SKUs
- }
-}
diff --git a/src/database/migrations/2021_04_22_120000_add_distlist_beta_sku.php b/src/database/migrations/2021_04_22_120000_add_distlist_beta_sku.php
deleted file mode 100644
index e7ae98d3..00000000
--- a/src/database/migrations/2021_04_22_120000_add_distlist_beta_sku.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-use Illuminate\Support\Facades\Schema;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Database\Migrations\Migration;
-
-// phpcs:ignore
-class AddDistlistBetaSku extends Migration
-{
- /**
- * Run the migrations.
- *
- * @return void
- */
- public function up()
- {
- if (!\App\Sku::where('title', 'distlist')->first()) {
- \App\Sku::create([
- 'title' => 'distlist',
- 'name' => 'Distribution lists',
- 'description' => 'Access to mail distribution lists',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Distlist',
- 'active' => true,
- ]);
- }
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- // there's no need to remove this SKU
- }
-}
diff --git a/src/database/migrations/2021_11_16_100000_create_resources_tables.php b/src/database/migrations/2021_11_16_100000_create_resources_tables.php
index c6c47c22..3689e411 100644
--- a/src/database/migrations/2021_11_16_100000_create_resources_tables.php
+++ b/src/database/migrations/2021_11_16_100000_create_resources_tables.php
@@ -1,80 +1,62 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
// phpcs:ignore
class CreateResourcesTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create(
'resources',
function (Blueprint $table) {
$table->unsignedBigInteger('id');
$table->string('email')->unique();
$table->string('name');
$table->smallInteger('status');
$table->unsignedBigInteger('tenant_id')->nullable();
$table->timestamps();
$table->softDeletes();
$table->primary('id');
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null');
}
);
Schema::create(
'resource_settings',
function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('resource_id');
$table->string('key');
$table->text('value');
$table->timestamps();
$table->foreign('resource_id')->references('id')->on('resources')
->onDelete('cascade')->onUpdate('cascade');
$table->unique(['resource_id', 'key']);
}
);
-
- \App\Sku::where('title', 'resource')->update([
- 'active' => true,
- 'cost' => 0,
- ]);
-
- if (!\App\Sku::where('title', 'beta-resources')->first()) {
- \App\Sku::create([
- 'title' => 'beta-resources',
- 'name' => 'Calendaring resources',
- 'description' => 'Access to calendaring resources',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\Resources',
- 'active' => true,
- ]);
- }
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('resource_settings');
Schema::dropIfExists('resources');
// there's no need to remove the SKU
}
}
diff --git a/src/database/migrations/2021_11_25_100000_create_shared_folders_table.php b/src/database/migrations/2021_11_25_100000_create_shared_folders_table.php
index 01ce6f94..e79242f9 100644
--- a/src/database/migrations/2021_11_25_100000_create_shared_folders_table.php
+++ b/src/database/migrations/2021_11_25_100000_create_shared_folders_table.php
@@ -1,83 +1,64 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
// phpcs:ignore
class CreateSharedFoldersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create(
'shared_folders',
function (Blueprint $table) {
$table->unsignedBigInteger('id');
$table->string('email')->unique();
$table->string('name');
$table->string('type', 8);
$table->smallInteger('status');
$table->unsignedBigInteger('tenant_id')->nullable();
$table->timestamps();
$table->softDeletes();
$table->primary('id');
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null');
}
);
Schema::create(
'shared_folder_settings',
function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('shared_folder_id');
$table->string('key');
$table->text('value');
$table->timestamps();
$table->foreign('shared_folder_id')->references('id')->on('shared_folders')
->onDelete('cascade')->onUpdate('cascade');
$table->unique(['shared_folder_id', 'key']);
}
);
-
- \App\Sku::where('title', 'shared_folder')->update([
- 'active' => true,
- 'cost' => 0,
- 'title' => 'shared-folder',
- ]);
-
- if (!\App\Sku::where('title', 'beta-shared-folders')->first()) {
- \App\Sku::create([
- 'title' => 'beta-shared-folders',
- 'name' => 'Shared folders',
- 'description' => 'Access to shared folders',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\SharedFolders',
- 'active' => true,
- ]);
- }
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('shared_folder_settings');
Schema::dropIfExists('shared_folders');
// there's no need to remove the SKU
}
}
diff --git a/src/database/migrations/2022_03_02_100000_create_filesystem_tables.php b/src/database/migrations/2022_03_02_100000_create_filesystem_tables.php
index 03314f35..e8c2e48d 100644
--- a/src/database/migrations/2022_03_02_100000_create_filesystem_tables.php
+++ b/src/database/migrations/2022_03_02_100000_create_filesystem_tables.php
@@ -1,91 +1,76 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create(
'fs_items',
function (Blueprint $table) {
$table->string('id', 36)->primary();
$table->bigInteger('user_id')->index();
$table->integer('type')->unsigned()->default(0);
$table->timestamps();
$table->softDeletes();
$table->foreign('user_id')->references('id')->on('users')
->onUpdate('cascade')->onDelete('cascade');
}
);
Schema::create(
'fs_properties',
function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('item_id', 36);
$table->string('key')->index();
$table->text('value');
$table->timestamps();
$table->unique(['item_id', 'key']);
$table->foreign('item_id')->references('id')->on('fs_items')
->onDelete('cascade')->onUpdate('cascade');
}
);
Schema::create(
'fs_chunks',
function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('item_id', 36);
$table->string('chunk_id', 36);
$table->integer('sequence')->default(0);
$table->integer('size')->unsigned()->default(0);
$table->timestamps();
$table->softDeletes();
$table->unique(['item_id', 'chunk_id']);
// $table->unique(['item_id', 'sequence', 'deleted_at']);
$table->foreign('item_id')->references('id')->on('fs_items')
->onUpdate('cascade')->onDelete('cascade');
}
);
-
- if (\config('app.with_files') && !\App\Sku::where('title', 'files')->first()) {
- \App\Sku::create([
- 'title' => 'files',
- 'name' => 'File storage',
- 'description' => 'Access to file storage',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Files',
- 'active' => true,
- ]);
- }
}
/**
* Reverse the migrations.
*/
public function down(): void
{
- \App\Sku::where('title', 'files')->delete();
-
Schema::dropIfExists('fs_properties');
Schema::dropIfExists('fs_chunks');
Schema::dropIfExists('fs_items');
}
};
diff --git a/src/database/migrations/2022_06_03_100000_drop_beta_skus.php b/src/database/migrations/2022_06_03_100000_drop_beta_skus.php
deleted file mode 100644
index cda5e82d..00000000
--- a/src/database/migrations/2022_06_03_100000_drop_beta_skus.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
- /**
- * Run the migrations.
- */
- public function up(): void
- {
- $skus = ['beta-distlists', 'beta-resources', 'beta-shared-folders', 'files'];
-
- \App\Sku::whereIn('title', $skus)->delete();
-
- \App\Sku::where('title', 'beta')->update(['description' => 'Access to the private beta program features']);
- }
-
- /**
- * Reverse the migrations.
- */
- public function down(): void
- {
- \App\Sku::create([
- 'title' => 'beta-distlists',
- 'name' => 'Distribution lists',
- 'description' => 'Access to mail distribution lists',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\Distlists',
- 'active' => true,
- ]);
- \App\Sku::create([
- 'title' => 'beta-resources',
- 'name' => 'Calendaring resources',
- 'description' => 'Access to calendaring resources',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\Resources',
- 'active' => true,
- ]);
- \App\Sku::create([
- 'title' => 'beta-shared-folders',
- 'name' => 'Shared folders',
- 'description' => 'Access to shared folders',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\SharedFolders',
- 'active' => true,
- ]);
- \App\Sku::create([
- 'title' => 'files',
- 'name' => 'File storage',
- 'description' => 'Access to file storage',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Files',
- 'active' => true,
- ]);
- }
-};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 31, 8:47 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426478
Default Alt Text
(63 KB)
Attached To
Mode
R2 kolab
Attached
Detach File
Event Timeline
Log In to Comment