Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 97 additions & 2 deletions app/Http/Controllers/API/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

namespace App\Http\Controllers\API;

use Illuminate\Http\JsonResponse;
use App\Helpers\Fixometer;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserAdmin;
use App\Role;
use App\User;
use Auth;
use Illuminate\Http\Request;
use Cache;
use DB;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
Expand Down Expand Up @@ -132,4 +136,95 @@ public function notifications(Request $request, int $id): JsonResponse
'discourse' => $discourseNotifications
], 200);
}

/**
* @OA\Get(
* path="/api/v2/users",
* operationId="listUsersv2",
* tags={"Users"},
* summary="List users with optional filtering and sorting",
* description="Administrator only. Paginated.",
* security={{"apiToken":{}}},
* @OA\Parameter(name="name", in="query", required=false, @OA\Schema(type="string")),
* @OA\Parameter(name="email", in="query", required=false, @OA\Schema(type="string")),
* @OA\Parameter(name="location", in="query", required=false, @OA\Schema(type="string")),
* @OA\Parameter(name="country", in="query", required=false, @OA\Schema(type="string")),
* @OA\Parameter(name="role", in="query", required=false, @OA\Schema(type="integer")),
* @OA\Parameter(name="sort", in="query", required=false, @OA\Schema(type="string", enum={"name","email","role","location","country","created_at","updated_at"})),
* @OA\Parameter(name="sortdir", in="query", required=false, @OA\Schema(type="string", enum={"asc","desc"})),
* @OA\Parameter(name="page", in="query", required=false, @OA\Schema(type="integer")),
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/UserAdmin")),
* @OA\Property(property="meta", type="object")
* )
* ),
* @OA\Response(response=401, description="Unauthenticated"),
* @OA\Response(response=403, description="Forbidden")
* )
*/
public function listUsersv2(Request $request): JsonResponse
{
if (!Fixometer::hasRole(Auth::user(), 'Administrator')) {
return response()->json(['message' => 'Forbidden'], 403);
}

$query = User::query()
->leftJoin('roles', 'roles.idroles', '=', 'users.role')
->select('users.*', 'roles.role as role_name')
->withCount('groups');

if ($name = $request->input('name')) {
$query->where('users.name', 'like', '%' . $name . '%');
}
if ($email = $request->input('email')) {
$query->where('users.email', 'like', '%' . $email . '%');
}
if ($location = $request->input('location')) {
$query->where('users.location', 'like', '%' . $location . '%');
}
if ($country = $request->input('country')) {
$query->where('users.country_code', '=', $country);
}
if (($role = $request->input('role')) !== null && $role !== '') {
$query->where('users.role', '=', (int) $role);
}

$sortMap = [
'name' => 'users.name',
'email' => 'users.email',
'role' => 'users.role',
'location' => 'users.location',
'country' => 'users.country_code',
'created_at' => 'users.created_at',
'updated_at' => 'users.updated_at',
];
$sort = $request->input('sort');
if ($sort && isset($sortMap[$sort])) {
$dir = strtolower($request->input('sortdir', 'asc'));
if (!in_array($dir, ['asc', 'desc'], true)) {
$dir = 'asc';
}
$query->orderBy($sortMap[$sort], $dir);
} else {
$query->orderBy('users.id', 'asc');
}

$perPage = (int) (env('PAGINATE') ?: 30);
$paginator = $query->paginate($perPage);

return response()->json([
'data' => UserAdmin::collection($paginator->getCollection())->toArray($request),
'meta' => [
'current_page' => $paginator->currentPage(),
'last_page' => $paginator->lastPage(),
'per_page' => $paginator->perPage(),
'total' => $paginator->total(),
'from' => $paginator->firstItem(),
'to' => $paginator->lastItem(),
],
]);
}
}
60 changes: 60 additions & 0 deletions app/Http/Resources/UserAdmin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace App\Http\Resources;

use App\Helpers\Fixometer;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

