diff --git a/app/Dto/Internal/CheckInRequestDto.php b/app/Dto/Internal/CheckInRequestDto.php index 6e69f3ce16..0a0d7a68ae 100644 --- a/app/Dto/Internal/CheckInRequestDto.php +++ b/app/Dto/Internal/CheckInRequestDto.php @@ -27,6 +27,7 @@ class CheckInRequestDto public bool $forceFlag; public bool $postOnMastodonFlag; public bool $chainFlag; + public array $hiddenUserIds; public function __construct() { $this->travelReason = Business::PRIVATE; @@ -36,6 +37,7 @@ public function __construct() { $this->forceFlag = false; $this->postOnMastodonFlag = false; $this->chainFlag = false; + $this->hiddenUserIds = []; } public function setUser(Authenticatable $user): CheckInRequestDto { @@ -102,4 +104,9 @@ public function setChainFlag(bool $chainFlag): CheckInRequestDto { $this->chainFlag = $chainFlag; return $this; } + + public function setHiddenUserIds(array $hiddenUserIds): CheckInRequestDto { + $this->hiddenUserIds = $hiddenUserIds; + return $this; + } } diff --git a/app/Http/Controllers/API/v1/StatusHiddenUserController.php b/app/Http/Controllers/API/v1/StatusHiddenUserController.php new file mode 100644 index 0000000000..9c0cd050ee --- /dev/null +++ b/app/Http/Controllers/API/v1/StatusHiddenUserController.php @@ -0,0 +1,210 @@ +authorize('update', $status); + + $hiddenUsers = $status->hiddenUsers()->with('user')->get()->pluck('user'); + + return $this->sendResponse(UserResource::collection($hiddenUsers)); + } catch (ModelNotFoundException) { + return $this->sendError('Status not found', 404); + } catch (AuthorizationException) { + return $this->sendError('You are not authorized to view hidden users for this status', 403); + } + } + + /** + * @OA\Post( + * path="/status/{statusId}/hidden-users", + * operationId="addHiddenUser", + * tags={"Status"}, + * summary="Add a user to the hidden list for a status", + * description="Adds a user who will not be able to see this specific status", + * @OA\Parameter ( + * name="statusId", + * in="path", + * description="Status-ID", + * example=1337, + * @OA\Schema(type="integer") + * ), + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * required={"userId"}, + * @OA\Property(property="userId", type="integer", example=42, + * description="ID of user to hide this status from") + * ) + * ), + * @OA\Response( + * response=201, + * description="User successfully added to hidden list", + * @OA\JsonContent( + * @OA\Property(property="message", type="string", example="User added to hidden list") + * ) + * ), + * @OA\Response(response=400, description="Bad request"), + * @OA\Response(response=403, description="User not authorized"), + * @OA\Response(response=404, description="Status or user not found"), + * @OA\Response(response=409, description="User already hidden"), + * security={ + * {"passport": {"write-statuses"}}, {"token": {}} + * } + * ) + * + * @param Request $request + * @param int $statusId + * @return JsonResponse + * @throws ValidationException + */ + public function store(Request $request, int $statusId): JsonResponse { + $validator = Validator::make($request->all(), [ + 'userId' => ['required', 'integer', 'exists:users,id'], + ]); + + if ($validator->fails()) { + return $this->sendError($validator->errors(), 400); + } + + $validated = $validator->validate(); + + try { + $status = Status::findOrFail($statusId); + $this->authorize('update', $status); + + $userToHide = User::findOrFail($validated['userId']); + + // Can't hide yourself from your own status + if ($userToHide->id === $status->user_id) { + return $this->sendError('You cannot hide yourself from your own status', 400); + } + + // Check if already hidden + if ($status->hiddenUsers()->where('user_id', $userToHide->id)->exists()) { + return $this->sendError('User is already hidden from this status', 409); + } + + StatusHiddenUser::create([ + 'status_id' => $status->id, + 'user_id' => $userToHide->id, + ]); + + return $this->sendResponse(['message' => __('status.hidden-user.added')], 201); + } catch (ModelNotFoundException) { + return $this->sendError('Status or user not found', 404); + } catch (AuthorizationException) { + return $this->sendError('You are not authorized to modify hidden users for this status', 403); + } + } + + /** + * @OA\Delete( + * path="/status/{statusId}/hidden-users/{userId}", + * operationId="removeHiddenUser", + * tags={"Status"}, + * summary="Remove a user from the hidden list for a status", + * description="Removes a user from the hidden list, allowing them to see this status again", + * @OA\Parameter ( + * name="statusId", + * in="path", + * description="Status-ID", + * example=1337, + * @OA\Schema(type="integer") + * ), + * @OA\Parameter ( + * name="userId", + * in="path", + * description="User-ID to remove from hidden list", + * example=42, + * @OA\Schema(type="integer") + * ), + * @OA\Response( + * response=200, + * description="User successfully removed from hidden list", + * @OA\JsonContent( + * @OA\Property(property="message", type="string", example="User removed from hidden list") + * ) + * ), + * @OA\Response(response=403, description="User not authorized"), + * @OA\Response(response=404, description="Status or hidden user entry not found"), + * security={ + * {"passport": {"write-statuses"}}, {"token": {}} + * } + * ) + * + * @param int $statusId + * @param int $userId + * @return JsonResponse + */ + public function destroy(int $statusId, int $userId): JsonResponse { + try { + $status = Status::findOrFail($statusId); + $this->authorize('update', $status); + + $hiddenEntry = $status->hiddenUsers()->where('user_id', $userId)->first(); + + if (!$hiddenEntry) { + return $this->sendError('User is not hidden from this status', 404); + } + + $hiddenEntry->delete(); + + return $this->sendResponse(['message' => __('status.hidden-user.removed')]); + } catch (ModelNotFoundException) { + return $this->sendError('Status not found', 404); + } catch (AuthorizationException) { + return $this->sendError('You are not authorized to modify hidden users for this status', 403); + } + } +} diff --git a/app/Http/Controllers/API/v1/TransportController.php b/app/Http/Controllers/API/v1/TransportController.php index a2ecffb12e..dfd5987af0 100644 --- a/app/Http/Controllers/API/v1/TransportController.php +++ b/app/Http/Controllers/API/v1/TransportController.php @@ -393,22 +393,30 @@ public function create(Request $request): JsonResponse { $withUsers = null; $validated = $request->validate([ - 'body' => ['nullable', 'max:280'], - 'business' => ['nullable', new Enum(Business::class)], - 'visibility' => ['nullable', new Enum(StatusVisibility::class)], - 'eventId' => ['nullable', 'integer', 'exists:events,id'], - 'toot' => ['nullable', 'boolean'], - 'chainPost' => ['nullable', 'boolean'], - 'ibnr' => ['nullable', 'boolean'], - 'tripId' => ['required'], - 'lineName' => ['required'], - 'start' => ['required', 'numeric'], - 'destination' => ['required', 'numeric'], - 'departure' => ['required', 'date'], - 'arrival' => ['required', 'date'], - 'force' => ['nullable', 'boolean'], - 'with' => ['nullable', 'array', 'max:10'], + 'body' => ['nullable', 'max:280'], + 'business' => ['nullable', new Enum(Business::class)], + 'visibility' => ['nullable', new Enum(StatusVisibility::class)], + 'eventId' => ['nullable', 'integer', 'exists:events,id'], + 'toot' => ['nullable', 'boolean'], + 'chainPost' => ['nullable', 'boolean'], + 'ibnr' => ['nullable', 'boolean'], + 'tripId' => ['required'], + 'lineName' => ['required'], + 'start' => ['required', 'numeric'], + 'destination' => ['required', 'numeric'], + 'departure' => ['required', 'date'], + 'arrival' => ['required', 'date'], + 'force' => ['nullable', 'boolean'], + 'with' => ['nullable', 'array', 'max:10'], + 'hiddenUserIds' => ['nullable', 'array'], + 'hiddenUserIds.*' => ['integer', 'exists:users,id'], ]); + + // Validate that user is not trying to hide themselves + if (isset($validated['hiddenUserIds']) && in_array(Auth::id(), $validated['hiddenUserIds'])) { + return $this->sendError('You cannot hide yourself from your own status.', 400); + } + if (isset($validated['with'])) { $withUsers = User::whereIn('id', $validated['with'])->get(); $forbiddenUsers = collect(); diff --git a/app/Http/Controllers/Backend/Transport/StatusController.php b/app/Http/Controllers/Backend/Transport/StatusController.php index 50d7945aca..cbd0df72bd 100644 --- a/app/Http/Controllers/Backend/Transport/StatusController.php +++ b/app/Http/Controllers/Backend/Transport/StatusController.php @@ -56,42 +56,52 @@ public static function getPrintableEscapedBody(Status $status): string { */ public static function filterStatusVisibility(?User $viewingUser = null): Closure { return function(EloquentBuilder $query) use ($viewingUser) { - //Visibility checks: One of the following options must be true - - //Option 1: User is public AND status is public - $query->where(function(EloquentBuilder $query) use ($viewingUser) { - $query->where('users.private_profile', 0) - ->whereIn('visibility', [StatusVisibility::PUBLIC->value] + ($viewingUser !== null ? [StatusVisibility::AUTHENTICATED->value] : [])); - }); - if ($viewingUser !== null) { - //Option 2: Status is from oneself - $query->orWhere('users.id', $viewingUser->id); - - //Option 3: Status is from a followed BUT not unlisted or private or trusted users only - $query->orWhere(function(EloquentBuilder $query) use ($viewingUser) { - $query->whereIn('users.id', $viewingUser->follows()->select('follow_id')) - ->whereNotIn('statuses.visibility', [ - StatusVisibility::UNLISTED->value, - StatusVisibility::PRIVATE->value, - StatusVisibility::TRUSTED->value, - ]); + $query->whereNotExists(function(QueryBuilder $subQuery) use ($viewingUser) { + $subQuery->select(DB::raw(1)) + ->from('status_hidden_users') + ->whereColumn('status_hidden_users.status_id', 'statuses.id') + ->where('status_hidden_users.user_id', $viewingUser->id); }); + } - //Option 4: Status is from a user who trusts the viewing user - $query->orWhere(function(EloquentBuilder $query) use ($viewingUser) { - $query->where('statuses.visibility', StatusVisibility::TRUSTED->value) - ->whereExists(function(QueryBuilder $subQuery) use ($viewingUser) { - $subQuery->from('trusted_users') - ->whereColumn('trusted_users.user_id', 'statuses.user_id') - ->where('trusted_users.trusted_id', $viewingUser->id) - ->where(function(QueryBuilder $expireQuery) { - $expireQuery->whereNull('trusted_users.expires_at') - ->orWhere('trusted_users.expires_at', '>', now()); - }); - }); + //Visibility checks: One of the following options must be true + $query->where(function(EloquentBuilder $visibility) use ($viewingUser) { + //Option 1: User is public AND status is public + $visibility->where(function(EloquentBuilder $query) use ($viewingUser) { + $query->where('users.private_profile', 0) + ->whereIn('visibility', [StatusVisibility::PUBLIC->value] + ($viewingUser !== null ? [StatusVisibility::AUTHENTICATED->value] : [])); }); - } + + if ($viewingUser !== null) { + //Option 2: Status is from oneself + $visibility->orWhere('users.id', $viewingUser->id); + + //Option 3: Status is from a followed BUT not unlisted or private or trusted users only + $visibility->orWhere(function(EloquentBuilder $query) use ($viewingUser) { + $query->whereIn('users.id', $viewingUser->follows()->select('follow_id')) + ->whereNotIn('statuses.visibility', [ + StatusVisibility::UNLISTED->value, + StatusVisibility::PRIVATE->value, + StatusVisibility::TRUSTED->value, + ]); + }); + + //Option 4: Status is from a user who trusts the viewing user + $visibility->orWhere(function(EloquentBuilder $query) use ($viewingUser) { + $query->where('statuses.visibility', StatusVisibility::TRUSTED->value) + ->whereExists(function(QueryBuilder $subQuery) use ($viewingUser) { + $subQuery->from('trusted_users') + ->whereColumn('trusted_users.user_id', 'statuses.user_id') + ->where('trusted_users.trusted_id', $viewingUser->id) + ->where(function(QueryBuilder $expireQuery) { + $expireQuery->whereNull('trusted_users.expires_at') + ->orWhere('trusted_users.expires_at', '>', now()); + }); + }); + }); + } + }); }; } } diff --git a/app/Http/Controllers/Backend/Transport/TrainCheckinController.php b/app/Http/Controllers/Backend/Transport/TrainCheckinController.php index 6509ea22b3..299c824f4d 100644 --- a/app/Http/Controllers/Backend/Transport/TrainCheckinController.php +++ b/app/Http/Controllers/Backend/Transport/TrainCheckinController.php @@ -55,6 +55,16 @@ public static function checkin(CheckInRequestDto $dto, ?User $checkedInBy = null event: $dto->event ); + // Add hidden users if specified + if (!empty($dto->hiddenUserIds)) { + foreach ($dto->hiddenUserIds as $userId) { + \App\Models\StatusHiddenUser::create([ + 'status_id' => $status->id, + 'user_id' => $userId, + ]); + } + } + $checkinResponse = self::createCheckin( status: $status, trip: $dto->trip, diff --git a/app/Http/Controllers/Backend/User/DashboardController.php b/app/Http/Controllers/Backend/User/DashboardController.php index 367e06db94..359cff0e01 100644 --- a/app/Http/Controllers/Backend/User/DashboardController.php +++ b/app/Http/Controllers/Backend/User/DashboardController.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Query\Builder as QueryBuilder; +use Illuminate\Support\Facades\DB; abstract class DashboardController extends Controller { @@ -17,6 +18,15 @@ abstract class DashboardController extends Controller public static function getPrivateDashboard(User $user): Paginator { $followingIDs = $user->follows->pluck('id'); $followingIDs[] = $user->id; + $hiddenFilter = static function(EloquentBuilder $query) use ($user) { + $query->whereNotExists(function(QueryBuilder $sub) use ($user) { + $sub->select(DB::raw(1)) + ->from('status_hidden_users') + ->whereColumn('status_hidden_users.status_id', 'statuses.id') + ->where('status_hidden_users.user_id', $user->id); + }); + }; + return Status::with([ 'event', 'likes', @@ -31,33 +41,47 @@ public static function getPrivateDashboard(User $user): Paginator { ]) ->join('train_checkins', 'train_checkins.status_id', '=', 'statuses.id') ->select('statuses.*') - ->where('train_checkins.departure', '<', now()->addMinutes(20)) - ->where('statuses.created_at', '>=', now()->subDays(7)) - ->whereIn('statuses.user_id', $followingIDs) - ->whereNotIn('statuses.user_id', $user->mutedUsers->pluck('id')) - ->where(function(EloquentBuilder $query) use ($user) { - $query->whereIn('statuses.visibility', [ - StatusVisibility::PUBLIC->value, - StatusVisibility::FOLLOWERS->value, - StatusVisibility::AUTHENTICATED->value - ]) - ->orWhere(function(EloquentBuilder $trustedQuery) use ($user) { - $trustedQuery->where('statuses.visibility', StatusVisibility::TRUSTED->value) - ->whereExists(function(QueryBuilder $sub) use ($user) { - $sub->from('trusted_users') - ->whereColumn('trusted_users.user_id', 'statuses.user_id') - ->where('trusted_users.trusted_id', $user->id) - ->where(function($expireQuery) { - $expireQuery->whereNull('trusted_users.expires_at') - ->orWhere('trusted_users.expires_at', '>', now()); + ->where(function(EloquentBuilder $outer) use ($user, $followingIDs, $hiddenFilter) { + $outer->where(function(EloquentBuilder $query) use ($user, $followingIDs, $hiddenFilter) { + $query->where('train_checkins.departure', '<', now()->addMinutes(20)) + ->where('statuses.created_at', '>=', now()->subDays(7)) + ->whereIn('statuses.user_id', $followingIDs) + ->whereNotIn('statuses.user_id', $user->mutedUsers->pluck('id')) + ->where($hiddenFilter) + ->where(function(EloquentBuilder $visibility) use ($user) { + $visibility->whereIn('statuses.visibility', [ + StatusVisibility::PUBLIC->value, + StatusVisibility::FOLLOWERS->value, + StatusVisibility::AUTHENTICATED->value + ]) + ->orWhere(function(EloquentBuilder $trustedQuery) use ($user) { + $trustedQuery->where('statuses.visibility', + StatusVisibility::TRUSTED->value) + ->whereExists( + function( + QueryBuilder $sub) use ($user) { + $sub->from('trusted_users') + ->whereColumn( + 'trusted_users.user_id', + 'statuses.user_id') + ->where( + 'trusted_users.trusted_id', + $user->id) + ->where(function($expireQuery) { + $expireQuery->whereNull( + 'trusted_users.expires_at') + ->orWhere('trusted_users.expires_at', '>', now()); + }); + }); }); - }); + }); + }) + ->orWhere(function(EloquentBuilder $query) use ($user, $hiddenFilter) { + $query->where('statuses.user_id', $user->id) + ->where('train_checkins.departure', '<', Carbon::now()->addMinutes(20)) + ->where($hiddenFilter); }); }) - ->orWhere(function($query) use ($user) { - $query->where('statuses.user_id', $user->id) - ->where('train_checkins.departure', '<', Carbon::now()->addMinutes(20)); - }) ->orderBy('train_checkins.departure', 'desc') ->latest() ->simplePaginate(15); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 95e36f5ebe..823f553398 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -81,6 +81,14 @@ public static function statusesForUser(User $user, ?int $limit = null): ?Paginat } }); }) + ->when(Auth::check(), function($query) { + $query->whereNotExists(function($subQuery) { + $subQuery->select(DB::raw(1)) + ->from('status_hidden_users') + ->whereColumn('status_hidden_users.status_id', 'statuses.id') + ->where('status_hidden_users.user_id', auth()->id()); + }); + }) ->select('statuses.*') ->orderByDesc('train_checkins.departure') ->simplePaginate($limit !== null && $limit <= 15 ? $limit : 15); diff --git a/app/Hydrators/CheckinRequestHydrator.php b/app/Hydrators/CheckinRequestHydrator.php index 84c3721490..a369d66e9c 100644 --- a/app/Hydrators/CheckinRequestHydrator.php +++ b/app/Hydrators/CheckinRequestHydrator.php @@ -102,6 +102,7 @@ private function parseDefaultFields(): void { ->setEvent($event) ->setForceFlag(!empty($this->validated['force'])) ->setPostOnMastodonFlag(!empty($this->validated['toot'])) - ->setChainFlag(!empty($this->validated['chainPost'])); + ->setChainFlag(!empty($this->validated['chainPost'])) + ->setHiddenUserIds($this->validated['hiddenUserIds'] ?? []); } } diff --git a/app/Models/Status.php b/app/Models/Status.php index 6e4789161c..406392ecff 100644 --- a/app/Models/Status.php +++ b/app/Models/Status.php @@ -78,6 +78,13 @@ public function mentions(): HasMany { return $this->hasMany(Mention::class, 'status_id', 'id'); } + /** + * Users that must not see this specific status. + */ + public function hiddenUsers(): HasMany { + return $this->hasMany(StatusHiddenUser::class, 'status_id', 'id'); + } + public function getFavoritedAttribute(): ?bool { if (!Auth::check()) { return null; diff --git a/app/Models/StatusHiddenUser.php b/app/Models/StatusHiddenUser.php new file mode 100644 index 0000000000..78d807572e --- /dev/null +++ b/app/Models/StatusHiddenUser.php @@ -0,0 +1,40 @@ + 'string', + 'status_id' => 'integer', + 'user_id' => 'integer', + ]; + + /** + * Get the status that this hidden user entry belongs to. + */ + public function status(): BelongsTo { + return $this->belongsTo(Status::class, 'status_id', 'id'); + } + + /** + * Get the user who is hidden from viewing the status. + */ + public function user(): BelongsTo { + return $this->belongsTo(User::class, 'user_id', 'id'); + } +} diff --git a/app/Policies/StatusPolicy.php b/app/Policies/StatusPolicy.php index e7d135bd0a..4cf777636d 100644 --- a/app/Policies/StatusPolicy.php +++ b/app/Policies/StatusPolicy.php @@ -47,6 +47,11 @@ public function view(?User $user, Status $status): Response|bool { return Response::deny(__('profile.youre-blocked-text')); } + // Case 3½: User is explicitly hidden from this status + if ($status->hiddenUsers()->where('user_id', $user->id)->exists()) { + return Response::deny(__('status.hidden-from-you')); + } + // Case 4: Status is private and the status doesn't belong to the user if ($status->visibility === StatusVisibility::PRIVATE) { return Response::deny('Status is private'); diff --git a/database/migrations/2024_08_11_000000_drop_tweet_id_from_statuses.php b/database/migrations/2024_08_11_000000_drop_tweet_id_from_statuses.php index b197c29bf4..e2e20b3de7 100644 --- a/database/migrations/2024_08_11_000000_drop_tweet_id_from_statuses.php +++ b/database/migrations/2024_08_11_000000_drop_tweet_id_from_statuses.php @@ -7,14 +7,22 @@ return new class extends Migration { public function up(): void { + // SQLite cannot drop columns without rebuilding tables; skip there to keep local dev easy. + if (Schema::connection(null)->getConnection()->getDriverName() === 'sqlite') { + return; + } + Schema::table('statuses', static function(Blueprint $table) { $table->dropColumn('tweet_id'); }); } public function down(): void { + // Only add back when column is missing; safe for sqlite Schema::table('statuses', static function(Blueprint $table) { - $table->string('tweet_id')->nullable()->after('event_id'); + if (!Schema::hasColumn('statuses', 'tweet_id')) { + $table->string('tweet_id')->nullable()->after('event_id'); + } }); } }; diff --git a/database/migrations/2025_05_07_203458_remove-twitter.php b/database/migrations/2025_05_07_203458_remove-twitter.php index b75459c9f7..fb8042e48d 100644 --- a/database/migrations/2025_05_07_203458_remove-twitter.php +++ b/database/migrations/2025_05_07_203458_remove-twitter.php @@ -7,6 +7,10 @@ return new class extends Migration { public function up(): void { + if (Schema::connection(null)->getConnection()->getDriverName() === 'sqlite') { + return; + } + Schema::table('social_login_profiles', function(Blueprint $table) { $table->dropUnique(['twitter_id']); $table->dropColumn('twitter_id'); diff --git a/database/migrations/2025_07_18_083705_drop_identifiers_from_hafas_operator.php b/database/migrations/2025_07_18_083705_drop_identifiers_from_hafas_operator.php index 317f2859f4..6a1afd513d 100644 --- a/database/migrations/2025_07_18_083705_drop_identifiers_from_hafas_operator.php +++ b/database/migrations/2025_07_18_083705_drop_identifiers_from_hafas_operator.php @@ -10,6 +10,10 @@ return new class extends Migration { public function up(): void { + if (Schema::connection(null)->getConnection()->getDriverName() === 'sqlite') { + return; + } + foreach (Operator::all() as $operator) { // Migrate existing identifiers to the new OperatorIdentifier model if ($operator->hafas_id) { diff --git a/database/migrations/2025_12_21_000000_create_status_hidden_users_table.php b/database/migrations/2025_12_21_000000_create_status_hidden_users_table.php new file mode 100644 index 0000000000..05badfe61a --- /dev/null +++ b/database/migrations/2025_12_21_000000_create_status_hidden_users_table.php @@ -0,0 +1,25 @@ +uuid('id')->primary(); + $table->foreignId('status_id')->constrained('statuses')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->timestamps(); + + $table->unique(['status_id', 'user_id']); + }); + } + + public function down(): void + { + Schema::dropIfExists('status_hidden_users'); + } +}; diff --git a/lang/de.json b/lang/de.json index 35db275a15..fa6630c34d 100644 --- a/lang/de.json +++ b/lang/de.json @@ -486,6 +486,15 @@ "status.visibility.5": "Vertraute Nutzer", "status.visibility.5.detail": "Nur für von dir vertrauten Nutzern sichtbar", "status.update.success": "Dein Status wurde erfolgreich aktualisiert.", + "status.hidden-users": "Vor Nutzern verbergen", + "status.hidden-users.description": "Wähle Nutzer aus, die diesen Check-in nicht sehen dürfen.", + "status.hidden-users.search": "Nutzer suchen ...", + "status.hidden-users.add": "Nutzer ausblenden", + "status.hidden-users.remove": "Ausblenden entfernen", + "status.hidden-users.empty": "Es ist noch niemand ausgeblendet.", + "status.hidden-user.added": "Nutzer wurde für diesen Status ausgeblendet.", + "status.hidden-user.removed": "Nutzer darf den Status wieder sehen.", + "status.hidden-from-you": "Dieser Status wurde für dich ausgeblendet.", "status.manual_journey_number": "Fahrtennummer manuell eingeben", "status.show_more": "Mehr anzeigen", "time-format": "HH:mm", diff --git a/lang/en.json b/lang/en.json index 06010efae8..bd9953acff 100644 --- a/lang/en.json +++ b/lang/en.json @@ -453,6 +453,15 @@ "status.visibility.5": "Trusted users", "status.visibility.5.detail": "Only visible for your trusted users", "status.update.success": "Your Status has been updated successfully.", + "status.hidden-users": "Hide from users", + "status.hidden-users.description": "Choose users who must not see this check-in.", + "status.hidden-users.search": "Search users...", + "status.hidden-users.add": "Hide from this user", + "status.hidden-users.remove": "Remove hidden user", + "status.hidden-users.empty": "No one is hidden for this status yet.", + "status.hidden-user.added": "User hidden for this status.", + "status.hidden-user.removed": "User removed from hidden list.", + "status.hidden-from-you": "This status is hidden from you.", "status.manual_journey_number": "Journey number manually overwritten", "status.show_more": "Show more", "transport_types.bus": "Bus", diff --git a/resources/vue/components/Checkin/CheckinInterface.vue b/resources/vue/components/Checkin/CheckinInterface.vue index 1b42bab706..df55eb1fd7 100644 --- a/resources/vue/components/Checkin/CheckinInterface.vue +++ b/resources/vue/components/Checkin/CheckinInterface.vue @@ -11,9 +11,10 @@ import {checkinSuccessStore} from "../../stores/checkinSuccess"; import {useUserStore} from "../../stores/user"; import BusinessDropdown from "../BusinessDropdown.vue"; import VisibilityDropdown from "../VisibilityDropdown.vue"; +import HiddenUsersSelectorCheckin from "../HiddenUsersSelectorCheckin.vue"; export default { - components: {VisibilityDropdown, BusinessDropdown, TagList, EventDropdown, FriendDropdown}, + components: {VisibilityDropdown, BusinessDropdown, TagList, EventDropdown, FriendDropdown, HiddenUsersSelectorCheckin}, setup() { const userStore = useUserStore(); userStore.fetchSettings(); @@ -52,7 +53,8 @@ export default { notyf: new Notyf({position: {x: "right", y: "bottom"}}), collision: false, selectedEvent: null, - selectedFriends: [] + selectedFriends: [], + hiddenUserIds: [] }; }, methods: { @@ -74,7 +76,8 @@ export default { arrival: DateTime.fromISO(this.selectedDestination.arrivalPlanned).setZone("UTC").toISO(), force: this.collision, eventId: this.selectedEvent ? this.selectedEvent.id : null, - with: this.selectedFriends.map((friend) => friend.user.id) + with: this.selectedFriends.map((friend) => friend.user.id), + hiddenUserIds: this.hiddenUserIds }; fetch("/api/v1/trains/checkin", { method: "POST", @@ -121,6 +124,9 @@ export default { }, selectFriends(friends) { this.selectedFriends = friends; + }, + updateHiddenUsers(userIds) { + this.hiddenUserIds = userIds; } }, computed: { @@ -202,6 +208,7 @@ export default { +