Page MenuHomePhorge

No OneTemporary

Size
63 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Sat, Jan 31, 8:47 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426478
Default Alt Text
(63 KB)

Event Timeline