init backend presensi
This commit is contained in:
1
app/Modules/Auth/Controllers/.gitkeep
Normal file
1
app/Modules/Auth/Controllers/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Auth Module - Controllers
|
||||
65
app/Modules/Auth/Controllers/AuthController.php
Normal file
65
app/Modules/Auth/Controllers/AuthController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Controllers;
|
||||
|
||||
use App\Core\BaseApiController;
|
||||
use App\Modules\Auth\Services\AuthService;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Auth Controller
|
||||
*
|
||||
* POST /api/auth/login, POST /api/auth/logout, GET /api/auth/me (session-based).
|
||||
*/
|
||||
class AuthController extends BaseApiController
|
||||
{
|
||||
protected AuthService $authService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->authService = new AuthService();
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/login
|
||||
* Body: { "email": "", "password": "" }
|
||||
*/
|
||||
public function login(): ResponseInterface
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$email = $input['email'] ?? '';
|
||||
$password = $input['password'] ?? '';
|
||||
|
||||
if ($email === '' || $password === '') {
|
||||
return $this->errorResponse('Email and password are required', null, null, 400);
|
||||
}
|
||||
|
||||
$user = $this->authService->login($email, $password);
|
||||
if (!$user) {
|
||||
return $this->errorResponse('Invalid email or password', null, null, 401);
|
||||
}
|
||||
|
||||
return $this->successResponse($user, 'Login successful');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout
|
||||
*/
|
||||
public function logout(): ResponseInterface
|
||||
{
|
||||
$this->authService->logout();
|
||||
return $this->successResponse(null, 'Logged out');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/auth/me
|
||||
*/
|
||||
public function me(): ResponseInterface
|
||||
{
|
||||
$user = $this->authService->currentUser();
|
||||
if (!$user) {
|
||||
return $this->errorResponse('Not authenticated', null, null, 401);
|
||||
}
|
||||
return $this->successResponse($user, 'Current user');
|
||||
}
|
||||
}
|
||||
232
app/Modules/Auth/Controllers/UserController.php
Normal file
232
app/Modules/Auth/Controllers/UserController.php
Normal file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Controllers;
|
||||
|
||||
use App\Core\BaseApiController;
|
||||
use App\Modules\Auth\Models\RoleModel;
|
||||
use App\Modules\Auth\Models\UserModel;
|
||||
use App\Modules\Auth\Models\UserRoleModel;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* User API (ADMIN only). List by role; create/update/delete users (for teachers).
|
||||
*/
|
||||
class UserController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* GET /api/users?role=GURU_MAPEL
|
||||
* Returns users with optional role filter. Response: [{ id, name, email }, ...]
|
||||
*/
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$roleCode = $this->request->getGet('role');
|
||||
if ($roleCode === null || $roleCode === '') {
|
||||
return $this->errorResponse('Query parameter role is required', null, null, 422);
|
||||
}
|
||||
|
||||
$roleModel = new RoleModel();
|
||||
$role = $roleModel->findByCode((string) $roleCode);
|
||||
if (!$role) {
|
||||
return $this->successResponse([], 'Users');
|
||||
}
|
||||
|
||||
$userRoleModel = new UserRoleModel();
|
||||
$db = \Config\Database::connect();
|
||||
$builder = $db->table('user_roles');
|
||||
$builder->select('user_id');
|
||||
$builder->where('role_id', $role->id);
|
||||
$rows = $builder->get()->getResultArray();
|
||||
$userIdList = array_values(array_unique(array_map(static fn ($r) => (int) $r['user_id'], $rows)));
|
||||
if ($userIdList === []) {
|
||||
return $this->successResponse([], 'Users');
|
||||
}
|
||||
|
||||
$userModel = new UserModel();
|
||||
$users = $userModel->whereIn('id', $userIdList)->findAll();
|
||||
$data = [];
|
||||
foreach ($users as $u) {
|
||||
$data[] = [
|
||||
'id' => (int) $u->id,
|
||||
'name' => (string) $u->name,
|
||||
'email' => (string) $u->email,
|
||||
];
|
||||
}
|
||||
usort($data, static fn ($a, $b) => strcasecmp($a['name'], $b['name']));
|
||||
|
||||
return $this->successResponse($data, 'Users');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/users
|
||||
* Body (baru): name, email, password, roles[] (isi: GURU_MAPEL dan/atau WALI_KELAS).
|
||||
* Body (lama, tetap didukung): name, email, password, role_code.
|
||||
*/
|
||||
public function store(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$name = trim($payload['name'] ?? '');
|
||||
$email = trim($payload['email'] ?? '');
|
||||
$password = $payload['password'] ?? '';
|
||||
$rolesInput = $payload['roles'] ?? null;
|
||||
$roleCode = $payload['role_code'] ?? null; // fallback lama
|
||||
|
||||
if ($name === '' || $email === '' || $password === '') {
|
||||
return $this->errorResponse('Name, email, and password are required', null, null, 422);
|
||||
}
|
||||
|
||||
$roleModel = new RoleModel();
|
||||
$allowedCodes = ['GURU_MAPEL', 'WALI_KELAS'];
|
||||
|
||||
// Normalisasi roles: jika roles[] tidak ada, pakai role_code tunggal (kompatibilitas lama)
|
||||
$roleCodes = [];
|
||||
if (is_array($rolesInput)) {
|
||||
foreach ($rolesInput as $rc) {
|
||||
$rc = (string) $rc;
|
||||
if (in_array($rc, $allowedCodes, true)) {
|
||||
$roleCodes[] = $rc;
|
||||
}
|
||||
}
|
||||
$roleCodes = array_values(array_unique($roleCodes));
|
||||
} elseif (is_string($roleCode) && $roleCode !== '') {
|
||||
if (! in_array($roleCode, $allowedCodes, true)) {
|
||||
return $this->errorResponse('role_code must be GURU_MAPEL or WALI_KELAS', null, null, 422);
|
||||
}
|
||||
$roleCodes = [$roleCode];
|
||||
}
|
||||
|
||||
if ($roleCodes === []) {
|
||||
return $this->errorResponse('At least one role (GURU_MAPEL or WALI_KELAS) is required', null, null, 422);
|
||||
}
|
||||
|
||||
$userModel = new UserModel();
|
||||
$existing = $userModel->findByEmail($email);
|
||||
if ($existing) {
|
||||
return $this->errorResponse('Email already registered', null, null, 422);
|
||||
}
|
||||
|
||||
$userModel->skipValidation(false);
|
||||
$id = $userModel->insert([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
|
||||
'is_active' => 1,
|
||||
]);
|
||||
if ($id === false) {
|
||||
return $this->errorResponse(implode(' ', $userModel->errors()), $userModel->errors(), null, 422);
|
||||
}
|
||||
|
||||
$userRoleModel = new UserRoleModel();
|
||||
foreach ($roleCodes as $code) {
|
||||
$role = $roleModel->findByCode($code);
|
||||
if ($role) {
|
||||
$userRoleModel->insert(['user_id' => $id, 'role_id' => $role->id]);
|
||||
}
|
||||
}
|
||||
|
||||
$user = $userModel->find($id);
|
||||
return $this->successResponse([
|
||||
'id' => (int) $user->id,
|
||||
'name' => (string) $user->name,
|
||||
'email' => (string) $user->email,
|
||||
], 'User created', null, ResponseInterface::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/users/{id}
|
||||
* Body (baru): name?, email?, password?, roles[] (GURU_MAPEL/WALI_KELAS)
|
||||
* Body (lama, tetap didukung): role_code tunggal.
|
||||
*/
|
||||
public function update(int $id): ResponseInterface
|
||||
{
|
||||
$userModel = new UserModel();
|
||||
$user = $userModel->find($id);
|
||||
if (! $user) {
|
||||
return $this->errorResponse('User not found', null, null, 404);
|
||||
}
|
||||
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$data = [];
|
||||
if (array_key_exists('name', $payload)) {
|
||||
$data['name'] = trim($payload['name']);
|
||||
}
|
||||
if (array_key_exists('email', $payload)) {
|
||||
$data['email'] = trim($payload['email']);
|
||||
}
|
||||
if (isset($payload['password']) && $payload['password'] !== '') {
|
||||
$data['password_hash'] = password_hash($payload['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
if ($data !== []) {
|
||||
$validation = \Config\Services::validation();
|
||||
$rules = [
|
||||
'name' => 'required|max_length[255]',
|
||||
'email' => 'required|valid_email|max_length[255]|is_unique[users.email,id,' . $id . ']',
|
||||
];
|
||||
$toValidate = [
|
||||
'name' => $data['name'] ?? $user->name,
|
||||
'email' => $data['email'] ?? $user->email,
|
||||
];
|
||||
if (! $validation->setRules($rules)->run($toValidate)) {
|
||||
return $this->errorResponse(implode(' ', $validation->getErrors()), $validation->getErrors(), null, 422);
|
||||
}
|
||||
$userModel->skipValidation(true);
|
||||
$userModel->update($id, $data);
|
||||
}
|
||||
|
||||
// Sinkronisasi roles
|
||||
$rolesInput = $payload['roles'] ?? null;
|
||||
$roleCode = $payload['role_code'] ?? null; // fallback lama
|
||||
$allowedCodes = ['GURU_MAPEL', 'WALI_KELAS'];
|
||||
|
||||
$roleCodes = null;
|
||||
if (is_array($rolesInput)) {
|
||||
$roleCodes = [];
|
||||
foreach ($rolesInput as $rc) {
|
||||
$rc = (string) $rc;
|
||||
if (in_array($rc, $allowedCodes, true)) {
|
||||
$roleCodes[] = $rc;
|
||||
}
|
||||
}
|
||||
$roleCodes = array_values(array_unique($roleCodes));
|
||||
} elseif (isset($roleCode) && in_array($roleCode, $allowedCodes, true)) {
|
||||
// kompatibilitas lama: satu role_code
|
||||
$roleCodes = [$roleCode];
|
||||
}
|
||||
|
||||
if (is_array($roleCodes)) {
|
||||
$roleModel = new RoleModel();
|
||||
$userRoleModel = new UserRoleModel();
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Hapus semua role sebelumnya, lalu insert sesuai request
|
||||
$db->table('user_roles')->where('user_id', $id)->delete();
|
||||
|
||||
foreach ($roleCodes as $code) {
|
||||
$role = $roleModel->findByCode($code);
|
||||
if ($role) {
|
||||
$userRoleModel->insert(['user_id' => $id, 'role_id' => $role->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$updated = $userModel->find($id);
|
||||
return $this->successResponse([
|
||||
'id' => (int) $updated->id,
|
||||
'name' => (string) $updated->name,
|
||||
'email' => (string) $updated->email,
|
||||
], 'User updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/users/{id}
|
||||
*/
|
||||
public function delete(int $id): ResponseInterface
|
||||
{
|
||||
$userModel = new UserModel();
|
||||
if (! $userModel->find($id)) {
|
||||
return $this->errorResponse('User not found', null, null, 404);
|
||||
}
|
||||
$userModel->delete($id);
|
||||
return $this->successResponse(null, 'User deleted');
|
||||
}
|
||||
}
|
||||
1
app/Modules/Auth/Entities/.gitkeep
Normal file
1
app/Modules/Auth/Entities/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Auth Module - Entities
|
||||
26
app/Modules/Auth/Entities/Role.php
Normal file
26
app/Modules/Auth/Entities/Role.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Entities;
|
||||
|
||||
use CodeIgniter\Entity\Entity;
|
||||
|
||||
/**
|
||||
* Role Entity
|
||||
*/
|
||||
class Role extends Entity
|
||||
{
|
||||
public const CODE_ADMIN = 'ADMIN';
|
||||
public const CODE_WALI_KELAS = 'WALI_KELAS';
|
||||
public const CODE_GURU_BK = 'GURU_BK';
|
||||
public const CODE_GURU_MAPEL = 'GURU_MAPEL';
|
||||
public const CODE_ORANG_TUA = 'ORANG_TUA';
|
||||
|
||||
protected $allowedFields = [
|
||||
'role_code',
|
||||
'role_name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
];
|
||||
}
|
||||
30
app/Modules/Auth/Entities/User.php
Normal file
30
app/Modules/Auth/Entities/User.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Entities;
|
||||
|
||||
use CodeIgniter\Entity\Entity;
|
||||
|
||||
/**
|
||||
* User Entity
|
||||
*/
|
||||
class User extends Entity
|
||||
{
|
||||
protected $allowedFields = [
|
||||
'name',
|
||||
'email',
|
||||
'password_hash',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return (bool) ($this->attributes['is_active'] ?? true);
|
||||
}
|
||||
}
|
||||
1
app/Modules/Auth/Models/.gitkeep
Normal file
1
app/Modules/Auth/Models/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Auth Module - Models
|
||||
39
app/Modules/Auth/Models/RoleModel.php
Normal file
39
app/Modules/Auth/Models/RoleModel.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Models;
|
||||
|
||||
use App\Modules\Auth\Entities\Role;
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* Role Model
|
||||
*/
|
||||
class RoleModel extends Model
|
||||
{
|
||||
protected $table = 'roles';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = Role::class;
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = [
|
||||
'role_code',
|
||||
'role_name',
|
||||
];
|
||||
|
||||
protected $useTimestamps = false;
|
||||
|
||||
protected $validationRules = [
|
||||
'role_code' => 'required|max_length[50]|is_unique[roles.role_code,id,{id}]',
|
||||
'role_name' => 'required|max_length[100]',
|
||||
];
|
||||
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
|
||||
public function findByCode(string $code): ?Role
|
||||
{
|
||||
return $this->where('role_code', $code)->first();
|
||||
}
|
||||
}
|
||||
46
app/Modules/Auth/Models/UserModel.php
Normal file
46
app/Modules/Auth/Models/UserModel.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Models;
|
||||
|
||||
use App\Modules\Auth\Entities\User;
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* User Model
|
||||
*/
|
||||
class UserModel extends Model
|
||||
{
|
||||
protected $table = 'users';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = User::class;
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = [
|
||||
'name',
|
||||
'email',
|
||||
'password_hash',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
protected $validationRules = [
|
||||
'name' => 'required|max_length[255]',
|
||||
'email' => 'required|valid_email|max_length[255]|is_unique[users.email,id,{id}]',
|
||||
'password_hash' => 'required|max_length[255]',
|
||||
'is_active' => 'permit_empty|in_list[0,1]',
|
||||
];
|
||||
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
|
||||
public function findByEmail(string $email): ?User
|
||||
{
|
||||
return $this->where('email', $email)->first();
|
||||
}
|
||||
}
|
||||
42
app/Modules/Auth/Models/UserRoleModel.php
Normal file
42
app/Modules/Auth/Models/UserRoleModel.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* User Role Model (pivot)
|
||||
*/
|
||||
class UserRoleModel extends Model
|
||||
{
|
||||
protected $table = 'user_roles';
|
||||
// Use a simple primary key to avoid composite PK issues in Model internals.
|
||||
// Database level tetap punya primary key (user_id, role_id) via migration.
|
||||
protected $primaryKey = 'user_id';
|
||||
protected $useAutoIncrement = false;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = [
|
||||
'user_id',
|
||||
'role_id',
|
||||
];
|
||||
|
||||
protected $useTimestamps = false;
|
||||
|
||||
/**
|
||||
* Get role IDs for a user
|
||||
*
|
||||
* @param int $userId
|
||||
* @return array<int>
|
||||
*/
|
||||
public function getRoleIdsForUser(int $userId): array
|
||||
{
|
||||
$rows = $this->where('user_id', $userId)->findAll();
|
||||
$ids = [];
|
||||
foreach ($rows as $row) {
|
||||
$ids[] = (int) $row['role_id'];
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
17
app/Modules/Auth/Routes.php
Normal file
17
app/Modules/Auth/Routes.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Auth Module Routes
|
||||
*
|
||||
* This file is automatically loaded by ModuleLoader.
|
||||
* Define your authentication routes here.
|
||||
*
|
||||
* @var RouteCollection $routes
|
||||
*/
|
||||
|
||||
// Auth routes (session-based)
|
||||
$routes->group('api/auth', ['namespace' => 'App\Modules\Auth\Controllers'], function ($routes) {
|
||||
$routes->post('login', 'AuthController::login');
|
||||
$routes->post('logout', 'AuthController::logout');
|
||||
$routes->get('me', 'AuthController::me');
|
||||
});
|
||||
1
app/Modules/Auth/Services/.gitkeep
Normal file
1
app/Modules/Auth/Services/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Auth Module - Services
|
||||
108
app/Modules/Auth/Services/AuthService.php
Normal file
108
app/Modules/Auth/Services/AuthService.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Auth\Services;
|
||||
|
||||
use App\Modules\Auth\Models\RoleModel;
|
||||
use App\Modules\Auth\Models\UserModel;
|
||||
use App\Modules\Auth\Models\UserRoleModel;
|
||||
|
||||
/**
|
||||
* Auth Service
|
||||
*
|
||||
* Login / logout / currentUser using PHP session.
|
||||
*/
|
||||
class AuthService
|
||||
{
|
||||
public const SESSION_USER_ID = 'auth_user_id';
|
||||
|
||||
protected UserModel $userModel;
|
||||
protected RoleModel $roleModel;
|
||||
protected UserRoleModel $userRoleModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userModel = new UserModel();
|
||||
$this->roleModel = new RoleModel();
|
||||
$this->userRoleModel = new UserRoleModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with email and password.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @return array|null User data + roles, or null on failure
|
||||
*/
|
||||
public function login(string $email, string $password): ?array
|
||||
{
|
||||
$user = $this->userModel->findByEmail($email);
|
||||
if (!$user || !$user->isActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!password_verify($password, $user->password_hash)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session = session();
|
||||
$session->set(self::SESSION_USER_ID, $user->id);
|
||||
|
||||
return $this->userWithRoles($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout (destroy session auth data).
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$session = session();
|
||||
$session->remove(self::SESSION_USER_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current logged-in user with roles, or null.
|
||||
*
|
||||
* @return array|null { id, name, email, roles: [ role_code, role_name ] }
|
||||
*/
|
||||
public function currentUser(): ?array
|
||||
{
|
||||
$session = session();
|
||||
$userId = $session->get(self::SESSION_USER_ID);
|
||||
if (!$userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $this->userModel->find($userId);
|
||||
if (!$user || !$user->isActive()) {
|
||||
$session->remove(self::SESSION_USER_ID);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->userWithRoles($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build user array with roles (no password).
|
||||
*/
|
||||
protected function userWithRoles($user): array
|
||||
{
|
||||
$roleIds = $this->userRoleModel->getRoleIdsForUser($user->id);
|
||||
$roles = [];
|
||||
foreach ($roleIds as $roleId) {
|
||||
$role = $this->roleModel->find($roleId);
|
||||
if ($role) {
|
||||
$roles[] = [
|
||||
'role_code' => $role->role_code,
|
||||
'role_name' => $role->role_name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'roles' => $roles,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user