Giter Site home page Giter Site logo

teamwork's Introduction

Teamwork (Laravel Package)

Latest Version Software License Build Status codecov.io SensioLabsInsight Scrutinizer Code Quality

Teamwork is the fastest and easiest method to add a User / Team association with Invites to your Laravel project.

Contents

For Laravel ^8.0|^9.0

"oliuz/teamwork": "^9.0"

Add the version you need to your composer.json. Then run composer install or composer update.

(or run composer require oliuz/teamwork if you prefere that)

The Teamwork Facade will be installed automatically within the Service Provider.

To publish Teamwork's configuration and migration files, run the vendor:publish command.

php artisan vendor:publish --provider="Mpociot\Teamwork\TeamworkServiceProvider"

This will create a teamwork.php in your config directory. The default configuration should work just fine for you, but you can take a look at it, if you want to customize the table / model names Teamwork will use.

Run the migration command, to generate all tables needed for Teamwork. If your users are stored in a different table other than users be sure to modify the published migration.

php artisan migrate

After the migration, 3 new tables will be created:

  • teams — stores team records
  • team_user — stores many-to-many relations between users and teams
  • team_invites — stores pending invites for email addresses to teams

You will also notice that a new column current_team_id has been added to your users table. This column will define the Team, the user is currently assigned to.

Add the UserHasTeams trait to your existing User model:

<?php namespace App;

use Mpociot\Teamwork\Traits\UserHasTeams;

class User extends Model {

	use UserHasTeams; // Add this trait to your model
}

This will enable the relation with Team and add the following methods teams(), ownedTeams() currentTeam(), invites(), isTeamOwner(), isOwnerOfTeam($team), attachTeam($team, $pivotData = []), detachTeam($team), attachTeams($teams), detachTeams($teams), switchTeam($team), isOwnerAuth(), isOwnerAuthCheck() within your User model.

Don't forget to dump composer autoload

composer dump-autoload

If you would like to use the middleware to protect to current team owner then just add the middleware provider to your app\Http\Kernel.php file.

    protected $routeMiddleware = [
        ...
        'teamowner' => \Mpociot\Teamwork\Middleware\TeamOwner::class,
        ...
    ];

Afterwards you can use the teamowner middleware in your routes file like so.

Route::get('/owner', function(){
    return "Owner of current team.";
})->middleware('auth', 'teamowner');

Now only if the authenticated user is the owner of the current team can access that route.

This middleware is aimed to protect routes where only the owner of the team can edit/create/delete that model

And you are ready to go.

The easiest way to give your new Laravel project Team abilities is by using the make:teamwork command.

php artisan make:teamwork

This command will create all views, routes, model and controllers to make your new project team-ready.

Out of the box, the following parts will be created for you:

  • Team listing
  • Team creation / editing / deletion
  • Invite new members to teams
  • Team model

Imagine it as a the make:auth command for Teamwork.

To get started, take a look at the new installed /teams route in your project.

Let's start by creating two different Teams.

$team	= new Team();
$team->owner_id = User::where('username', '=', 'sebastian')->first()->getKey();
$team->name = 'My awesome team';
$team->save();

$myOtherCompany = new Team();
$myOtherCompany->owner_id = User::where('username', '=', 'marcel')->first()->getKey();
$myOtherCompany->name = 'My other awesome team';
$myOtherCompany->save();

Now thanks to the UserHasTeams trait, assigning the Teams to the user is uber easy:

$user = User::where('username', '=', 'sebastian')->first();

// team attach alias
$user->attachTeam($team, $pivotData); // First parameter can be a Team object, array, or id

// or eloquent's original technique
$user->teams()->attach($team->id); // id only

By using the attachTeam method, if the User has no Teams assigned, the current_team_id column will automatically be set.

The currently assigned Team of a user can be accessed through the currentTeam relation like this:

echo "I'm currently in team: " . Auth::user()->currentTeam->name;
echo "The team owner is: " . Auth::user()->currentTeam->owner->username;

echo "I also have these teams: ";
print_r( Auth::user()->teams );

echo "I am the owner of these teams: ";
print_r( Auth::user()->ownedTeams );

echo "My team has " . Auth::user()->currentTeam->users->count() . " users.";

The Team model has access to these methods:

  • invites() — Returns a many-to-many relation to associated invitations.
  • users() — Returns a many-to-many relation with all users associated to this team.
  • owner() — Returns a one-to-one relation with the User model that owns this team.
  • hasUser(User $user) — Helper function to determine if a user is a teammember

If you need to check if the User is a team owner (regardless of the current team) use the isTeamOwner() method on the User model.

