Skip to content

Commit

Permalink
Feature: Implemented team switching
Browse files Browse the repository at this point in the history
  • Loading branch information
ok200paul committed Aug 16, 2024
1 parent e09b9b2 commit 2ad6d77
Show file tree
Hide file tree
Showing 11 changed files with 906 additions and 47 deletions.
6 changes: 6 additions & 0 deletions app/Enums/PersonalAccessTokenAbility.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ enum PersonalAccessTokenAbility: string
case MY_PROFILE_READ = 'my-profile-read';
case MY_PROFILE_UPDATE = 'my-profile-update';
case MY_PROFILE_DELETE = 'my-profile-delete';

case MY_TEAM_CREATE = 'my-team-create';
case MY_TEAM_READ = 'my-team-read';
case MY_TEAM_UPDATE = 'my-team-update';
case MY_TEAM_DELETE = 'my-team-delete';

case MY_TEAM_VOUCHERS_CREATE = 'my-team-vouchers-create';
case MY_TEAM_VOUCHERS_READ = 'my-team-vouchers-read';
case MY_TEAM_VOUCHERS_UPDATE = 'my-team-vouchers-update';
Expand Down
175 changes: 175 additions & 0 deletions app/Http/Controllers/Api/V1/ApiMyTeamsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

namespace App\Http\Controllers\Api\V1;

use App\Enums\ApiResponse;
use App\Exceptions\DisallowedApiFieldException;
use App\Http\Controllers\Api\HandlesAPIRequests;
use App\Http\Controllers\Controller;
use App\Models\Team;
use App\Models\TeamUser;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Knuckles\Scribe\Attributes\Authenticated;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\QueryParam;
use Knuckles\Scribe\Attributes\Response;

#[Group('/my-teams', 'Teams you are a member of.')]
class ApiMyTeamsController extends Controller
{
use HandlesAPIRequests;

/**
* Set the related data the GET request is allowed to ask for
*/
public array $availableRelations = [

];

public static array $searchableFields = [
'id',
'name'
];

/**
* GET /
*
* @return JsonResponse
* @throws DisallowedApiFieldException
*/
#[Endpoint(
title : 'GET /',
description : 'Retrieve your teams. Automatically filtered to your profile.',
authenticated: true
)]
#[Authenticated]
#[QueryParam(
name : 'cached',
type : 'bool',
description: 'Request the response to be cached. Default: `true`.',
required : false,
example : true
)]
#[QueryParam(
name : 'page',
type : 'int',
description: 'The pagination page number.',
required : false,
example : 1
)]
#[QueryParam(
name : 'limit',
type : 'int',
description: 'The number of entries returned per pagination page.',
required : false,
example : 50
)]
#[QueryParam(
name : 'fields',
type : 'string',
description: 'Comma-separated list of database fields to return within the object.',
required : false,
example : 'id,created_at'
)]
#[QueryParam(
name : 'orderBy',
type : 'comma-separated',
description: 'Order the data by a given field. Comma-separated string.',
required : false,
example : 'orderBy=id,desc'
)]
#[QueryParam(
name : 'orderBy[]',
type : 'comma-separated',
description: 'Compound `orderBy`. Order the data by a given field. Comma-separated string. Can not be used in conjunction as standard `orderBy`.',
required : false,
example : 'orderBy[]=id,desc&orderBy[]=created_at,asc'
)]
#[QueryParam(
name : 'where',
type : 'comma-separated',
description: 'Filter the request on a single field. Key-Value or Key-Operator-Value comma-separated string.',
required : false,
example : 'where=id,like,*550e*'
)]
#[QueryParam(
name : 'where[]',
type : 'comma-separated',
description: 'Compound `where`. Use when you need to filter on multiple `where`\'s. Note only AND is possible; ORWHERE is not available.',
required : false,
example : 'where[]=id,like,*550e*&where[]=created_at,>=,2024-01-01'
)]
#[Response(
content : '{"meta": {"responseCode": 200, "limit": 50, "offset": 0, "message": "", "cached": false, "availableRelations": []}, "data": {"current_page": 1, "data": [{"id": 1, "name": "Team A", "created_at": "2024-08-16T06:54:28.000000Z", "updated_at": "2024-08-16T06:54:28.000000Z", "deleted_at": null}, {"id": 2, "name": "Team B", "created_at": "2024-08-16T06:54:29.000000Z", "updated_at": "2024-08-16T06:54:29.000000Z", "deleted_at": null}], "first_page_url": "https:\/\/vine.test\/api\/v1\/my-teams?page=1", "from": 1, "last_page": 1, "last_page_url": "https:\/\/vine.test\/api\/v1\/my-teams?page=1", "links": [{"url": null, "label": "&laquo; Previous", "active": false}, {"url": "https:\/\/vine.test\/api\/v1\/my-teams?page=1", "label": "1", "active": true}, {"url": null, "label": "Next &raquo;", "active": false}], "next_page_url": null, "path": "https:\/\/vine.test\/api\/v1\/my-teams", "per_page": 50, "prev_page_url": null, "to": 2, "total": 2}}',
status : 200,
description: ''
)]
public function index(): JsonResponse
{
$myTeamIds = TeamUser::where('user_id', Auth::id())->pluck('team_id')->toArray();

$this->query = Team::with($this->associatedData)->whereIn('id', $myTeamIds);
$this->query = $this->updateReadQueryBasedOnUrl();
$this->data = $this->query->paginate($this->limit);

return $this->respond();
}

/**
* POST /
* @hideFromAPIDocumentation
* @return JsonResponse
*/
public function store(): JsonResponse
{
$this->responseCode = 403;
$this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value;

return $this->respond();
}

