mirror of
https://github.com/Kovah/LinkAce.git
synced 2025-01-17 13:18:21 +01:00
Add public user profiles (#165)
This commit is contained in:
parent
26ac19a114
commit
cbebfa48eb
@ -23,6 +23,8 @@ class SetDefaultSettingsForUser extends SettingsMigration
|
||||
$this->migrator->add($group . '.time_format', $defaults['time_format']);
|
||||
$this->migrator->add($group . '.locale', $defaults['locale']);
|
||||
|
||||
$this->migrator->add($group . '.profile_is_public', $defaults['profile_is_public']);
|
||||
|
||||
$this->migrator->add($group . '.links_default_visibility', $defaults['links_default_visibility']);
|
||||
$this->migrator->add($group . '.notes_default_visibility', $defaults['notes_default_visibility']);
|
||||
$this->migrator->add($group . '.lists_default_visibility', $defaults['lists_default_visibility']);
|
||||
|
@ -31,14 +31,19 @@ function setupCompleted()
|
||||
* Shorthand for the current user settings
|
||||
*
|
||||
* @param string $key
|
||||
* @param int|null $userId
|
||||
* @return mixed
|
||||
*/
|
||||
function usersettings(string $key = ''): mixed
|
||||
function usersettings(string $key = '', ?int $userId = null): mixed
|
||||
{
|
||||
if (!auth()->user()) {
|
||||
if (is_null($userId) && !auth()->user()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_null($userId)) {
|
||||
app(UserSettings::class)::setUserId($userId);
|
||||
}
|
||||
|
||||
if ($key === '') {
|
||||
return app(UserSettings::class)->toArray();
|
||||
}
|
||||
|
@ -53,8 +53,6 @@ class UserSettingsController extends Controller
|
||||
*/
|
||||
public function saveAppSettings(UserSettings $settings, UserSettingsUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$userId = $request->user()->id;
|
||||
|
||||
// Save all user settings or update them
|
||||
$newSettings = $request->except(['_token', 'share']);
|
||||
foreach ($newSettings as $key => $value) {
|
||||
|
54
app/Http/Controllers/Guest/UserController.php
Normal file
54
app/Http/Controllers/Guest/UserController.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Guest;
|
||||
|
||||
use App\Enums\ModelAttribute;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Link;
|
||||
use App\Models\LinkList;
|
||||
use App\Models\Note;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function show(User $user)
|
||||
{
|
||||
if (usersettings('profile_is_public', $user->id) === false) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$links = Link::byUser($user->id)
|
||||
->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)
|
||||
->latest()
|
||||
->take(10)
|
||||
->paginate(pageName: 'link_page');
|
||||
|
||||
$lists = LinkList::byUser($user->id)
|
||||
->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)
|
||||
->latest()
|
||||
->take(10)
|
||||
->paginate(pageName: 'link_page');
|
||||
|
||||
$tags = Tag::byUser($user->id)
|
||||
->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)
|
||||
->latest()
|
||||
->take(10)
|
||||
->paginate(pageName: 'link_page');
|
||||
|
||||
$stats = [
|
||||
'total_links' => Link::byUser($user->id)->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)->count(),
|
||||
'total_lists' => LinkList::byUser($user->id)->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)->count(),
|
||||
'total_tags' => Tag::byUser($user->id)->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)->count(),
|
||||
'total_notes' => Note::byUser($user->id)->whereVisibility(ModelAttribute::VISIBILITY_PUBLIC)->count(),
|
||||
];
|
||||
|
||||
return view('guest.users.show', [
|
||||
'user' => $user,
|
||||
'links' => $links,
|
||||
'lists' => $lists,
|
||||
'tags' => $tags,
|
||||
'stats' => $stats,
|
||||
]);
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ class UserSettings extends Settings
|
||||
public string $time_format;
|
||||
public string $locale;
|
||||
|
||||
public bool $profile_is_public;
|
||||
|
||||
public int $links_default_visibility;
|
||||
public int $notes_default_visibility;
|
||||
public int $lists_default_visibility;
|
||||
@ -47,16 +49,23 @@ class UserSettings extends Settings
|
||||
public bool $share_whatsapp;
|
||||
public bool $share_xing;
|
||||
|
||||
private static int $user_id = 0;
|
||||
|
||||
public static function group(): string
|
||||
{
|
||||
return 'user-' . auth()->id();
|
||||
return 'user-' . self::getUserId();
|
||||
}
|
||||
|
||||
public static function setUserId(int $user_id): void
|
||||
{
|
||||
self::$user_id = $user_id;
|
||||
}
|
||||
|
||||
protected static function getUserId(): int
|
||||
{
|
||||
return self::$user_id ?: auth()->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default settings for users
|
||||
*
|
||||
* @return array{string: string|int|bool|null}
|
||||
*/
|
||||
public static function defaults(): array
|
||||
{
|
||||
return [
|
||||
@ -64,6 +73,7 @@ class UserSettings extends Settings
|
||||
'date_format' => config('linkace.default.date_format'),
|
||||
'time_format' => config('linkace.default.time_format'),
|
||||
'locale' => config('app.fallback_locale'),
|
||||
'profile_is_public' => false,
|
||||
'links_default_visibility' => ModelAttribute::VISIBILITY_PUBLIC,
|
||||
'notes_default_visibility' => ModelAttribute::VISIBILITY_PUBLIC,
|
||||
'lists_default_visibility' => ModelAttribute::VISIBILITY_PUBLIC,
|
||||
|
@ -79,6 +79,11 @@ class MigrateExistingSettings extends SettingsMigration
|
||||
$this->userSettings->get('locale', config('app.fallback_locale'))
|
||||
);
|
||||
|
||||
$this->migrator->add(
|
||||
'user-1.profile_is_public',
|
||||
(bool)$this->sysSettings->get('system_guest_access', false)
|
||||
);
|
||||
|
||||
$this->migrator->add(
|
||||
'user-1.links_default_visibility',
|
||||
$this->userSettings->get('links_private_default', false)
|
||||
|
@ -18,7 +18,9 @@ return [
|
||||
'markdown_for_text' => 'Enable Markdown for descriptions and notes',
|
||||
|
||||
'privacy' => 'Privacy',
|
||||
'default_visibility_help' => 'Choose the default visibility for Links, Lists, Notes and Tags when adding new entries.',
|
||||
'profile_privacy' => 'The following settings apply to your user profile which is visible to guests.',
|
||||
'profile_is_public' => 'Profile is public',
|
||||
'default_visibility_help' => 'The following settings define the default visibility for Links, Lists, Notes and Tags when adding new ones.',
|
||||
'links_default_visibility' => 'Default Links visibility',
|
||||
'notes_default_visibility' => 'Default Notes visibility',
|
||||
'lists_default_visibility' => 'Default Lists visibility',
|
||||
|
@ -14,6 +14,33 @@ $settings = [
|
||||
@lang('settings.privacy')
|
||||
</h5>
|
||||
|
||||
<p>@lang('settings.profile_privacy')</p>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 col-sm-8 col-md-6">
|
||||
<label class="form-label" for="profile_is_public">
|
||||
@lang('settings.profile_is_public')
|
||||
</label>
|
||||
<select id="profile_is_public" name="profile_is_public"
|
||||
class="form-select{{ $errors->has('profile_is_public') ? ' is-invalid' : '' }}">
|
||||
<option value="1"
|
||||
@if(usersettings('profile_is_public') === true) selected @endif>
|
||||
@lang('linkace.yes')
|
||||
</option>
|
||||
<option value="0"
|
||||
@if(usersettings('profile_is_public') === false) selected @endif>
|
||||
@lang('linkace.no')
|
||||
</option>
|
||||
</select>
|
||||
@if ($errors->has('profile_is_public'))
|
||||
<p class="invalid-feedback" role="alert">
|
||||
{{ $errors->first('profile_is_public') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p>@lang('settings.default_visibility_help')</p>
|
||||
|
||||
<div class="row">
|
||||
|
103
resources/views/guest/users/show.blade.php
Normal file
103
resources/views/guest/users/show.blade.php
Normal file
@ -0,0 +1,103 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
@lang('user.user')
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h2 class="mb-0">{{ $user->name }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-7">
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('link.recent_links')
|
||||
</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
@forelse($links as $link)
|
||||
<a href="{{ $link->url }}" class="list-group-item list-group-item-action one-line">
|
||||
{!! $link->getIcon('me-1') !!}
|
||||
{{ $link->title }}
|
||||
</a>
|
||||
@empty
|
||||
<li class="list-group-item text-muted">
|
||||
@lang('linkace.no_results_found', ['model' => trans('link.links')])
|
||||
</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-5">
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('stats.stats')
|
||||
</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="me-1">@lang('stats.total_links')</span>
|
||||
<span class="badge bg-secondary">{{ $stats['total_links'] }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="me-1">@lang('stats.total_lists')</span>
|
||||
<span class="badge bg-secondary">{{ $stats['total_lists'] }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="me-1">@lang('stats.total_tags')</span>
|
||||
<span class="badge bg-secondary">{{ $stats['total_tags'] }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="me-1">@lang('stats.total_notes')</span>
|
||||
<span class="badge bg-secondary">{{ $stats['total_notes'] }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('list.recent_lists')
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@forelse($lists as $list)
|
||||
<a href="{{ route('guest.lists.show', ['list' => $list]) }}" class="btn btn-light btn-sm m-1">
|
||||
{{ $list->name }}
|
||||
</a>
|
||||
@empty
|
||||
<div class="text-muted">
|
||||
@lang('linkace.no_results_found', ['model' => trans('list.lists')])
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('tag.recent_tags')
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@forelse($tags as $tag)
|
||||
<a href="{{ route('guest.tags.show', ['tag' => $tag]) }}" class="btn btn-light btn-sm m-1">
|
||||
{{ $tag->name }}
|
||||
</a>
|
||||
@empty
|
||||
<div class="text-muted">
|
||||
@lang('linkace.no_results_found', ['model' => trans('tag.tags')])
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
@ -62,12 +62,6 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-7">
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('list.recent_lists')
|
||||
@ -75,7 +69,7 @@
|
||||
|
||||
<div class="card-body">
|
||||
@forelse($lists as $list)
|
||||
<a href="{{ route('lists.show', ['list' => $list]) }}" class="btn btn-light btn-sm">
|
||||
<a href="{{ route('lists.show', ['list' => $list]) }}" class="btn btn-light btn-sm m-1">
|
||||
{{ $list->name }}
|
||||
</a>
|
||||
@empty
|
||||
@ -86,9 +80,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-5">
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('tag.recent_tags')
|
||||
@ -96,7 +87,7 @@
|
||||
|
||||
<div class="card-body">
|
||||
@forelse($tags as $tag)
|
||||
<a href="{{ route('tags.show', ['tag' => $tag]) }}" class="btn btn-light btn-sm">
|
||||
<a href="{{ route('tags.show', ['tag' => $tag]) }}" class="btn btn-light btn-sm m-1">
|
||||
{{ $tag->name }}
|
||||
</a>
|
||||
@empty
|
||||
|
@ -18,6 +18,7 @@ use App\Http\Controllers\Guest\FeedController as GuestFeedController;
|
||||
use App\Http\Controllers\Guest\LinkController as GuestLinkController;
|
||||
use App\Http\Controllers\Guest\ListController as GuestListController;
|
||||
use App\Http\Controllers\Guest\TagController as GuestTagController;
|
||||
use App\Http\Controllers\Guest\UserController as GuestUserController;
|
||||
use App\Http\Controllers\Models\LinkController;
|
||||
use App\Http\Controllers\Models\ListController;
|
||||
use App\Http\Controllers\Models\NoteController;
|
||||
@ -180,6 +181,8 @@ Route::prefix('guest')->middleware(['guestaccess'])->group(function () {
|
||||
Route::get('tags/feed', [GuestFeedController::class, 'tags'])->name('guest.tags.feed');
|
||||
Route::get('tags/{tag}/feed', [GuestFeedController::class, 'tagLinks'])->name('guest.tags.links.feed');
|
||||
|
||||
Route::get('users/{user:name}', [GuestUserController::class, 'show'])->name('guest.users.show');
|
||||
|
||||
Route::resource('links', GuestLinkController::class)
|
||||
->only(['index'])
|
||||
->names([
|
||||
|
@ -14,7 +14,7 @@ class FeedControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private $user;
|
||||
private User $user;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@ -82,7 +82,7 @@ class FeedControllerTest extends TestCase
|
||||
$tagLink = Link::factory()->create();
|
||||
$unrelatedLink = Link::factory()->create();
|
||||
|
||||
$tagLink->tags()->sync(['tag' => $tag]);
|
||||
$tagLink->tags()->sync([$tag->id]);
|
||||
|
||||
$response = $this->getAuthorized('tags/1/feed');
|
||||
|
||||
|
68
tests/Controller/Guest/UserControllerTest.php
Normal file
68
tests/Controller/Guest/UserControllerTest.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Controller\Guest;
|
||||
|
||||
use App\Enums\ModelAttribute;
|
||||
use App\Models\Link;
|
||||
use App\Models\LinkList;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use App\Settings\SystemSettings;
|
||||
use App\Settings\UserSettings;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function testPublicUserProfile(): void
|
||||
{
|
||||
SystemSettings::fake(['setup_completed' => true, 'guest_access_enabled' => true]);
|
||||
|
||||
User::factory()->create(['name' => 'MrTestUser']);
|
||||
|
||||
Link::factory()->create(['url' => 'https://public.com', 'visibility' => ModelAttribute::VISIBILITY_PUBLIC]);
|
||||
Link::factory()->create(['url' => 'https://internal.com', 'visibility' => ModelAttribute::VISIBILITY_INTERNAL]);
|
||||
Link::factory()->create(['url' => 'https://private.com', 'visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
LinkList::factory()->create(['name' => 'Public List', 'visibility' => ModelAttribute::VISIBILITY_PUBLIC]);
|
||||
LinkList::factory()->create(['name' => 'Internal List', 'visibility' => ModelAttribute::VISIBILITY_INTERNAL]);
|
||||
LinkList::factory()->create(['name' => 'Private List', 'visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
Tag::factory()->create(['name' => 'Public Tag', 'visibility' => ModelAttribute::VISIBILITY_PUBLIC]);
|
||||
Tag::factory()->create(['name' => 'Internal Tag', 'visibility' => ModelAttribute::VISIBILITY_INTERNAL]);
|
||||
Tag::factory()->create(['name' => 'Private Tag', 'visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
UserSettings::setUserId(1);
|
||||
UserSettings::fake([
|
||||
'profile_is_public' => true,
|
||||
]);
|
||||
|
||||
$this->get('guest/users/MrTestUser')
|
||||
->assertOk()
|
||||
->assertSee('MrTestUser')
|
||||
->assertSee('https://public.com')
|
||||
->assertDontSee('https://internal.com')
|
||||
->assertDontSee('https://private.com')
|
||||
->assertSee('Public List')
|
||||
->assertDontSee('Internal List')
|
||||
->assertDontSee('Private List')
|
||||
->assertSee('Public Tag')
|
||||
->assertDontSee('Internal Tag')
|
||||
->assertDontSee('Private Tag');
|
||||
}
|
||||
|
||||
public function testPrivateUserProfile(): void
|
||||
{
|
||||
SystemSettings::fake(['setup_completed' => true, 'guest_access_enabled' => true]);
|
||||
|
||||
User::factory()->create(['name' => 'MrPrivateUser']);
|
||||
|
||||
UserSettings::fake([
|
||||
'profile_is_public' => false,
|
||||
]);
|
||||
|
||||
$this->get('guest/user/MrPrivateUser')->assertNotFound();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user