if( Auth::user()->isTeamOwner() )
{
	echo "I'm a team owner. Please let me pay more.";
}

Additionally if you need to check if the user is the owner of a specific team, use:

$team = Auth::user()->currentTeam;
if( Auth::user()->isOwnerOfTeam( $team ) )
{
	echo "I'm a specific team owner. Please let me pay even more.";
}

The isOwnerOfTeam method also allows an array or id as team parameter.

If your Users are members of multiple teams you might want to give them access to a switch team mechanic in some way.

This means that the user has one "active" team, that is currently assigned to the user. All other teams still remain attached to the relation!

Glad we have the UserHasTeams trait.

try {
	Auth::user()->switchTeam( $team_id );
	// Or remove a team association at all
	Auth::user()->switchTeam( null );
} catch( UserNotInTeamException $e )
{
	// Given team is not allowed for the user
}

Just like the isOwnerOfTeam method, switchTeam accepts a Team object, array, id or null as a parameter.

The best team is of no avail if you're the only team member.

To invite other users to your teams, use the Teamwork facade.

Teamwork::inviteToTeam( $email, $team, function( $invite )
{
	// Send email to user / let them know that they got invited
});

You can also send invites by providing an object with an email property like:

$user = Auth::user();

Teamwork::inviteToTeam( $user , $team, function( $invite )
{
	// Send email to user / let them know that they got invited
});

This method will create a TeamInvite model and return it in the callable third parameter.

This model has these attributes:

  • email — The email that was invited.
  • accept_token — Unique token used to accept the invite.
  • deny_token — Unique token used to deny the invite.

In addition to these attributes, the model has these relations:

  • user() — one-to-one relation using the email as a unique identifier on the User model.
  • team() — one-to-one relation return the Team, that invite was aiming for.
  • inviter() — one-to-one relation return the User, that created the invite.

Note: The inviteToTeam method will not check if the given email already has a pending invite. To check for pending invites use the hasPendingInvite method on the Teamwork facade.

Example usage:

if( !Teamwork::hasPendingInvite( $request->email, $request->team) )
{
	Teamwork::inviteToTeam( $request->email, $request->team, function( $invite )
	{
                // Send email to user
	});
} else {
	// Return error - user already invited
}

Once you invited other users to join your team, in order to accept the invitation use the Teamwork facade once again.

$invite = Teamwork::getInviteFromAcceptToken( $request->token ); // Returns a TeamworkInvite model or null

if( $invite ) // valid token found
{
	Teamwork::acceptInvite( $invite );
}

The acceptInvite method does two thing:

  • Call attachTeam with the invite-team on the currently authenticated user.
  • Delete the invitation afterwards.

Just like accepting invites:

$invite = Teamwork::getInviteFromDenyToken( $request->token ); // Returns a TeamworkInvite model or null

if( $invite ) // valid token found
{
	Teamwork::denyInvite( $invite );
}

The denyInvite method is only responsible for deleting the invitation from the database.

If you need to run additional processes after attaching/detaching a team from a user or inviting a user, you can Listen for these events:

\Mpociot\Teamwork\Events\UserJoinedTeam

\Mpociot\Teamwork\Events\UserLeftTeam

\Mpociot\Teamwork\Events\UserInvitedToTeam

In your EventServiceProvider add your listener(s):

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    ...
    \Mpociot\Teamwork\Events\UserJoinedTeam::class => [
        App\Listeners\YourJoinedTeamListener::class,
    ],
    \Mpociot\Teamwork\Events\UserLeftTeam::class => [
        App\Listeners\YourLeftTeamListener::class,
    ],
    \Mpociot\Teamwork\Events\UserInvitedToTeam::class => [
        App\Listeners\YourUserInvitedToTeamListener::class,
    ],
];

The UserJoinedTeam and UserLeftTeam event exposes the User and Team's ID. In your listener, you can access them like so:

<?php

namespace App\Listeners;

use Mpociot\Teamwork\Events\UserJoinedTeam;

class YourJoinedTeamListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  UserJoinedTeam  $event
     * @return void
     */
    public function handle(UserJoinedTeam $event)
    {
        // $user = $event->getUser();
        // $teamId = $event->getTeamId();
        
        // Do something with the user and team ID.
    }
}

The UserInvitedToTeam event contains an invite object which could be accessed like this:

<?php

namespace App\Listeners;

use Mpociot\Teamwork\Events\UserInvitedToTeam;

class YourUserInvitedToTeamListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  UserInvitedToTeam  $event
     * @return void
     */
    public function handle(UserInvitedToTeam $event)
    {
        // $user = $event->getInvite()->user;
        // $teamId = $event->getTeamId();
        
        // Do something with the user and team ID.
    }
}