/**
* @OA\Schema(
* title="UserAdmin",
* schema="UserAdmin",
* description="A user row for the admin user list",
* @OA\Property(property="id", type="integer", format="int64", example=42),
* @OA\Property(property="name", type="string", example="Jane Doe"),
* @OA\Property(property="email", type="string", example="jane@example.com"),
* @OA\Property(property="role", type="integer", example=3),
* @OA\Property(property="role_name", type="string", example="Host"),
* @OA\Property(property="location", type="string", nullable=true, example="London"),
* @OA\Property(property="country", type="string", nullable=true, example="GB"),
* @OA\Property(property="country_name", type="string", nullable=true, example="United Kingdom"),
* @OA\Property(property="groups_count", type="integer", example=2),
* @OA\Property(property="created_at", type="string", format="date-time", nullable=true),
* @OA\Property(property="last_login_at", type="string", format="date-time", nullable=true)
* )
*/
class UserAdmin extends JsonResource
{
public function toArray(Request $request): array
{
$lastLogin = method_exists($this->resource, 'lastLogin') ? $this->resource->lastLogin() : null;

return [
'id' => (int) $this->id,
'name' => $this->name,
'email' => $this->email,
'role' => (int) $this->role,
'role_name' => $this->role_name ?? (string) $this->getRoleName(),
'location' => $this->location,
'country' => $this->country_code,
'country_name' => $this->country_code ? Fixometer::getCountryFromCountryCode($this->country_code) : null,
'groups_count' => (int) ($this->groups_count ?? $this->groups()->count()),
'created_at' => optional($this->created_at)?->toIso8601String(),
'last_login_at' => optional($lastLogin)?->toIso8601String(),
];
}

private function getRoleName(): string
{
$roleNames = [
\App\Role::ROOT => 'Root',
\App\Role::ADMINISTRATOR => 'Administrator',
\App\Role::NETWORK_COORDINATOR => 'NetworkCoordinator',
\App\Role::HOST => 'Host',
\App\Role::RESTARTER => 'Restarter',
];

return $roleNames[$this->role] ?? '';
}
}
23 changes: 23 additions & 0 deletions lang/en/users.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

return [
'title' => 'Users',
'reveal_filters' => 'Reveal filters',
'create_new' => 'Create new user',
'name' => 'Name',
'email' => 'Email address',
'role' => 'Role',
'role_any' => 'Any role',
'location' => 'Location',
'country' => 'Country',
'groups' => 'Groups',
'joined' => 'Joined',
'last_login' => 'Last login',
'placeholder_name' => 'Search by name',
'placeholder_email' => 'Search by email address',
'placeholder_location' => 'E.g. Paris, London, Brussels',
'search' => 'Search all users',
'empty' => 'No users match the current filters.',
'never' => 'Never',
'showing' => 'Showing :from to :to of :total results',
];
23 changes: 23 additions & 0 deletions lang/fr-BE/users.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

return [
'title' => 'Utilisateurs',
'reveal_filters' => 'Afficher les filtres',
'create_new' => 'Créer un nouvel utilisateur',
'name' => 'Nom',
'email' => 'Adresse e-mail',
'role' => 'Rôle',
'role_any' => 'Tous les rôles',
'location' => 'Ville',
'country' => 'Pays',
'groups' => 'Groupes',
'joined' => 'Inscrit',
'last_login' => 'Dernière connexion',
'placeholder_name' => 'Rechercher par nom',
'placeholder_email' => 'Rechercher par adresse e-mail',
'placeholder_location' => 'Par ex. Paris, Londres, Bruxelles',
'search' => 'Rechercher tous les utilisateurs',
'empty' => 'Aucun utilisateur ne correspond aux filtres actuels.',
'never' => 'Jamais',
'showing' => 'Affichage de :from à :to sur :total résultats',
];
23 changes: 23 additions & 0 deletions lang/fr/users.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

return [
'title' => 'Utilisateurs',
'reveal_filters' => 'Afficher les filtres',
'create_new' => 'Créer un nouvel utilisateur',
'name' => 'Nom',
'email' => 'Adresse e-mail',
'role' => 'Rôle',
'role_any' => 'Tous les rôles',
'location' => 'Ville',
'country' => 'Pays',
'groups' => 'Groupes',
'joined' => 'Inscrit',
'last_login' => 'Dernière connexion',
'placeholder_name' => 'Rechercher par nom',
'placeholder_email' => 'Rechercher par adresse e-mail',
'placeholder_location' => 'Par ex. Paris, Londres, Bruxelles',
'search' => 'Rechercher tous les utilisateurs',
'empty' => 'Aucun utilisateur ne correspond aux filtres actuels.',
'never' => 'Jamais',
'showing' => 'Affichage de :from à :to sur :total résultats',
];
2 changes: 2 additions & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import EventsRequiringModeration from './components/EventsRequiringModeration.vu
import EventPage from './components/EventPage.vue'
import FixometerPage from './components/FixometerPage.vue'
import GroupsPage from './components/GroupsPage.vue'
import UsersPage from './components/UsersPage.vue'
import GroupPage from './components/GroupPage.vue'
import GroupAddEditPage from './components/GroupAddEditPage.vue'
import GroupEventsPage from './components/GroupEventsPage.vue'
Expand Down Expand Up @@ -402,6 +403,7 @@ function initializeJQuery() {
'eventpage': EventPage,
'fixometerpage': FixometerPage,
'groupspage': GroupsPage,
'userspage': UsersPage,
'grouppage': GroupPage,
'groupaddeditpage': GroupAddEditPage,
'groupeventspage': GroupEventsPage,
Expand Down
Loading