Skip to content

Commit

Permalink
Add ability to join team
Browse files Browse the repository at this point in the history
  • Loading branch information
nanaya committed Jan 22, 2025
1 parent 515ffbd commit a193647
Show file tree
Hide file tree
Showing 19 changed files with 627 additions and 0 deletions.
74 changes: 74 additions & 0 deletions app/Http/Controllers/Teams/ApplicationsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

declare(strict_types=1);

namespace App\Http\Controllers\Teams;

use App\Http\Controllers\Controller;
use App\Jobs\Notifications\TeamApplicationAccept;
use App\Jobs\Notifications\TeamApplicationReject;
use App\Models\Team;
use App\Models\TeamApplication;
use Symfony\Component\HttpFoundation\Response;

class ApplicationsController extends Controller
{
public function __construct()
{
parent::__construct();

$this->middleware('auth');
}

public function accept(string $teamId, string $id): Response
{
$member = \DB::transaction(function () use ($id, $teamId) {
$team = Team::findOrFail($teamId);
$application = $team->applications()->findOrFail($id);

priv_check('TeamApplicationAccept', $application)->ensureCan();

$application->delete();

return $team->members()->create(['user_id' => $application->user_id]);
});
(new TeamApplicationAccept($member, \Auth::user()))->dispatch();

\Session::flash('popup', osu_trans('teams.applications.accept.ok'));

return response(null, 204);
}

public function destroy(string $teamId, string $id): Response
{
$currentUser = \Auth::user();
TeamApplication::where('team_id', $teamId)->findOrFail($currentUser->getKey())->delete();
\Session::flash('popup', osu_trans('teams.applications.destroy.ok'));

return response(null, 204);
}

public function reject(string $teamId, string $id): Response
{
$application = TeamApplication::where('team_id', $teamId)->findOrFail($id);
$application->delete();
\Session::flash('popup', osu_trans('teams.applications.reject.ok'));
(new TeamApplicationReject($application, \Auth::user()))->dispatch();

return response(null, 204);
}

public function store(string $teamId): Response
{
$team = Team::findOrFail($teamId);
priv_check('TeamApplicationStore', $team)->ensureCan();

$team->applications()->createOrFirst(['user_id' => \Auth::id()]);
\Session::flash('popup', osu_trans('teams.applications.store.ok'));

return response(null, 204);
}
}
51 changes: 51 additions & 0 deletions app/Jobs/Notifications/TeamApplicationAccept.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

namespace App\Jobs\Notifications;

use App\Models\Notification;
use App\Models\Team;
use App\Models\TeamMember;
use App\Models\User;

class TeamApplicationAccept extends BroadcastNotificationBase
{
const DELIVERY_MODE_DEFAULTS = ['mail' => true, 'push' => true];

private Team $team;
private int $userId;

public static function getMailLink(Notification $notification): string
{
return route('teams.show', ['team' => $notification->notifiable_id]);
}

public function __construct(TeamMember $member, User $source)
{
$this->team = $member->team;
$this->userId = $member->user_id;

parent::__construct($source);
}

public function getDetails(): array
{
return [
'cover_url' => $this->team->logo()->url(),
'team_id' => $this->team->getKey(),
'title' => $this->team->name,
];
}

public function getListeningUserIds(): array
{
return [$this->userId];
}

public function getNotifiable()
{
return $this->team;
}
}
51 changes: 51 additions & 0 deletions app/Jobs/Notifications/TeamApplicationReject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

namespace App\Jobs\Notifications;

use App\Models\Notification;
use App\Models\Team;
use App\Models\TeamApplication;
use App\Models\User;

class TeamApplicationReject extends BroadcastNotificationBase
{
const DELIVERY_MODE_DEFAULTS = ['mail' => true, 'push' => true];

private Team $team;
private int $userId;

public static function getMailLink(Notification $notification): string
{
return route('teams.show', ['team' => $notification->notifiable_id]);
}

public function __construct(TeamApplication $application, User $source)
{
$this->team = $application->team;
$this->userId = $application->user_id;

parent::__construct($source);
}

public function getDetails(): array
{
return [
'cover_url' => $this->team->logo()->url(),
'team_id' => $this->team->getKey(),
'title' => $this->team->name,
];
}

public function getListeningUserIds(): array
{
return [$this->userId];
}

public function getNotifiable()
{
return $this->team;
}
}
2 changes: 2 additions & 0 deletions app/Libraries/MorphMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use App\Models\NewsPost;
use App\Models\Score;
use App\Models\Solo;
use App\Models\Team;
use App\Models\User;

class MorphMap
Expand All @@ -44,6 +45,7 @@ class MorphMap
Score\Osu::class => 'score_osu',
Score\Taiko::class => 'score_taiko',
Solo\Score::class => 'solo_score',
Team::class => 'team',
User::class => 'user',
];

