mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-21 19:47:48 +00:00
Add ability to invite users
This commit is contained in:
49
app/Events/Admin/InvitationCreated.php
Normal file
49
app/Events/Admin/InvitationCreated.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* InvitationCreated.php
|
||||||
|
* Copyright (c) 2022 james@firefly-iii.org
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Events\Admin;
|
||||||
|
|
||||||
|
use FireflyIII\Events\Event;
|
||||||
|
use FireflyIII\Models\InvitedUser;
|
||||||
|
use FireflyIII\Models\TransactionGroup;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvitationCreated
|
||||||
|
*/
|
||||||
|
class InvitationCreated extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public InvitedUser $invitee;
|
||||||
|
|
||||||
|
public TransactionGroup $transactionGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param InvitedUser $invitee
|
||||||
|
*/
|
||||||
|
public function __construct(InvitedUser $invitee)
|
||||||
|
{
|
||||||
|
$this->invitee = $invitee;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,9 +22,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Handlers\Events;
|
namespace FireflyIII\Handlers\Events;
|
||||||
|
|
||||||
|
use FireflyIII\Events\Admin\InvitationCreated;
|
||||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||||
use FireflyIII\Events\NewVersionAvailable;
|
use FireflyIII\Events\NewVersionAvailable;
|
||||||
use FireflyIII\Notifications\Admin\TestNotification;
|
use FireflyIII\Notifications\Admin\TestNotification;
|
||||||
|
use FireflyIII\Notifications\Admin\UserInvitation;
|
||||||
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\Support\Facades\FireflyConfig;
|
use FireflyIII\Support\Facades\FireflyConfig;
|
||||||
@@ -57,6 +59,27 @@ class AdminEventHandler
|
|||||||
Notification::send($event->user, new TestNotification($event->user->email));
|
Notification::send($event->user, new TestNotification($event->user->email));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InvitationCreated $event
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function sendInvitationNotification(InvitationCreated $event): void
|
||||||
|
{
|
||||||
|
$sendMail = FireflyConfig::get('notification_invite_created', true)->data;
|
||||||
|
if (false === $sendMail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var UserRepositoryInterface $repository */
|
||||||
|
$repository = app(UserRepositoryInterface::class);
|
||||||
|
$all = $repository->all();
|
||||||
|
foreach ($all as $user) {
|
||||||
|
if ($repository->hasRole($user, 'owner')) {
|
||||||
|
Notification::send($user, new UserInvitation($event->invitee));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send new version message to admin.
|
* Send new version message to admin.
|
||||||
*
|
*
|
||||||
|
@@ -22,14 +22,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers\Admin;
|
namespace FireflyIII\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use FireflyIII\Events\Admin\InvitationCreated;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||||
|
use FireflyIII\Http\Requests\InviteUserFormRequest;
|
||||||
use FireflyIII\Http\Requests\UserFormRequest;
|
use FireflyIII\Http\Requests\UserFormRequest;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Redirector;
|
use Illuminate\Routing\Redirector;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Log;
|
use Log;
|
||||||
@@ -142,9 +145,17 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$subTitle = (string) trans('firefly.user_administration');
|
$subTitle = (string) trans('firefly.user_administration');
|
||||||
$subTitleIcon = 'fa-users';
|
$subTitleIcon = 'fa-users';
|
||||||
$users = $this->repository->all();
|
$users = $this->repository->all();
|
||||||
|
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||||
|
$allowInvites = false;
|
||||||
|
if (!$this->externalIdentity && $singleUserMode) {
|
||||||
|
// also registration enabled.
|
||||||
|
$allowInvites = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invitedUsers = $this->repository->getInvitedUsers();
|
||||||
|
|
||||||
// add meta stuff.
|
// add meta stuff.
|
||||||
$users->each(
|
$users->each(
|
||||||
@@ -154,7 +165,7 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users'));
|
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users', 'allowInvites', 'invitedUsers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,6 +196,22 @@ class UserController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InviteUserFormRequest $request
|
||||||
|
* @return RedirectResponse
|
||||||
|
*/
|
||||||
|
public function invite(InviteUserFormRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$address = (string) $request->get('invited_user');
|
||||||
|
$invitee = $this->repository->inviteUser(auth()->user(), $address);
|
||||||
|
session()->flash('info', trans('firefly.user_is_invited', ['address' => $address]));
|
||||||
|
|
||||||
|
// event!
|
||||||
|
event(new InvitationCreated($invitee));
|
||||||
|
|
||||||
|
return redirect(route('admin.users'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update single user.
|
* Update single user.
|
||||||
*
|
*
|
||||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Auth;
|
|||||||
use FireflyIII\Events\RegisteredUser;
|
use FireflyIII\Events\RegisteredUser;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\Support\Http\Controllers\CreateStuff;
|
use FireflyIII\Support\Http\Controllers\CreateStuff;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
@@ -88,11 +89,15 @@ class RegisterController extends Controller
|
|||||||
public function register(Request $request)
|
public function register(Request $request)
|
||||||
{
|
{
|
||||||
$allowRegistration = $this->allowedToRegister();
|
$allowRegistration = $this->allowedToRegister();
|
||||||
|
$inviteCode = (string) $request->get('invite_code');
|
||||||
|
$repository = app(UserRepositoryInterface::class);
|
||||||
|
$validCode = $repository->validateInviteCode($inviteCode);
|
||||||
|
|
||||||
if (false === $allowRegistration) {
|
if (false === $allowRegistration && false === $validCode) {
|
||||||
throw new FireflyException('Registration is currently not available :(');
|
throw new FireflyException('Registration is currently not available :(');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->validator($request->all())->validate();
|
$this->validator($request->all())->validate();
|
||||||
$user = $this->createUser($request->all());
|
$user = $this->createUser($request->all());
|
||||||
Log::info(sprintf('Registered new user %s', $user->email));
|
Log::info(sprintf('Registered new user %s', $user->email));
|
||||||
@@ -104,6 +109,10 @@ class RegisterController extends Controller
|
|||||||
|
|
||||||
$this->registered($request, $user);
|
$this->registered($request, $user);
|
||||||
|
|
||||||
|
if ($validCode) {
|
||||||
|
$repository->redeemCode($inviteCode);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect($this->redirectPath());
|
return redirect($this->redirectPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,4 +166,39 @@ class RegisterController extends Controller
|
|||||||
|
|
||||||
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
|
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the application registration form if the invitation code is valid.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return Factory|View
|
||||||
|
* @throws ContainerExceptionInterface
|
||||||
|
* @throws FireflyException
|
||||||
|
* @throws NotFoundExceptionInterface
|
||||||
|
*/
|
||||||
|
public function showInviteForm(Request $request, string $code)
|
||||||
|
{
|
||||||
|
$isDemoSite = app('fireflyconfig')->get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||||
|
$pageTitle = (string) trans('firefly.register_page_title');
|
||||||
|
$repository = app(UserRepositoryInterface::class);
|
||||||
|
$allowRegistration = $this->allowedToRegister();
|
||||||
|
$inviteCode = $code;
|
||||||
|
$validCode = $repository->validateInviteCode($inviteCode);
|
||||||
|
|
||||||
|
if (true === $allowRegistration) {
|
||||||
|
$message = 'You do not need an invite code on this installation.';
|
||||||
|
|
||||||
|
return view('error', compact('message'));
|
||||||
|
}
|
||||||
|
if(false === $validCode) {
|
||||||
|
$message = 'Invalid code.';
|
||||||
|
|
||||||
|
return view('error', compact('message'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = $request->old('email');
|
||||||
|
|
||||||
|
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle', 'inviteCode'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
43
app/Http/Requests/InviteUserFormRequest.php
Normal file
43
app/Http/Requests/InviteUserFormRequest.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* InviteUserFormRequest.php
|
||||||
|
* Copyright (c) 2022 james@firefly-iii.org
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Requests;
|
||||||
|
|
||||||
|
use FireflyIII\Support\Request\ChecksLogin;
|
||||||
|
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class InviteUserFormRequest extends FormRequest
|
||||||
|
{
|
||||||
|
use ConvertsDataTypes, ChecksLogin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rules for this request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'invited_user' => 'required|email|unique:invited_users,email',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
49
app/Models/InvitedUser.php
Normal file
49
app/Models/InvitedUser.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* InvitedUser.php
|
||||||
|
* Copyright (c) 2022 james@firefly-iii.org
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Models;
|
||||||
|
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvitedUser
|
||||||
|
*/
|
||||||
|
class InvitedUser extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed'];
|
||||||
|
|
||||||
|
protected $casts
|
||||||
|
= [
|
||||||
|
'expires' => 'datetime',
|
||||||
|
'redeemed' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
@@ -22,7 +22,76 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Notifications\Admin;
|
namespace FireflyIII\Notifications\Admin;
|
||||||
|
|
||||||
class UserInvitation
|
use FireflyIII\Models\InvitedUser;
|
||||||
{
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UserInvitation
|
||||||
|
*/
|
||||||
|
class UserInvitation extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
private InvitedUser $invitee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(InvitedUser $invitee)
|
||||||
|
{
|
||||||
|
$this->invitee = $invitee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification's delivery channels.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function via($notifiable)
|
||||||
|
{
|
||||||
|
return ['mail', 'slack'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mail representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||||
|
*/
|
||||||
|
public function toMail($notifiable)
|
||||||
|
{
|
||||||
|
return (new MailMessage)
|
||||||
|
->markdown('emails.invitation-created', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email])
|
||||||
|
->subject((string) trans('email.invitation_created_subject'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Slack representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return SlackMessage
|
||||||
|
*/
|
||||||
|
public function toSlack($notifiable)
|
||||||
|
{
|
||||||
|
return (new SlackMessage)->content((string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the array representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray($notifiable)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ namespace FireflyIII\Providers;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use FireflyIII\Events\ActuallyLoggedIn;
|
use FireflyIII\Events\ActuallyLoggedIn;
|
||||||
|
use FireflyIII\Events\Admin\InvitationCreated;
|
||||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||||
use FireflyIII\Events\DetectedNewIPAddress;
|
use FireflyIII\Events\DetectedNewIPAddress;
|
||||||
@@ -113,6 +114,11 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
NewVersionAvailable::class => [
|
NewVersionAvailable::class => [
|
||||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
||||||
],
|
],
|
||||||
|
InvitationCreated::class => [
|
||||||
|
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
|
||||||
|
//'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
|
||||||
|
],
|
||||||
|
|
||||||
// is a Transaction Journal related event.
|
// is a Transaction Journal related event.
|
||||||
StoredTransactionGroup::class => [
|
StoredTransactionGroup::class => [
|
||||||
'FireflyIII\Handlers\Events\StoredGroupEventHandler@processRules',
|
'FireflyIII\Handlers\Events\StoredGroupEventHandler@processRules',
|
||||||
|
@@ -22,9 +22,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Repositories\User;
|
namespace FireflyIII\Repositories\User;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Models\BudgetLimit;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
|
use FireflyIII\Models\InvitedUser;
|
||||||
use FireflyIII\Models\Role;
|
use FireflyIII\Models\Role;
|
||||||
use FireflyIII\Models\UserGroup;
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
@@ -103,22 +105,6 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return $this->all()->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function all(): Collection
|
|
||||||
{
|
|
||||||
return User::orderBy('id', 'DESC')->get(['users.*']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $displayName
|
* @param string $displayName
|
||||||
@@ -164,6 +150,22 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->all()->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function all(): Collection
|
||||||
|
{
|
||||||
|
return User::orderBy('id', 'DESC')->get(['users.*']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $userId
|
* @param int $userId
|
||||||
*
|
*
|
||||||
@@ -265,6 +267,24 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function inviteUser(User $user, string $email): InvitedUser
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$now->addDays(2);
|
||||||
|
$invitee = new InvitedUser;
|
||||||
|
$invitee->user()->associate($user);
|
||||||
|
$invitee->invite_code = Str::random(64);
|
||||||
|
$invitee->email = $email;
|
||||||
|
$invitee->redeemed = false;
|
||||||
|
$invitee->expires = $now;
|
||||||
|
$invitee->save();
|
||||||
|
|
||||||
|
return $invitee;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set MFA code.
|
* Set MFA code.
|
||||||
*
|
*
|
||||||
@@ -416,4 +436,34 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
{
|
{
|
||||||
return Role::where('name', $role)->first();
|
return Role::where('name', $role)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getInvitedUsers(): Collection
|
||||||
|
{
|
||||||
|
return InvitedUser::with('user')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function validateInviteCode(string $code): bool
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$invitee = InvitedUser::where('invite_code', $code)->where('expires', '<=', $now)->where('redeemed', 0)->first();
|
||||||
|
return null !== $invitee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function redeemCode(string $code): void
|
||||||
|
{
|
||||||
|
$obj = InvitedUser::where('invite_code', $code)->where('redeemed', 0)->first();
|
||||||
|
if ($obj) {
|
||||||
|
$obj->redeemed = true;
|
||||||
|
$obj->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Repositories\User;
|
namespace FireflyIII\Repositories\User;
|
||||||
|
|
||||||
|
use FireflyIII\Models\InvitedUser;
|
||||||
use FireflyIII\Models\Role;
|
use FireflyIII\Models\Role;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -159,6 +160,30 @@ interface UserRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function hasRole(User $user, string $role): bool;
|
public function hasRole(User $user, string $role): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
* @param string $email
|
||||||
|
* @return InvitedUser
|
||||||
|
*/
|
||||||
|
public function inviteUser(User $user, string $email): InvitedUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getInvitedUsers(): Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $code
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateInviteCode(string $code): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $code
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function redeemCode(string $code): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove any role the user has.
|
* Remove any role the user has.
|
||||||
*
|
*
|
||||||
|
@@ -49,6 +49,7 @@ use FireflyIII\Models\TransactionJournal;
|
|||||||
use FireflyIII\Models\UserGroup;
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\Models\Webhook;
|
use FireflyIII\Models\Webhook;
|
||||||
use FireflyIII\Notifications\Admin\TestNotification;
|
use FireflyIII\Notifications\Admin\TestNotification;
|
||||||
|
use FireflyIII\Notifications\Admin\UserInvitation;
|
||||||
use FireflyIII\Notifications\Admin\UserRegistration;
|
use FireflyIII\Notifications\Admin\UserRegistration;
|
||||||
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -599,6 +600,9 @@ class User extends Authenticatable
|
|||||||
if ($notification instanceof VersionCheckResult) {
|
if ($notification instanceof VersionCheckResult) {
|
||||||
return app('fireflyconfig')->get('slack_webhook_url', '')->data;
|
return app('fireflyconfig')->get('slack_webhook_url', '')->data;
|
||||||
}
|
}
|
||||||
|
if ($notification instanceof UserInvitation) {
|
||||||
|
return app('fireflyconfig')->get('slack_webhook_url', '')->data;
|
||||||
|
}
|
||||||
return app('preferences')->getForUser($this, 'slack_webhook_url', '')->data;
|
return app('preferences')->getForUser($this, 'slack_webhook_url', '')->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -150,7 +150,7 @@ return [
|
|||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
'available_notifications' => ['bill_reminder', 'new_access_token', 'transaction_creation', 'user_login'],
|
'available_notifications' => ['bill_reminder', 'new_access_token', 'transaction_creation', 'user_login'],
|
||||||
'admin_notifications' => ['admin_new_reg', 'user_new_reg', 'new_version'],
|
'admin_notifications' => ['admin_new_reg', 'user_new_reg', 'new_version', 'invite_created', 'invite_redeemed'],
|
||||||
|
|
||||||
// enabled languages
|
// enabled languages
|
||||||
'languages' => [
|
'languages' => [
|
||||||
@@ -486,7 +486,7 @@ return [
|
|||||||
'update_piggy' => UpdatePiggybank::class,
|
'update_piggy' => UpdatePiggybank::class,
|
||||||
'delete_transaction' => DeleteTransaction::class,
|
'delete_transaction' => DeleteTransaction::class,
|
||||||
'append_descr_to_notes' => AppendDescriptionToNotes::class,
|
'append_descr_to_notes' => AppendDescriptionToNotes::class,
|
||||||
'append_notes_to_descr' => AppendNotesToDescription::class,
|
'append_notes_to_descr' => AppendNotesToDescription::class,
|
||||||
'move_descr_to_notes' => MoveDescriptionToNotes::class,
|
'move_descr_to_notes' => MoveDescriptionToNotes::class,
|
||||||
'move_notes_to_descr' => MoveNotesToDescription::class,
|
'move_notes_to_descr' => MoveNotesToDescription::class,
|
||||||
],
|
],
|
||||||
|
37
database/migrations/2022_10_01_074908_invited_users.php
Normal file
37
database/migrations/2022_10_01_074908_invited_users.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('invited_users', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->integer('user_id', false, true);
|
||||||
|
$table->string('email', 255);
|
||||||
|
$table->string('invite_code', 64);
|
||||||
|
$table->dateTime('expires');
|
||||||
|
$table->boolean('redeemed');
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('invited_users');
|
||||||
|
}
|
||||||
|
};
|
@@ -11,14 +11,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.2",
|
"@popperjs/core": "^2.11.2",
|
||||||
"@quasar/extras": "^1.14.3",
|
"@quasar/extras": "^1.15.4",
|
||||||
"apexcharts": "^3.32.1",
|
"apexcharts": "^3.32.1",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"axios-cache-adapter": "^2.7.3",
|
"axios-cache-adapter": "^2.7.3",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
"quasar": "^2.7.5",
|
"quasar": "^2.8.4",
|
||||||
"vue": "3",
|
"vue": "3",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.0.0",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.13.14",
|
"@babel/eslint-parser": "^7.13.14",
|
||||||
"@quasar/app-webpack": "^3.5.7",
|
"@quasar/app-webpack": "^3.6.1",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||||
"@typescript-eslint/parser": "^4.16.1",
|
"@typescript-eslint/parser": "^4.16.1",
|
||||||
|
@@ -1165,10 +1165,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8"
|
resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8"
|
||||||
integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg=
|
integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg=
|
||||||
|
|
||||||
"@quasar/app-webpack@^3.5.7":
|
"@quasar/app-webpack@^3.6.1":
|
||||||
version "3.5.7"
|
version "3.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@quasar/app-webpack/-/app-webpack-3.5.7.tgz#1c314288dfa8fd0b4167fd46d74d3e90c21d0ce1"
|
resolved "https://registry.yarnpkg.com/@quasar/app-webpack/-/app-webpack-3.6.1.tgz#f2189316e931b77df9c3c63bf21f6108c3111a2a"
|
||||||
integrity sha512-VvOOzvYjJa1ibl/w+dcvnBP9r97dtVHqaf3N7tTj7QaT667QX/BN6wCx2qKh8KOFODvKVs8vt/Nvvw02pWn7YA==
|
integrity sha512-dwHs34fMXPaAY6iO4B0Qs6zexIS8kv9kI9LuYDEiU9uHndn6roH0Wsnk6MjiHk/4zPqFAKIVn6zF6FdE5nD9nA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@quasar/babel-preset-app" "2.0.1"
|
"@quasar/babel-preset-app" "2.0.1"
|
||||||
"@quasar/fastclick" "1.1.5"
|
"@quasar/fastclick" "1.1.5"
|
||||||
@@ -1215,7 +1215,7 @@
|
|||||||
ouch "^2.0.1"
|
ouch "^2.0.1"
|
||||||
postcss "^8.4.4"
|
postcss "^8.4.4"
|
||||||
postcss-loader "6.2.1"
|
postcss-loader "6.2.1"
|
||||||
postcss-rtlcss "3.6.3"
|
postcss-rtlcss "3.7.2"
|
||||||
pretty-error "4.0.0"
|
pretty-error "4.0.0"
|
||||||
register-service-worker "1.7.2"
|
register-service-worker "1.7.2"
|
||||||
sass "1.32.12"
|
sass "1.32.12"
|
||||||
@@ -1231,7 +1231,7 @@
|
|||||||
webpack "^5.58.1"
|
webpack "^5.58.1"
|
||||||
webpack-bundle-analyzer "4.5.0"
|
webpack-bundle-analyzer "4.5.0"
|
||||||
webpack-chain "6.5.1"
|
webpack-chain "6.5.1"
|
||||||
webpack-dev-server "4.9.2"
|
webpack-dev-server "4.9.3"
|
||||||
webpack-merge "5.8.0"
|
webpack-merge "5.8.0"
|
||||||
webpack-node-externals "3.0.0"
|
webpack-node-externals "3.0.0"
|
||||||
|
|
||||||
@@ -1261,10 +1261,10 @@
|
|||||||
core-js "^3.6.5"
|
core-js "^3.6.5"
|
||||||
core-js-compat "^3.6.5"
|
core-js-compat "^3.6.5"
|
||||||
|
|
||||||
"@quasar/extras@^1.14.3":
|
"@quasar/extras@^1.15.4":
|
||||||
version "1.14.3"
|
version "1.15.4"
|
||||||
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.14.3.tgz#2a4d7a2f773a789ca43e3a02d5b797c9d2888884"
|
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.15.4.tgz#c3c78416c2c39e4d4e791e8bd4dd6ff4b7e14b14"
|
||||||
integrity sha512-OHyR/pfW0R8E5DvnsV1wg9ISnLL/yXHyOMZsqPY3gUtmfdF2634x2osdVg4YpBYW29vIQeV5feGWGIx8nuprdA==
|
integrity sha512-GGURiH/K/IZM41RD9hcNG0Zly63sFGFZ97Q+doVMFSGBqNySfVNsb3WFSovOyL5K/Lfnb/sjzslroVIUoDVTKw==
|
||||||
|
|
||||||
"@quasar/fastclick@1.1.5":
|
"@quasar/fastclick@1.1.5":
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
@@ -2481,10 +2481,10 @@ concat-map@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
connect-history-api-fallback@^1.6.0:
|
connect-history-api-fallback@^2.0.0:
|
||||||
version "1.6.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
|
||||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
|
||||||
|
|
||||||
content-disposition@0.5.4:
|
content-disposition@0.5.4:
|
||||||
version "0.5.4"
|
version "0.5.4"
|
||||||
@@ -5158,10 +5158,10 @@ postcss-reduce-transforms@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
|
||||||
postcss-rtlcss@3.6.3:
|
postcss-rtlcss@3.7.2:
|
||||||
version "3.6.3"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-rtlcss/-/postcss-rtlcss-3.6.3.tgz#aabd1122a5b082157ea06d606c441002c1060030"
|
resolved "https://registry.yarnpkg.com/postcss-rtlcss/-/postcss-rtlcss-3.7.2.tgz#0a06cbfd74aec36ad1fe6c6fd1ec74069c62ce45"
|
||||||
integrity sha512-jJlS7gM5JPH8n/hcHqqekK8wusdFEFYi79mBvAK2GWvl3aehOFgj9vEMwFzUTJrrErakYTgiQ+uuGAzdL98g0g==
|
integrity sha512-GurrGedCKvOTe1QrifI+XpDKXA3bJky1v8KiOa/TYYHs1bfJOxI53GIRvVSqLJLly7e1WcNMz8KMESTN01vbZQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
rtlcss "^3.5.0"
|
rtlcss "^3.5.0"
|
||||||
|
|
||||||
@@ -5259,10 +5259,10 @@ qs@6.9.7:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
||||||
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
||||||
|
|
||||||
quasar@^2.7.5:
|
quasar@^2.8.4:
|
||||||
version "2.7.5"
|
version "2.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.7.5.tgz#a3feb5d50647313c4d6e1451223c158e10792902"
|
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.8.4.tgz#d32d7f0c1c4f313ee45f8f3d72028f3085727172"
|
||||||
integrity sha512-DWI0S+bXASfMSPrB8c/LVsXpA4dF7cBUbaJlcrM+1ioTNBHtiudma2Nhk2SDd5bzk9AYVHh5A8JCZuKqQAXt7g==
|
integrity sha512-bygg0GgSwQyrUJJTaHmYV50nVrz779QsNeH/cg2R/SHOQ4UmJI2FBA1hxU/nlpJ6DbmezNab1COa5ID57PvKfw==
|
||||||
|
|
||||||
queue-microtask@^1.2.2:
|
queue-microtask@^1.2.2:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
@@ -6404,10 +6404,10 @@ webpack-dev-middleware@^5.3.1:
|
|||||||
range-parser "^1.2.1"
|
range-parser "^1.2.1"
|
||||||
schema-utils "^4.0.0"
|
schema-utils "^4.0.0"
|
||||||
|
|
||||||
webpack-dev-server@4.9.2:
|
webpack-dev-server@4.9.3:
|
||||||
version "4.9.2"
|
version "4.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.2.tgz#c188db28c7bff12f87deda2a5595679ebbc3c9bc"
|
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9"
|
||||||
integrity sha512-H95Ns95dP24ZsEzO6G9iT+PNw4Q7ltll1GfJHV4fKphuHWgKFzGHWi4alTlTnpk1SPPk41X+l2RB7rLfIhnB9Q==
|
integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/bonjour" "^3.5.9"
|
"@types/bonjour" "^3.5.9"
|
||||||
"@types/connect-history-api-fallback" "^1.3.5"
|
"@types/connect-history-api-fallback" "^1.3.5"
|
||||||
@@ -6421,7 +6421,7 @@ webpack-dev-server@4.9.2:
|
|||||||
chokidar "^3.5.3"
|
chokidar "^3.5.3"
|
||||||
colorette "^2.0.10"
|
colorette "^2.0.10"
|
||||||
compression "^1.7.4"
|
compression "^1.7.4"
|
||||||
connect-history-api-fallback "^1.6.0"
|
connect-history-api-fallback "^2.0.0"
|
||||||
default-gateway "^6.0.3"
|
default-gateway "^6.0.3"
|
||||||
express "^4.17.3"
|
express "^4.17.3"
|
||||||
graceful-fs "^4.2.6"
|
graceful-fs "^4.2.6"
|
||||||
|
@@ -33,6 +33,10 @@ return [
|
|||||||
'admin_test_subject' => 'A test message from your Firefly III installation',
|
'admin_test_subject' => 'A test message from your Firefly III installation',
|
||||||
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
|
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
|
||||||
|
|
||||||
|
// invite
|
||||||
|
'invitation_created_subject' => 'An invitation has been created',
|
||||||
|
'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.',
|
||||||
|
|
||||||
// new IP
|
// new IP
|
||||||
'login_from_new_ip' => 'New login on Firefly III',
|
'login_from_new_ip' => 'New login on Firefly III',
|
||||||
'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)',
|
'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)',
|
||||||
|
@@ -2221,62 +2221,70 @@ return [
|
|||||||
'updated_tag' => 'Updated tag ":tag"',
|
'updated_tag' => 'Updated tag ":tag"',
|
||||||
'created_tag' => 'Tag ":tag" has been created!',
|
'created_tag' => 'Tag ":tag" has been created!',
|
||||||
|
|
||||||
'transaction_journal_information' => 'Transaction information',
|
'transaction_journal_information' => 'Transaction information',
|
||||||
'transaction_journal_meta' => 'Meta information',
|
'transaction_journal_meta' => 'Meta information',
|
||||||
'transaction_journal_more' => 'More information',
|
'transaction_journal_more' => 'More information',
|
||||||
'basic_journal_information' => 'Basic transaction information',
|
'basic_journal_information' => 'Basic transaction information',
|
||||||
'transaction_journal_extra' => 'Extra information',
|
'transaction_journal_extra' => 'Extra information',
|
||||||
'att_part_of_journal' => 'Stored under ":journal"',
|
'att_part_of_journal' => 'Stored under ":journal"',
|
||||||
'total_amount' => 'Total amount',
|
'total_amount' => 'Total amount',
|
||||||
'number_of_decimals' => 'Number of decimals',
|
'number_of_decimals' => 'Number of decimals',
|
||||||
|
|
||||||
// administration
|
// administration
|
||||||
'administration' => 'Administration',
|
'invite_new_user_title' => 'Invite new user',
|
||||||
'user_administration' => 'User administration',
|
'invite_new_user_text' => 'As an administrator, you can invite users to register on your Firefly III administration. Using the direct link you can share with them, they will be able to register an account. The invited user and their invite link will appear in the table below. You are free to share the invitation link with them.',
|
||||||
'list_all_users' => 'All users',
|
'invited_user_mail' => 'Email address',
|
||||||
'all_users' => 'All users',
|
'invite_user' => 'Invite user',
|
||||||
'instance_configuration' => 'Configuration',
|
'user_is_invited' => 'Email address ":address" was invited to Firefly III',
|
||||||
'firefly_instance_configuration' => 'Configuration options for Firefly III',
|
'administration' => 'Administration',
|
||||||
'setting_single_user_mode' => 'Single user mode',
|
'code_already_used' => 'Invite code has been used',
|
||||||
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).',
|
'user_administration' => 'User administration',
|
||||||
'store_configuration' => 'Store configuration',
|
'list_all_users' => 'All users',
|
||||||
'single_user_administration' => 'User administration for :email',
|
'all_users' => 'All users',
|
||||||
'edit_user' => 'Edit user :email',
|
'instance_configuration' => 'Configuration',
|
||||||
'hidden_fields_preferences' => 'You can enable more transaction options in your <a href="preferences">preferences</a>.',
|
'firefly_instance_configuration' => 'Configuration options for Firefly III',
|
||||||
'user_data_information' => 'User data',
|
'setting_single_user_mode' => 'Single user mode',
|
||||||
'user_information' => 'User information',
|
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).',
|
||||||
'total_size' => 'total size',
|
'store_configuration' => 'Store configuration',
|
||||||
'budget_or_budgets' => ':count budget|:count budgets',
|
'single_user_administration' => 'User administration for :email',
|
||||||
'budgets_with_limits' => ':count budget with configured amount|:count budgets with configured amount',
|
'edit_user' => 'Edit user :email',
|
||||||
'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)',
|
'hidden_fields_preferences' => 'You can enable more transaction options in your <a href="preferences">preferences</a>.',
|
||||||
'tag_or_tags' => ':count tag|:count tags',
|
'user_data_information' => 'User data',
|
||||||
'configuration_updated' => 'The configuration has been updated',
|
'user_information' => 'User information',
|
||||||
'setting_is_demo_site' => 'Demo site',
|
'total_size' => 'total size',
|
||||||
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
|
'budget_or_budgets' => ':count budget|:count budgets',
|
||||||
'block_code_bounced' => 'Email message(s) bounced',
|
'budgets_with_limits' => ':count budget with configured amount|:count budgets with configured amount',
|
||||||
'block_code_expired' => 'Demo account expired',
|
'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)',
|
||||||
'no_block_code' => 'No reason for block or user not blocked',
|
'tag_or_tags' => ':count tag|:count tags',
|
||||||
'block_code_email_changed' => 'User has not yet confirmed new email address',
|
'configuration_updated' => 'The configuration has been updated',
|
||||||
'admin_update_email' => 'Contrary to the profile page, the user will NOT be notified their email address has changed!',
|
'setting_is_demo_site' => 'Demo site',
|
||||||
'update_user' => 'Update user',
|
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
|
||||||
'updated_user' => 'User data has been changed.',
|
'block_code_bounced' => 'Email message(s) bounced',
|
||||||
'delete_user' => 'Delete user :email',
|
'block_code_expired' => 'Demo account expired',
|
||||||
'user_deleted' => 'The user has been deleted',
|
'no_block_code' => 'No reason for block or user not blocked',
|
||||||
'send_test_email' => 'Send test email message',
|
'block_code_email_changed' => 'User has not yet confirmed new email address',
|
||||||
'send_test_email_text' => 'To see if your installation is capable of sending email or posting Slack messages, please press this button. You will not see an error here (if any), <strong>the log files will reflect any errors</strong>. You can press this button as many times as you like. There is no spam control. The message will be sent to <code>:email</code> and should arrive shortly.',
|
'admin_update_email' => 'Contrary to the profile page, the user will NOT be notified their email address has changed!',
|
||||||
'send_message' => 'Send message',
|
'update_user' => 'Update user',
|
||||||
'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.',
|
'updated_user' => 'User data has been changed.',
|
||||||
'give_admin_careful' => 'Users who are given admin rights can take away yours. Be careful.',
|
'delete_user' => 'Delete user :email',
|
||||||
'admin_maintanance_title' => 'Maintenance',
|
'user_deleted' => 'The user has been deleted',
|
||||||
'admin_maintanance_expl' => 'Some nifty buttons for Firefly III maintenance',
|
'send_test_email' => 'Send test email message',
|
||||||
'admin_maintenance_clear_cache' => 'Clear cache',
|
'send_test_email_text' => 'To see if your installation is capable of sending email or posting Slack messages, please press this button. You will not see an error here (if any), <strong>the log files will reflect any errors</strong>. You can press this button as many times as you like. There is no spam control. The message will be sent to <code>:email</code> and should arrive shortly.',
|
||||||
'admin_notifications' => 'Admin notifications',
|
'send_message' => 'Send message',
|
||||||
'admin_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. If you want to get these messages over Slack as well, set the "incoming webhook" URL.',
|
'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.',
|
||||||
'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message',
|
'give_admin_careful' => 'Users who are given admin rights can take away yours. Be careful.',
|
||||||
'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification',
|
'admin_maintanance_title' => 'Maintenance',
|
||||||
'admin_notification_check_new_version' => 'A new version is available',
|
'admin_maintanance_expl' => 'Some nifty buttons for Firefly III maintenance',
|
||||||
'save_notification_settings' => 'Save settings',
|
'admin_maintenance_clear_cache' => 'Clear cache',
|
||||||
'notification_settings_saved' => 'The notification settings have been saved',
|
'admin_notifications' => 'Admin notifications',
|
||||||
|
'admin_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. If you want to get these messages over Slack as well, set the "incoming webhook" URL.',
|
||||||
|
'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message',
|
||||||
|
'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification',
|
||||||
|
'admin_notification_check_new_version' => 'A new version is available',
|
||||||
|
'admin_notification_check_invite_created' => 'A user is invited to Firefly III',
|
||||||
|
'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed',
|
||||||
|
'save_notification_settings' => 'Save settings',
|
||||||
|
'notification_settings_saved' => 'The notification settings have been saved',
|
||||||
|
|
||||||
|
|
||||||
'split_transaction_title' => 'Description of the split transaction',
|
'split_transaction_title' => 'Description of the split transaction',
|
||||||
|
@@ -43,6 +43,10 @@ return [
|
|||||||
'lastActivity' => 'Last activity',
|
'lastActivity' => 'Last activity',
|
||||||
'balanceDiff' => 'Balance difference',
|
'balanceDiff' => 'Balance difference',
|
||||||
'other_meta_data' => 'Other meta data',
|
'other_meta_data' => 'Other meta data',
|
||||||
|
'invited_at' => 'Invited at',
|
||||||
|
'expires' => 'Invitation expires',
|
||||||
|
'invited_by' => 'Invited by',
|
||||||
|
'invite_link' => 'Invite link',
|
||||||
'account_type' => 'Account type',
|
'account_type' => 'Account type',
|
||||||
'created_at' => 'Created at',
|
'created_at' => 'Created at',
|
||||||
'account' => 'Account',
|
'account' => 'Account',
|
||||||
|
@@ -4,6 +4,31 @@
|
|||||||
{{ Breadcrumbs.render }}
|
{{ Breadcrumbs.render }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if allowInvites %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<form action="{{ route('admin.users.invite') }}" method="post">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'invite_new_user_title'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
<p>
|
||||||
|
{{ 'invite_new_user_text'|_ }}
|
||||||
|
</p>
|
||||||
|
{{ ExpandedForm.text('invited_user',null, {'type': 'email', 'label' : 'invited_user_mail'|_}) }}
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<button type="submit" class="btn pull-right btn-success">
|
||||||
|
{{ ('invite_user')|_ }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="box box-primary">
|
<div class="box box-primary">
|
||||||
@@ -29,8 +54,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="hidden-xs" data-value="{{ user.id }}">
|
<td class="hidden-xs" data-value="{{ user.id }}">
|
||||||
<div class="btn-group btn-group-xs">
|
<div class="btn-group btn-group-xs">
|
||||||
<a class="btn btn-default" href="{{ route('admin.users.edit',user.id) }}"><span class="fa fa-pencil"></span></a>
|
<a class="btn btn-default" href="{{ route('admin.users.edit',user.id) }}"><span
|
||||||
<a class="btn btn-danger" href="{{ route('admin.users.delete',user.id) }}"><span class="fa fa-trash"></span></a>
|
class="fa fa-pencil"></span></a>
|
||||||
|
<a class="btn btn-danger" href="{{ route('admin.users.delete',user.id) }}"><span
|
||||||
|
class="fa fa-trash"></span></a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden-xs" data-value="{{ user.id }}">#{{ user.id }}</td>
|
<td class="hidden-xs" data-value="{{ user.id }}">#{{ user.id }}</td>
|
||||||
@@ -60,9 +87,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td data-value="{% if user.blocked %}1{% else %}0{% endif %}">
|
<td data-value="{% if user.blocked %}1{% else %}0{% endif %}">
|
||||||
{% if user.blocked == 1 %}
|
{% if user.blocked == 1 %}
|
||||||
<small class="text-danger"><span class="fa fa-fw fa-check" title="{{ 'yes'|_ }}"></span></small>
|
<small class="text-danger"><span class="fa fa-fw fa-check"
|
||||||
|
title="{{ 'yes'|_ }}"></span></small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<small class="text-success"><span class="fa fa-fw fa-times" title="{{ 'no'|_ }}"></span></small>
|
<small class="text-success"><span class="fa fa-fw fa-times"
|
||||||
|
title="{{ 'no'|_ }}"></span></small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden-xs">
|
<td class="hidden-xs">
|
||||||
@@ -84,10 +113,71 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if invitedUsers.count > 0 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'all_invited_users'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body no-padding">
|
||||||
|
<table class="table table-responsive table-condensed sortable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-defaultsign="_19" class="hidden-xs" colspan="1"> </th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.email') }}</th>
|
||||||
|
<th data-defaultsign="month" class="hidden-xs">{{ trans('list.invited_at') }}</th>
|
||||||
|
<th data-defaultsign="month" class="hidden-xs">{{ trans('list.expires') }}</th>
|
||||||
|
<th class="hidden-xs">{{ trans('list.invited_by') }}</th>
|
||||||
|
<th>{{ trans('list.invite_link') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for invitee in invitedUsers %}
|
||||||
|
<tr>
|
||||||
|
<td class="hidden-xs" data-value="{{ user.id }}">
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<a class="btn btn-danger" href="{{ route('admin.users.delete-invite', invitee.id) }}"><span
|
||||||
|
class="fa fa-trash"></span></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ invitee.email }}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-xs">
|
||||||
|
{{ invitee.created_at.isoFormat(monthAndDayFormat) }}
|
||||||
|
{{ invitee.created_at.format('H:i') }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ invitee.expires.isoFormat(monthAndDayFormat) }}
|
||||||
|
{{ invitee.expires.format('H:i') }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ invitee.user.email }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if invitee.redeemed %}
|
||||||
|
<em><s>{{ 'code_already_used'|_ }}</s></em>
|
||||||
|
{% else %}
|
||||||
|
<input type="text" class="form-control" readonly value="{{ route('invite', [invitee.invite_code]) }}">
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<link rel="stylesheet" href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all" nonce="{{ JS_NONCE }}">
|
<link rel="stylesheet" href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all"
|
||||||
|
nonce="{{ JS_NONCE }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script>
|
<script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}"
|
||||||
|
nonce="{{ JS_NONCE }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
<form action="{{ route('register') }}" method="post">
|
<form action="{{ route('register') }}" method="post">
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="invite_code" value="{{ inviteCode }}">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="email" name="email" value="{{ email }}" class="form-control"
|
<input type="email" name="email" value="{{ email }}" class="form-control"
|
||||||
placeholder="{{ trans('form.email') }}"/>
|
placeholder="{{ trans('form.email') }}"/>
|
||||||
|
3
resources/views/emails/invitation-created.blade.php
Normal file
3
resources/views/emails/invitation-created.blade.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@component('mail::message')
|
||||||
|
{{ trans('email.invitation_created_body', ['email' => $email,'invitee' => $invitee]) }}
|
||||||
|
@endcomponent
|
@@ -58,6 +58,7 @@ Route::group(
|
|||||||
|
|
||||||
// Registration Routes...
|
// Registration Routes...
|
||||||
Route::get('register', ['uses' => 'Auth\RegisterController@showRegistrationForm', 'as' => 'register']);
|
Route::get('register', ['uses' => 'Auth\RegisterController@showRegistrationForm', 'as' => 'register']);
|
||||||
|
Route::get('invitee/{code}', ['uses' => 'Auth\RegisterController@showInviteForm', 'as' => 'invite']);
|
||||||
Route::post('register', 'Auth\RegisterController@register');
|
Route::post('register', 'Auth\RegisterController@register');
|
||||||
|
|
||||||
// Password Reset Routes...
|
// Password Reset Routes...
|
||||||
@@ -1101,6 +1102,10 @@ Route::group(
|
|||||||
Route::post('users/update/{user}', ['uses' => 'UserController@update', 'as' => 'users.update']);
|
Route::post('users/update/{user}', ['uses' => 'UserController@update', 'as' => 'users.update']);
|
||||||
Route::post('users/destroy/{user}', ['uses' => 'UserController@destroy', 'as' => 'users.destroy']);
|
Route::post('users/destroy/{user}', ['uses' => 'UserController@destroy', 'as' => 'users.destroy']);
|
||||||
|
|
||||||
|
// invitee management
|
||||||
|
Route::get('users/delete_invite/{invitedUser}', ['uses' => 'UserController@deleteInvite', 'as' => 'users.delete-invite']);
|
||||||
|
Route::post('users/invite', ['uses' => 'UserController@invite', 'as' => 'users.invite']);
|
||||||
|
|
||||||
// journal links manager
|
// journal links manager
|
||||||
Route::get('links', ['uses' => 'LinkController@index', 'as' => 'links.index']);
|
Route::get('links', ['uses' => 'LinkController@index', 'as' => 'links.index']);
|
||||||
Route::get('links/create', ['uses' => 'LinkController@create', 'as' => 'links.create']);
|
Route::get('links/create', ['uses' => 'LinkController@create', 'as' => 'links.create']);
|
||||||
|
Reference in New Issue
Block a user