This commit is contained in:
2023-10-02 23:10:49 +03:00
commit c6cb488379
255 changed files with 18731 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Lucent\Account;
use Lucent\Primitive\Collection;
readonly class AccountService
{
public function __construct(
private UserRepo $userRepo,
)
{
}
/**
* @return Collection<User>
*/
public function all(): Collection
{
return $this->userRepo->all();
}
/**
* @return Collection<UserProfile>
*/
public function allProfiles(): Collection
{
return $this->all()->map(fn($user) => $user->safe());
}
}
+158
View File
@@ -0,0 +1,158 @@
<?php
namespace Lucent\Account;
use Carbon\Carbon;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Mail\LoginMail;
readonly class AuthService
{
public function __construct(
private ChannelService $channelService,
private UserRepo $userRepo,
public Session $session,
)
{
}
public function currentUserId(): ?string
{
return $this->session->get("user.id");
}
public function isLoggedIn(): bool
{
return !empty($this->currentUserId());
}
/**
* @throws LucentException
*/
public function login(string $email, string $token): void
{
$user = $this->userRepo->findByEmail(new Email($email));
if ($user->isEmpty()) {
throw new LucentException("You account was not found");
}
if ($user->get()->role === Role::REMOVED) {
throw new LucentException("Your account is not active");
}
if ($user->get()->mailToken !== $token) {
throw new LucentException("Token has expired or is invalid");
}
if (Carbon::parse($user->get()->loggedInAt)->lte(Carbon::now()->subHours(1))) {
throw new LucentException("Token has expired.");
}
$newUser = $user->get();
$newUser->updatedAt = Carbon::now()->toJson();
$newUser->mailToken = null;
$this->userRepo->update($newUser);
$this->session->put(["user" => $user->get()->safe()]);
}
public function create(string $name, string $email, string $role): User
{
$user = new User(
id: (string)Str::uuid(),
name: new Name($name),
email: new Email($email),
role: Role::from($role),
createdAt: Carbon::now()->toJson(),
updatedAt: Carbon::now()->toJson(),
loggedInAt: Carbon::now()->toJson(),
mailToken: Token::new(32),
);
$this->userRepo->insert($user);
return $user;
}
/**
* @throws LucentException
*/
public function sendLoginEmail(string $email): void
{
$emailAddress = (new Email($email));
$user = $this->userRepo->findByEmail($emailAddress);
if ($user->isEmpty()) {
throw new LucentException("User not found");
}
if ($user->get()->role === Role::REMOVED) {
throw new LucentException("Cannot reset email if the user is not active");
}
$newToken = $this->userRepo->updateLoginToken($user->get()->id);
Mail::to($email)->send(
new LoginMail(
$email,
$newToken,
$this->channelService->channel->lucentUrl
)
);
}
/**
* @throws LucentException
*/
public function changeRole(string $userId, string $newRole): void
{
$user = $this->userRepo->findById($userId);
if ($user->isEmpty()) {
throw new LucentException("User not found");
}
$newUser = $user->get();
$newUser->role = Role::from($newRole);
$newUser->updatedAt = Carbon::now()->toJson();
$this->userRepo->update($newUser);
}
/**
* @throws LucentException
*/
public function updateName(string $userId, string $name): void
{
$name = (new Name($name));
$this->userRepo->updateName($userId, $name);
}
/**
* @throws LucentException
*/
public function invite(
string $name,
string $email,
string $role
): User
{
$user = $this->create($name, $email, $role);
$this->sendLoginEmail($user->email);
return $user;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Lucent\Account;
use JsonSerializable;
use Lucent\Validator\Validator;
class Email implements JsonSerializable
{
private string $value;
function __construct(string $value)
{
Validator::single("Email", $value, "required|email|min:6|max:50");
$this->value = \strtolower($value);
}
function value(): string
{
return $this->value;
}
function __toString(): string{
return $this->value;
}
public function jsonSerialize(): string
{
return $this->value;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Lucent\Account;
use JsonSerializable;
use Lucent\LucentException;
use Lucent\Validator\Validator;
class Name implements JsonSerializable
{
public string $value;
/**
* @throws LucentException
*/
function __construct(string $value)
{
Validator::single("Name", $value, "required|min:2|max:50");
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
public function __toString(): string
{
return $this->value;
}
public function jsonSerialize(): string
{
return $this->value;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Account;
enum Role: string
{
case ADMIN = 'admin';
case EDITOR = 'editor';
case READER = 'reader';
case REMOVED = 'removed';
function hasAccess(string $roleName): bool
{
$trialRole = Role::from($roleName);
$access = match ($trialRole) {
Role::ADMIN => [Role::ADMIN, Role::DEVELOPER, Role::EDITOR, Role::READER],
Role::EDITOR => [Role::EDITOR, Role::READER],
Role::READER => [Role::READER],
Role::REMOVED => [],
};
return in_array($trialRole, $access);
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Lucent\Account;
class Token
{
public static function new(int $length = 64): string
{
return bin2hex(random_bytes($length));
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Lucent\Account;
class User
{
function __construct(
public string $id,
public Name $name,
public Email $email,
public Role $role,
public string $createdAt,
public string $updatedAt,
public ?string $loggedInAt = null,
public ?string $mailToken = null,
)
{
}
public static function fromArray(array $data): User
{
return new User(
id: $data["id"],
name: new Name($data["name"] ?? ""),
email: new Email($data["email"]),
role: Role::tryFrom($data["role"]),
createdAt: $data["createdAt"],
updatedAt: $data["updatedAt"],
loggedInAt: $data["loggedInAt"] ?? null,
mailToken: $data["mailToken"] ?? null,
);
}
public function safe(): array
{
$userData = collect(toArray($this));
return $userData->only(["id", "name", "email", "role", "status"])->toArray();
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Lucent\Account;
readonly class UserProfile
{
function __construct(
public string $id,
public string $name,
public string $email,
public Role $role,
)
{
}
public static function fromUser(User $user): UserProfile
{
return new UserProfile(
$user->id,
$user->name,
$user->email,
$user->role,
);
}
public static function fromArray(array $data): ?UserProfile
{
if (empty($data)) {
return null;
}
return new UserProfile(
id: data_get($data, "id"),
name: data_get($data, "name"),
email: data_get($data, "email"),
role: data_get($data, "role"),
);
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
namespace Lucent\Account;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Lucent\Primitive\Collection;
use PhpOption\Option;
class UserRepo
{
public function count(): int
{
return DB::table("users")->count();
}
/**
* @return Collection<User>
*/
public function all(): Collection
{
$usersData = DB::table("users")->whereNot("status", "invite")->get();
$users = array_map(fn($userData) => User::fromArray((array)$userData), $usersData->toArray());
return new Collection($users);
}
public static function insert(User $user): void
{
$userData = toArray($user);
DB::table("users")->insert($userData);
}
public function update(User $user): void
{
$userData = toArray($user);
DB::table("users")->where("id", $user->id)->update($userData);
}
public function updateLoginToken(string $id): string
{
$newToken = Token::new(32);
DB::table("users")
->where("id", $id)
->update([
'loggedInAt' => Carbon::now()->toJson(),
'mailToken' => $newToken,
]);
return $newToken;
}
/**
* @return Option<User>
*/
public function findByEmail(Email $email): Option
{
$user = DB::table("users")->where("email", $email->value())->first();
if (empty($user)) {
return none();
}
return some(User::fromArray((array)$user));
}
/**
* @return Option<User>
*/
public function findById(string $id): Option
{
$user = DB::table("users")->where("id", $id)->first();
if (empty($user)) {
return none();
}
return some(User::fromArray((array)$user));
}
public function updateName(string $userId, Name $name): void
{
DB::table("users")->where("id", $userId)->update(["name" => $name->value]);
}
}