1
0
mirror of https://github.com/Kovah/LinkAce.git synced 2025-03-14 19:59:38 +01:00

Merge pull request #486 from Kovah/dev

Release
This commit is contained in:
Kevin Woblick 2022-06-10 16:44:38 +02:00 committed by GitHub
commit 68c2292520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1653 additions and 1880 deletions

View File

@ -3,8 +3,7 @@ _ide_*
/.git
/.idea
/.tmp
/storage/app/backup-temp
/storage/app/backups
/storage/app/backups/*
/storage/debugbar/*
/storage/framework/cache/*
/storage/framework/sessions/*

View File

@ -51,7 +51,11 @@ class ImportCommand extends Command
if ($this->option('skip-check')) {
$this->info('Skipping link check.');
} else {
if (config('mail.host') !== null) {
Artisan::queue('links:check');
} else {
$this->warn('Links are configured to be checked, but email is not configured!');
}
}
$this->info(trans('import.import_successfully', [

View File

@ -7,8 +7,7 @@ use Illuminate\Support\Facades\Log;
class WaybackMachine
{
/** @var string */
public static $baseUrl = 'https://web.archive.org';
public static string $baseUrl = 'https://web.archive.org';
/**
* Save an URL to the Wayback Machine
@ -25,12 +24,19 @@ class WaybackMachine
$archiveUrl = self::$baseUrl . '/save/' . $url;
$request = Http::timeout(10);
$request = Http::timeout(1);
if (config('html-meta.user_agents', false)) {
$agents = config('html-meta.user_agents');
$request->withHeaders(['User-Agent' => $agents[array_rand($agents)]]);
}
try {
$response = $request->head($archiveUrl);
} catch (\Exception $e) {
if (!str_contains($e->getMessage(), 'cURL error 28: Operation timed out')) {
Log::warning($archiveUrl . ': ' . $e->getMessage());
}
return false;
}
try {
$response->throw();

View File

@ -21,7 +21,7 @@ class LinkController extends Controller
*/
public function index(Request $request): JsonResponse
{
$links = Link::byUser(auth()->id())
$links = Link::byUser()
->orderBy(
$request->input('order_by', 'created_at'),
$request->input('order_dir', 'DESC')

View File

@ -21,7 +21,7 @@ class ListController extends Controller
*/
public function index(Request $request): JsonResponse
{
$lists = LinkList::byUser(auth()->id())
$lists = LinkList::byUser()
->orderBy(
$request->input('order_by', 'created_at'),
$request->input('order_dir', 'DESC')

View File

@ -21,7 +21,7 @@ class TagController extends Controller
*/
public function index(Request $request): JsonResponse
{
$tags = Tag::byUser(auth()->id())
$tags = Tag::byUser()
->orderBy(
$request->input('order_by', 'created_at'),
$request->input('order_dir', 'DESC')

View File

@ -18,22 +18,22 @@ class DashboardController extends Controller
*/
public function index(): View
{
$recentLinks = Link::byUser(auth()->user()->id)
$recentLinks = Link::byUser()
->latest()
->limit(5)
->get();
$recentTags = Tag::byUser(auth()->user()->id)
$recentTags = Tag::byUser()
->latest()
->limit(25)
->get();
$recentLists = LinkList::byUser(auth()->user()->id)
$recentLists = LinkList::byUser()
->latest()
->limit(15)
->get();
$brokenLinks = Link::byUser(auth()->user()->id)
$brokenLinks = Link::byUser()
->where('status', '>', 1)
->count();

View File

@ -23,19 +23,19 @@ class TrashController extends Controller
public function index(): View
{
$links = Link::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
$lists = LinkList::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
$tags = Tag::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
$notes = Note::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
return view('app.trash.index', [

View File

@ -27,7 +27,7 @@ class FetchController extends Controller
return response()->json([]);
}
$tags = Tag::byUser(auth()->user()->id)
$tags = Tag::byUser()
->where('name', 'like', '%' . escapeSearchQuery($query) . '%')
->orderBy('name')
->get();
@ -59,7 +59,7 @@ class FetchController extends Controller
return response()->json([]);
}
$tags = LinkList::byUser(auth()->user()->id)
$tags = LinkList::byUser()
->where('name', 'like', '%' . escapeSearchQuery($query) . '%')
->orderBy('name')
->get();
@ -92,7 +92,7 @@ class FetchController extends Controller
return response()->json([]);
}
$link = Link::byUser(auth()->user()->id)
$link = Link::byUser()
->where('url', trim($query))
->where('id', '!=', $request->input('ignore_id', 0))
->first();

View File

@ -29,7 +29,7 @@ class LinkController extends Controller
session()->put('links.index.orderBy', $orderBy);
session()->put('links.index.orderDir', $orderDir);
$links = Link::byUser(auth()->id())
$links = Link::byUser()
->with('tags')
->orderBy($orderBy, $orderDir)
->paginate(getPaginationLimit());

View File

@ -28,7 +28,7 @@ class ListController extends Controller
session()->put('lists.index.orderBy', $orderBy);
session()->put('lists.index.orderDir', $orderDir);
$lists = LinkList::byUser(auth()->id())
$lists = LinkList::byUser()
->withCount('links')
->orderBy($orderBy, $orderDir);
@ -88,7 +88,7 @@ class ListController extends Controller
public function show(Request $request, LinkList $list): View
{
$links = $list->links()
->byUser(auth()->id())
->byUser()
->orderBy(
$request->input('orderBy', 'created_at'),
$request->input('orderDir', 'desc')

View File

@ -28,7 +28,7 @@ class TagController extends Controller
session()->put('tags.index.orderBy', $orderBy);
session()->put('tags.index.orderDir', $orderDir);
$tags = Tag::byUser(auth()->id())
$tags = Tag::byUser()
->withCount('links')
->orderBy($orderBy, $orderDir);
@ -88,7 +88,7 @@ class TagController extends Controller
*/
public function show(Request $request, Tag $tag): View
{
$links = $tag->links()->byUser(auth()->id())
$links = $tag->links()->byUser()
->orderBy(
$request->input('orderBy', 'created_at'),
$request->input('orderDir', 'desc')

View File

@ -38,7 +38,7 @@ use Venturecraft\Revisionable\RevisionableTrait;
* @property Collection|Revision[] $revisionHistory
* @property Collection|Tag[] $tags
* @property User $user
* @method static Builder|Link byUser($user_id)
* @method static Builder|Link byUser($user_id = null)
* @method static Builder|Link privateOnly()
* @method static Builder|Link publicOnly()
* @method static MorphMany revisionHistory()
@ -49,8 +49,18 @@ class Link extends Model
use RevisionableTrait;
use HasFactory;
public const STATUS_OK = 1;
public const STATUS_MOVED = 2;
public const STATUS_BROKEN = 3;
public const DISPLAY_CARDS = 1;
public const DISPLAY_CARDS_DETAILED = 3;
public const DISPLAY_LIST_SIMPLE = 2;
public const DISPLAY_LIST_DETAILED = 0;
public const REV_TAGS_NAME = 'revtags';
public const REV_LISTS_NAME = 'revlists';
public $table = 'links';
// Revisions settings
public $fillable = [
'user_id',
'url',
@ -62,31 +72,16 @@ class Link extends Model
'check_disabled',
'thumbnail',
];
protected $casts = [
'user_id' => 'integer',
'is_private' => 'boolean',
'status' => 'integer',
'check_disabled' => 'boolean',
];
public const STATUS_OK = 1;
public const STATUS_MOVED = 2;
public const STATUS_BROKEN = 3;
public const DISPLAY_CARDS = 1;
public const DISPLAY_CARDS_DETAILED = 3;
public const DISPLAY_LIST_SIMPLE = 2;
public const DISPLAY_LIST_DETAILED = 0;
// Revisions settings
protected $revisionCleanup = true;
protected $historyLimit = 50;
protected $dontKeepRevisionOf = ['icon'];
public const REV_TAGS_NAME = 'revtags';
public const REV_LISTS_NAME = 'revlists';
/*
| ========================================================================
@ -97,12 +92,15 @@ class Link extends Model
* Scope for the user relation
*
* @param Builder $query
* @param int $userId
* @param int|null $user_id
* @return Builder
*/
public function scopeByUser(Builder $query, int $userId): Builder
public function scopeByUser(Builder $query, int $user_id = null): Builder
{
return $query->where('user_id', $userId);
if (is_null($user_id) && auth()->check()) {
$user_id = auth()->id();
}
return $query->where('user_id', $user_id);
}
/**
@ -187,19 +185,6 @@ class Link extends Model
return Str::markdown($this->description, ['html_input' => 'escape']);
}
/**
* Get the URL shortened to max 50 characters and with https:// removed.
* Other protocols like magnet://, ftp:// and so on will be kept to make
* those protocols more obvious for the user.
*
* @param int $maxLength
* @return string
*/
public function shortUrl(int $maxLength = 50): string
{
return preg_replace('/http(s)?:\/\//', '', Str::limit(trim($this->url, '/'), $maxLength));
}
/**
* Get the title shortened to max 50 characters
*
@ -222,6 +207,19 @@ class Link extends Model
return $urlDetails['host'] ?? $this->shortUrl(20);
}
/**
* Get the URL shortened to max 50 characters and with https:// removed.
* Other protocols like magnet://, ftp:// and so on will be kept to make
* those protocols more obvious for the user.
*
* @param int $maxLength
* @return string
*/
public function shortUrl(int $maxLength = 50): string
{
return preg_replace('/http(s)?:\/\//', '', Str::limit(trim($this->url, '/'), $maxLength));
}
public function tagsForInput(): ?string
{
$tags = $this->tags;

View File

@ -27,7 +27,7 @@ use Illuminate\Support\Str;
* @property string|null $deleted_at
* @property-read Collection|Link[] $links
* @property-read User $user
* @method static Builder|Tag byUser($user_id)
* @method static Builder|Tag byUser($user_id = null)
* @method static Builder|Tag privateOnly()
* @method static Builder|Tag publicOnly()
*/
@ -50,6 +50,34 @@ class LinkList extends Model
'is_private' => 'boolean',
];
/**
* Get a collection of all lists for the current user, ordered by name
*
* @return Builder[]|Collection
*/
public static function getAllForCurrentUser()
{
return self::byUser()
->orderBy('name')
->get();
}
/*
| ========================================================================
| SCOPES
*/
/**
* @param string|int $listId
* @param string $newName
* @return bool
*/
public static function nameHasChanged($listId, string $newName): bool
{
$oldName = self::find($listId)->name ?? null;
return $oldName !== $newName;
}
/**
* Add the OrderNameScope to the Tag model
*/
@ -60,23 +88,26 @@ class LinkList extends Model
static::addGlobalScope(new OrderNameScope());
}
/*
| ========================================================================
| SCOPES
*/
/**
* Scope for the user relation
*
* @param Builder $query
* @param int $user_id
* @param int|null $user_id
* @return Builder
*/
public function scopeByUser(Builder $query, int $user_id): Builder
public function scopeByUser(Builder $query, int $user_id = null): Builder
{
if (is_null($user_id) && auth()->check()) {
$user_id = auth()->id();
}
return $query->where('user_id', $user_id);
}
/*
| ========================================================================
| RELATIONSHIPS
*/
/**
* Scope for selecting private lists only
*
@ -101,7 +132,7 @@ class LinkList extends Model
/*
| ========================================================================
| RELATIONSHIPS
| METHODS
*/
/**
@ -120,11 +151,6 @@ class LinkList extends Model
return $this->belongsToMany('App\Models\Link', 'link_lists', 'list_id', 'link_id');
}
/*
| ========================================================================
| METHODS
*/
/**
* Get the formatted description of the list
*
@ -142,27 +168,4 @@ class LinkList extends Model
return Str::markdown($this->description, ['html_input' => 'escape']);
}
/**
* Get a collection of all lists for the current user, ordered by name
*
* @return Builder[]|Collection
*/
public static function getAllForCurrentUser()
{
return self::byUser(auth()->id())
->orderBy('name')
->get();
}
/**
* @param string|int $listId
* @param string $newName
* @return bool
*/
public static function nameHasChanged($listId, string $newName): bool
{
$oldName = self::find($listId)->name ?? null;
return $oldName !== $newName;
}
}

View File

@ -24,7 +24,7 @@ use Illuminate\Support\Str;
* @property string|null $deleted_at
* @property-read Link $link
* @property-read User $user
* @method static Builder|Link byUser($user_id)
* @method static Builder|Link byUser($user_id = null)
*/
class Note extends Model
{
@ -55,11 +55,14 @@ class Note extends Model
* Scope for the user relation
*
* @param Builder $query
* @param int $user_id
* @param int|null $user_id
* @return Builder
*/
public function scopeByUser(Builder $query, int $user_id): Builder
public function scopeByUser(Builder $query, int $user_id = null): Builder
{
if (is_null($user_id) && auth()->check()) {
$user_id = auth()->id();
}
return $query->where('user_id', $user_id);
}

View File

@ -13,7 +13,7 @@ use Illuminate\Database\Eloquent\Model;
* @property int $user_id
* @property string $key
* @property mixed $value
* @method static Builder|Setting byUser($user_id)
* @method static Builder|Setting byUser($user_id = null)
* @method static Builder|Setting systemOnly()
*/
class Setting extends Model
@ -41,11 +41,14 @@ class Setting extends Model
* Scope for the user relation
*
* @param Builder $query
* @param int $user_id
* @param int|null $user_id
* @return Builder
*/
public function scopeByUser(Builder $query, int $user_id): Builder
public function scopeByUser(Builder $query, int $user_id = null): Builder
{
if (is_null($user_id) && auth()->check()) {
$user_id = auth()->id();
}
return $query->where('user_id', $user_id);
}

View File

@ -25,7 +25,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property string|null $deleted_at
* @property-read Collection|Link[] $links
* @property-read User $user
* @method static Builder|Tag byUser(int $user_id)
* @method static Builder|Tag byUser(int $user_id = null)
* @method static Builder|Tag publicOnly()
* @method static Builder|Tag privateOnly()
*/
@ -47,6 +47,22 @@ class Tag extends Model
'is_private' => 'boolean',
];
/**
* @param string|int $tagId
* @param string $newName
* @return bool
*/
public static function nameHasChanged($tagId, string $newName): bool
{
$oldName = self::find($tagId)->name ?? null;
return $oldName !== $newName;
}
/*
| ========================================================================
| SCOPES
*/
/**
* Add the OrderNameScope to the Tag model
*/
@ -57,20 +73,18 @@ class Tag extends Model
static::addGlobalScope(new OrderNameScope());
}
/*
| ========================================================================
| SCOPES
*/
/**
* Scope for the user relation
*
* @param Builder $query
* @param int $user_id
* @param int|null $user_id
* @return Builder
*/
public function scopeByUser(Builder $query, int $user_id): Builder
public function scopeByUser(Builder $query, int $user_id = null): Builder
{
if (is_null($user_id) && auth()->check()) {
$user_id = auth()->id();
}
return $query->where('user_id', $user_id);
}
@ -85,6 +99,11 @@ class Tag extends Model
return $query->where('is_private', true);
}
/*
| ========================================================================
| RELATIONSHIPS
*/
/**
* Scope for selecting public tags only
*
@ -96,11 +115,6 @@ class Tag extends Model
return $query->where('is_private', false);
}
/*
| ========================================================================
| RELATIONSHIPS
*/
/**
* @return BelongsTo
*/
@ -109,6 +123,11 @@ class Tag extends Model
return $this->belongsTo('App\Models\User', 'user_id');
}
/*
| ========================================================================
| METHODS
*/
/**
* @return BelongsToMany
*/
@ -116,20 +135,4 @@ class Tag extends Model
{
return $this->belongsToMany('App\Models\Link', 'link_tags', 'tag_id', 'link_id');
}
/*
| ========================================================================
| METHODS
*/
/**
* @param string|int $tagId
* @param string $newName
* @return bool
*/
public static function nameHasChanged($tagId, string $newName): bool
{
$oldName = self::find($tagId)->name ?? null;
return $oldName !== $newName;
}
}

View File

@ -23,22 +23,22 @@ class TrashRepository
switch ($model) {
case 'links':
$entries = Link::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
break;
case 'lists':
$entries = LinkList::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
break;
case 'tags':
$entries = Tag::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
break;
case 'notes':
$entries = Note::onlyTrashed()
->byUser(auth()->id())
->byUser()
->get();
break;
}

673
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -73,6 +73,12 @@ return [
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'scheme' => env('AWS_SCHEME', 'https'),
'bucket_endpoint' => env('AWS_BUCKET_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_PATH_STYLE_ENDPOINT'),
'use_accelerate_endpoint' => env('AWS_ACCELERATE_ENDPOINT'),
'use_fips_endpoint' => env('AWS_FIPS_ENDPOINT'),
'use_dual_stack_endpoint' => env('AWS_DUAL_STACK_ENDPOINT'),
],
],

View File

@ -29,7 +29,7 @@ return [
|
*/
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'host' => env('MAIL_HOST'),
/*
|--------------------------------------------------------------------------

2380
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "linkace",
"version": "1.10.1",
"version": "1.10.2",
"description": "A small, selfhosted bookmark manager with advanced features, built with Laravel and Docker",
"homepage": "https://github.com/Kovah/LinkAce",
"repository": {

View File

@ -26,6 +26,10 @@ export default class TagsSelect {
delimiter: ',',
persist: false,
create: this.selectAllowsCreation(),
onItemAdd:function(){
this.setTextboxValue('');
this.refreshOptions();
},
load: (query, callback) => {
this.handleTagLoading(query, callback);
}

View File

@ -1,86 +0,0 @@
# DOCKERFILE RELEASE
# ================================
# PHP Dependency Setup
FROM composer AS builder
WORKDIR /app
# Make needed parts of the app available in the container
COPY ./app /app/app
COPY ./bootstrap /app/bootstrap
COPY ./config /app/config
COPY ./database /app/database
COPY ./resources /app
COPY ./routes /app/routes
COPY ./tests /app/tests
COPY ./artisan /app
COPY ./composer.json /app
COPY ./composer.lock /app
COPY ./README.md /app
COPY ./.env.example /app/.env
# Install dependencies using Composer
RUN composer install -n --prefer-dist --no-dev
# ================================
# Compile all assets
FROM node:14.15.4 AS npm_builder
WORKDIR /srv
# Copy package.json and Gruntfile
COPY ./package.json ./
COPY ./package-lock.json ./
COPY ./webpack.mix.js ./
COPY ./resources/assets ./resources/assets
RUN npm install
RUN npm run production
# ================================
# Prepare the final image including nginx
FROM webdevops/php-nginx:8.0-alpine
WORKDIR /app
# Copy the app into the container
COPY ./app /app/app
COPY ./bootstrap /app/bootstrap
COPY ./config /app/config
COPY ./database /app/database
COPY ./public /app/public
COPY ./resources /app/resources
COPY ./routes /app/routes
COPY ./storage /app/storage
COPY ./tests /app/tests
COPY ./artisan /app
COPY ./composer.json /app
COPY ./composer.lock /app
COPY ./README.md /app
COPY ./package.json /app
COPY ./server.php /app
COPY ./.env.example /app/.env
# Copy the PHP config files
COPY ./resources/docker/php/php.ini /opt/docker/etc/php/php.ini
# Copy files from the composer build
COPY --from=builder /app/vendor /app/vendor
COPY --from=builder /app/bootstrap/cache /app/bootstrap/cache
# Install MySQL Dump for automated backups
RUN apk add --no-cache mariadb-client
# Publish package resources
RUN php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
# Copy files from the theme build
COPY --from=npm_builder /srv/public/assets/dist/js /app/public/assets/dist/js
COPY --from=npm_builder /srv/public/assets/dist/css /app/public/assets/dist/css
COPY --from=npm_builder /srv/public/mix-manifest.json /app/public/mix-manifest.json
# Set correct permissions for the storage directory
RUN chown -R application:application /app
RUN chmod -R 0777 /app/storage
ENV WEB_DOCUMENT_ROOT /app/public

View File

@ -1,83 +0,0 @@
# DOCKERFILE RELEASE
# ================================
# PHP Dependency Setup
FROM composer AS builder
WORKDIR /app
# Make needed parts of the app available in the container
COPY ./app /app/app
COPY ./bootstrap /app/bootstrap
COPY ./config /app/config
COPY ./database /app/database
COPY ./resources /app
COPY ./routes /app/routes
COPY ./tests /app/tests
COPY ./artisan /app
COPY ./composer.json /app
COPY ./composer.lock /app
COPY ./README.md /app
COPY ./.env.example /app/.env
# Install dependencies using Composer
RUN composer install -n --prefer-dist --no-dev
# ================================
# Compile all assets
FROM node:14.15.4 AS npm_builder
WORKDIR /srv
# Copy package.json and Gruntfile
COPY ./package.json ./
COPY ./package-lock.json ./
COPY ./webpack.mix.js ./
COPY ./resources/assets ./resources/assets
RUN npm install
RUN npm run production
# ================================
# Prepare the final image
FROM bitnami/php-fpm:8.0-prod
WORKDIR /app
# Copy the app into the container
COPY ./app /app/app
COPY ./bootstrap /app/bootstrap
COPY ./config /app/config
COPY ./database /app/database
COPY ./public /app/public
COPY ./resources /app/resources
COPY ./routes /app/routes
COPY ./storage /app/storage
COPY ./tests /app/tests
COPY ./artisan /app
COPY ./composer.json /app
COPY ./composer.lock /app
COPY ./README.md /app
COPY ./package.json /app
COPY ./server.php /app
COPY ./.env.example /app/.env
# Copy the PHP config files
COPY ./resources/docker/php/php.ini /opt/bitnami/php/etc/conf.d/php.ini
# Install MySQL Dump for automated backups
RUN install_packages mariadb-client
# Copy files from the composer build
COPY --from=builder /app/vendor /app/vendor
COPY --from=builder /app/bootstrap/cache /app/bootstrap/cache
# Publish package resources
RUN php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
# Copy files from the theme build
COPY --from=npm_builder /srv/public/assets/dist/js /app/public/assets/dist/js
COPY --from=npm_builder /srv/public/assets/dist/css /app/public/assets/dist/css
COPY --from=npm_builder /srv/public/mix-manifest.json /app/public/mix-manifest.json
# Set correct permissions for the storage directory
RUN chmod -R 0777 /app/storage