If your models are somehow limited to the current team you will find yourself writing this query over and over again: Model::where('team_id', auth()->user()->currentTeam->id)->get();.

To automate this process, you can let your models use the UsedByTeams trait. This trait will automatically append the current team id of the authenticated user to all queries and will also add it to a field called team_id when saving the models.

Note:

This assumes that the model has a field called team_id

Usage

use Mpociot\Teamwork\Traits\UsedByTeams;

class Task extends Model
{
    use UsedByTeams;
}

When using this trait, all queries will append WHERE team_id=CURRENT_TEAM_ID. If theres a place in your app, where you really want to retrieve all models, no matter what team they belong to, you can use the allTeams scope.

Example:

// gets all tasks for the currently active team of the authenticated user
Task::all();

// gets all tasks from all teams globally
Task::allTeams()->get();

Teamwork is free software distributed under the terms of the MIT license.

'Marvel Avengers' image licensed under Creative Commons 2.0 - Photo from W_Minshull

teamwork's People

Contributors

carlosmls1 avatar dependabot[bot] avatar jadamec avatar oliuz avatar siarheipashkevich avatar tanthammar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

teamwork's Issues

Make uuid and slug column as optional

Could you please make the uuid and slug columns as optional. Because your logic in the boot method from TemworkTeam model break my code. I don't need these fields uuid and slug

Bug in namespacing ?

Mpociot\Teamwork\Teamwork::acceptInvite(): Argument #1 ($invite) must be of type Mpociot\Teamwork\TeamInvite,
Mpociot\Teamwork\Models\TeamInvite given, called in 
/var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php on line 261

This comes straight from your published assets.

public function __invoke($token)
    {
        $invite = Teamwork::getInviteFromAcceptToken($token);
        if (!$invite) {
            abort(404);
        }

        if (auth()->check()) {
            Teamwork::acceptInvite($invite);
            return redirect()->route('teams.index');
        } else {
            session(['invite_token' => $token]);
            return redirect()->to('login');
        }
    }

From your source, the type hint has no namespace or use statement so PHP assumes the same namespace as the file:

/**
     * @param TeamInvite $invite
     */
    public function acceptInvite(TeamInvite $invite)
    {
        $this->user()->attachTeam($invite->team);
        $invite->delete();
    }

Make owner_id field optional when creating a team.

In TeamworkTeam.php the boot() needs to have

static::creating(function ($model) {
            $model->uuid = (string)Uuid::uuid1();
            $model->slug = Str::slug($model->name);
            $model->owner_id = auth()->user()->getKey(); // we don't have a auth() user yet.. here it breaks
        });

When I'm creating a team on Registration, obviously it is not possible anymore since the user is not yet authenticated.
On the previous version, I did manually assign the owner id when creating the team


 $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
)]

$team = $teamModel::create([
            'name' => $user->email,
            'owner_id' => $user->id // notice that we don't need an autheticated user here
        ]);

Don't update uuid column after each updating of the team

Hi @oliuz

I think that the logic of updating uuid column during updating team model incorrect. There is maybe case that we can lose relation to the team after updating team.

Simple example: we create team record and user which will be related to this team

creating process:
team table:
uuid: 'uuid1'

user table:
id: 1
team_uuid: 'uuid1' - relation to team table

but after updating the team model the uuid column will be updated and the user will lose relation with the company by uuid

What do you think about this?

laravel 8 update

Do you think you will update this package to support laravel 8? Thanks

PHP 8

Would you be so kind to add a PHP 8 support, please?

oliuz/teamwork[8.0, ..., 8.0.3] require php ^7.2 -> your php version (8.0.3) does not satisfy that requirement.
- Root composer.json requires oliuz/teamwork ^8.0 -> satisfiable by oliuz/teamwork[8.0, 8.0.1, 8.0.2, 8.0.3].

Laravel 9 compatibility

With the release of Laravel 9, we need to be able to update.

I believe that adding the compatibility of Laravel Framework 9 should be enough to allow us to update, and not cause issues:
"laravel/framework": "^6.0|^7.0|^8.0|^9.0"

How to avoid updating uuid?

Hi @oliuz could you please assist me how I can avoid issue which I described above. In the latest version I can see that you moved this logic to boot method but it's not resolve my problem.

PS: I don't need uuid column in my teams table

I see 2 possible solutions:

  1. don't extend TeamworkTeam model and just use TeamworkTeamTrait directly in my Team model.
  2. try to suppress your attached listeners in the boot method

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.