/**
* GET / {id}
* @hideFromAPIDocumentation
* @param string $id
* @return JsonResponse
*/
public function show(string $id)
{
$this->responseCode = 403;
$this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value;

return $this->respond();
}

/**
* PUT / {id}
* @hideFromAPIDocumentation
* @param string $id
* @return JsonResponse
*/
public function update(string $id)
{
$this->responseCode = 403;
$this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value;

return $this->respond();
}

/**
* DELETE / {id}
* @hideFromAPIDocumentation
* @param string $id
*
* @return JsonResponse
*/
public function destroy(string $id)
{
$this->responseCode = 403;
$this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value;

return $this->respond();
}
}
6 changes: 4 additions & 2 deletions app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Http\Middleware;

use App\Enums\PersonalAccessTokenAbility;
use App\Models\Team;
use Illuminate\Http\Request;
use Inertia\Middleware;

Expand Down Expand Up @@ -36,8 +37,9 @@ public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => [
'user' => $request->user(),
'auth' => [
'user' => $request->user(),
'currentTeam' => Team::find($request->user()->current_team_id),
],
'personalAccessTokenAbilities' => PersonalAccessTokenAbility::cases(),
];
Expand Down
13 changes: 7 additions & 6 deletions database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function run(): void

$usersAndTeams = [
[
'team' => [
'team' => [
'name' => 'OK200 Team',
],
'users' => [
Expand All @@ -42,7 +42,7 @@ public function run(): void
],
],
[
'team' => [
'team' => [
'name' => 'Open Food Network',
],
'users' => [
Expand All @@ -66,10 +66,11 @@ public function run(): void
foreach ($userAndTeam['users'] as $user) {
$user = User::factory()->create(
[
'name' => $user['name'],
'email' => $user['email'],
'password' => $user['email'],
'is_admin' => 1,
'name' => $user['name'],
'email' => $user['email'],
'password' => $user['email'],
'is_admin' => 1,
'current_team_id' => $team->id,
]
);

Expand Down
14 changes: 13 additions & 1 deletion resources/js/Layouts/AuthenticatedLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const showingNavigationDropdown = ref(false);
<NavLink :href="route('dashboard')" :active="route().current('dashboard')">
Dashboard
</NavLink>
<a href="/api-documentation"
target="_blank"
class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-light leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">
Api Docs
</a>
</div>
</div>

Expand Down Expand Up @@ -71,6 +76,9 @@ const showingNavigationDropdown = ref(false);
Admin Section
</DropdownLink>
<DropdownLink :href="route('profile.edit')"> Profile </DropdownLink>
<DropdownLink :href="route('my-team')">
My Team
</DropdownLink>
<DropdownLink :href="route('logout')" method="post" as="button">
Log Out
</DropdownLink>
Expand Down Expand Up @@ -144,8 +152,12 @@ const showingNavigationDropdown = ref(false);

<!-- Page Heading -->
<header class="bg-white shadow" v-if="$slots.header">
<div class="container mx-auto py-6 px-4 sm:px-6 lg:px-8">
<div class="container mx-auto py-6 px-4 sm:px-6 lg:px-8 flex justify-between items-center">
<slot name="header" />

<div class="opacity-50">
Logged into: {{ $page.props.auth.currentTeam.name}}
</div>
</div>
</header>

Expand Down
49 changes: 46 additions & 3 deletions resources/js/Pages/App/MyTeam.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {Head} from '@inertiajs/vue3';
import {onMounted, ref} from "vue";
const myTeam = ref({})
const myTeams = ref({})
onMounted(() => {
getMyTeam()
getMyTeam();
getMyTeams();
})
function getMyTeam() {
Expand All @@ -17,6 +19,16 @@ function getMyTeam() {
})
}
function getMyTeams() {
axios.get('/my-teams?cached=false&orderBy=name,asc').then(response => {
myTeams.value = response.data.data
}).catch(error => {
console.log(error)
})
}
</script>

<template>
Expand All @@ -29,13 +41,14 @@ function getMyTeam() {

<div class="card">
<div class="flex items-start font-bold">
<div>#{{ myTeam.id }}</div>
<div class="pl-2 text-2xl">{{ myTeam.name }}</div>
</div>
</div>

<div class="card">
<div class="text-sm pb-2 text-gray-500">Team members</div>
<div class="card-header">
Team members
</div>

<div v-if="myTeam.team_users && myTeam.team_users.length > 0">
<div v-for="teamUser in myTeam.team_users" class="">
Expand All @@ -47,5 +60,35 @@ function getMyTeam() {
</div>
</div>
</div>

<div class="card">
<div class="card-header">
Teams You Belong To
</div>


<div v-for="team in myTeams.data" class="">
<div class="border-b py-2 flex justify-between">

<div>
{{team.name}}
</div>
<div>
<div v-if="team.id === $page.props.auth.user.current_team_id">
Current
</div>
<div v-else>
<a :href="'/switch-team/' + team.id" class="text-red-500">Switch to this team</a>
</div>
</div>
</div>
</div>

</div>

<div class="pb-32">

</div>

</AuthenticatedLayout>
</template>
14 changes: 6 additions & 8 deletions resources/js/Pages/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head } from '@inertiajs/vue3';
import {Head} from '@inertiajs/vue3';
</script>

<template>
<Head title="Dashboard" />
<Head title="Dashboard"/>

<AuthenticatedLayout>
<template #header>
<h2 class="font-normal text-xl text-gray-800 leading-tight">Dashboard</h2>
</template>

<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 ">You're logged in!</div>
</div>
</div>
<div class="card">

You're logged in!

</div>
</AuthenticatedLayout>
</template>
Loading

0 comments on commit 2ad6d77

Please sign in to comment.