Expand Down
5 changes: 5 additions & 0 deletions app/Models/Notification.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ class Notification extends Model
const CHANNEL_MESSAGE = 'channel_message';
const COMMENT_NEW = 'comment_new';
const FORUM_TOPIC_REPLY = 'forum_topic_reply';
const TEAM_APPLICATION_ACCEPT = 'team_application_accept';
const TEAM_APPLICATION_REJECT = 'team_application_reject';
const USER_ACHIEVEMENT_UNLOCK = 'user_achievement_unlock';
const USER_BEATMAPSET_NEW = 'user_beatmapset_new';
const USER_BEATMAPSET_REVIVE = 'user_beatmapset_revive';

// sync with resources/js/notification-maps/category.ts
const NAME_TO_CATEGORY = [
self::BEATMAP_OWNER_CHANGE => 'beatmap_owner_change',
self::BEATMAPSET_DISCUSSION_LOCK => 'beatmapset_discussion',
Expand All @@ -65,6 +68,8 @@ class Notification extends Model
self::CHANNEL_MESSAGE => 'channel',
self::COMMENT_NEW => 'comment',
self::FORUM_TOPIC_REPLY => 'forum_topic_reply',
self::TEAM_APPLICATION_ACCEPT => 'team_application',
self::TEAM_APPLICATION_REJECT => 'team_application',
self::USER_ACHIEVEMENT_UNLOCK => 'user_achievement_unlock',
self::USER_BEATMAPSET_NEW => 'user_beatmapset_new',
self::USER_BEATMAPSET_REVIVE => 'user_beatmapset_new',
Expand Down
15 changes: 15 additions & 0 deletions app/Models/Team.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ public function delete()
});
}

public function emptySlots(): int
{
$max = $this->maxMembers();
$current = $this->members->count();

return max(0, $max - $current);
}

public function header(): Uploader
{
return $this->header ??= new Uploader(
Expand Down Expand Up @@ -131,4 +139,11 @@ public function logo(): Uploader
['image' => ['maxDimensions' => [512, 256]]],
);
}

public function maxMembers(): int
{
$this->loadMissing('members.user');

return 8 + (4 * $this->members->filter(fn ($member) => $member->user?->osu_subscriber ?? false)->count());
}
}
27 changes: 27 additions & 0 deletions app/Models/TeamApplication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TeamApplication extends Model
{
public $incrementing = false;

protected $primaryKey = 'user_id';

public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
6 changes: 6 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ public function team(): HasOneThrough
);
}

public function teamApplication(): HasOne
{
return $this->hasOne(TeamApplication::class);
}

public function getAuthPassword()
{
return $this->user_password;
Expand Down Expand Up @@ -967,6 +972,7 @@ public function getAttribute($key)
'supporterTagPurchases',
'supporterTags',
'team',
'teamApplication',
'tokens',
'topicWatches',
'userAchievements',
Expand Down
41 changes: 41 additions & 0 deletions app/Singletons/OsuAuthorize.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use App\Models\Score\Best\Model as ScoreBest;
use App\Models\Solo;
use App\Models\Team;
use App\Models\TeamApplication;
use App\Models\Traits\ReportableInterface;
use App\Models\User;
use App\Models\UserContestEntry;
Expand Down Expand Up @@ -1905,6 +1906,46 @@ public function checkScorePin(?User $user, ScoreBest|Solo\Score $score): string
return 'ok';
}

public function checkTeamApplicationAccept(?User $user, TeamApplication $application): ?string
{
$this->ensureLoggedIn($user);

$team = $application->team;

if ($team->leader_id !== $user->getKey()) {
return null;
}
if ($team->emptySlots() < 1) {
return 'team.member.store.full';
}

return 'ok';
}

public function checkTeamApplicationStore(?User $user, Team $team): ?string
{
$prefix = 'team.application.store.';

$this->ensureLoggedIn($user);

if ($user->team !== null) {
return $user->team->getKey() === $team->getKey()
? $prefix.'already_member'
: $prefix.'already_other_member';
}
if ($user->teamApplication()->exists()) {
return $prefix.'currently_applying';
}
if (!$team->is_open) {
return $prefix.'team_closed';
}
if ($team->emptySlots() < 1) {
return $prefix.'team_full';
}

return 'ok';
}

public function checkTeamPart(?User $user, Team $team): ?string
{
$this->ensureLoggedIn($user);
Expand Down
25 changes: 25 additions & 0 deletions database/factories/TeamMemberFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

declare(strict_types=1);

namespace Database\Factories;

use App\Models\Team;
use App\Models\TeamMember;
use App\Models\User;

class TeamMemberFactory extends Factory
{
protected $model = TeamMember::class;

public function definition(): array
{
return [
'team_id' => Team::factory(),
'user_id' => User::factory(),
];
}
}
Loading

0 comments on commit a193647

Please sign in to comment.