Skip to content

Commit

Permalink
Join?
Browse files Browse the repository at this point in the history
  • Loading branch information
nanaya committed Jan 20, 2025
1 parent f1d2b72 commit ebacf42
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 0 deletions.
68 changes: 68 additions & 0 deletions app/Http/Controllers/Teams/ApplicationsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?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\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
{
\DB::transaction(function () use ($id, $teamId) {
$team = Team::findOrFail($teamId);
$application = $team->applications()->findOrFail($id);

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

$application->delete();
$team->members()->create(['user_id' => $application->user_id]);
});

\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
{
TeamApplication::where('team_id', $teamId)->findOrFail($id)->delete();
\Session::flash('popup', osu_trans('teams.applications.reject.ok'));

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);
}
}
66 changes: 66 additions & 0 deletions app/Jobs/Notifications/TeamApplicationAccept.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?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\TeamMember;
use App\Models\User;

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

protected $achievement;

public static function getMailGroupingKey(Notification $notification): string
{
$base = parent::getMailGroupingKey($notification);

return "{$base}-{$notification->details['achievement_id']}-{$notification->source_user_id}";
}

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

public function __construct(private TeamMember $teamMember, User $source)
{
parent::__construct($source);

$this->achievement = $achievement;
}

public function getDetails(): array
{
return [
'achievement_id' => $this->achievement->getKey(),
'achievement_mode' => $this->achievement->mode,
'cover_url' => $this->achievement->iconUrl(),
'slug' => $this->achievement->slug,
'title' => $this->achievement->name,
'description' => $this->achievement->description,
'user_id' => $this->source->getKey(),
];
}

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

public function getNotifiable()
{
return $this->teamMember;
}

public function getReceiverIds(): array
{
return [$this->teamMember->getKey()];
}
}
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(),
];
}
}
30 changes: 30 additions & 0 deletions database/migrations/2025_01_15_000001_create_team_applications.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('team_applications', function (Blueprint $table) {
$table->unsignedBigInteger('user_id')->nullable(false);
$table->unsignedBigInteger('team_id')->nullable(false);
$table->timestampsTz();

$table->primary('user_id');
$table->index('team_id');
});
}

public function down(): void
{
Schema::dropIfExists('team_applications');
}
};
9 changes: 9 additions & 0 deletions resources/lang/en/authorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@
],

'team' => [
'application' => [
'store' => [
'already_member' => "You're already part of the team.",
'already_other_member' => "You're already part of a different team.",
'currently_applying' => 'You have pending team join request.',
'team_closed' => 'The team is currently not accepting any join requests.',
'team_full' => "The team is full and can't accept any more members.",
],
],
'part' => [
'is_leader' => "Team leader can't leave the team.",
'not_member' => 'Not a member of the team.',
Expand Down
13 changes: 13 additions & 0 deletions resources/lang/en/teams.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
// See the LICENCE file in the repository root for full licence text.

return [
'applications' => [
'create' => [
'title' => 'Join Team',

'form' => [
'message' => 'Message (optional)',
'message_help' => 'Write message for the team leader as part of your join request.',
],
],
],

'destroy' => [
'ok' => 'Team removed',
],
Expand Down Expand Up @@ -71,6 +82,8 @@
'show' => [
'bar' => [
'destroy' => 'Disband Team',
'join' => 'Request Join',
'join_cancel' => 'Cancel Join',
'part' => 'Leave Team',
],

Expand Down
Loading

0 comments on commit ebacf42

Please sign in to comment.