diff --git a/app/Livewire/Maven/DirectoryIndex.php b/app/Livewire/Maven/DirectoryIndex.php new file mode 100644 index 0000000..00efd9b --- /dev/null +++ b/app/Livewire/Maven/DirectoryIndex.php @@ -0,0 +1,37 @@ +isRoot = $path == ''; + $this->path = $path; + if (!Storage::directoryExists("maven$path")) { + return redirect(route('maven.download', $path)); + } + $fullPath = Storage::path("maven/$path"); + foreach (new DirectoryIterator($fullPath) as $file) { + if ($file->isDot()) continue; + $path = $file->getRealPath(); + $this->files[] = MavenFile::fromPath($path); + } + } + + public function render() + { + return view('livewire.maven.directory-index') + ->layout('components.layouts.public.maven-header') + ->title($this->isRoot ? "Index | Maven" : "Index of $this->path | Maven"); + } +} diff --git a/app/Maven/MavenFile.php b/app/Maven/MavenFile.php new file mode 100644 index 0000000..bf30d8f --- /dev/null +++ b/app/Maven/MavenFile.php @@ -0,0 +1,110 @@ +name = $name; + $this->modified = $modified; + $this->size = $size; + $this->hash = $hash; + $this->isDir = $isDir; + } + + public function getName(): string + { + return $this->name; + } + + public function getModified(): Carbon + { + return $this->modified; + } + + public function getSize(): int + { + return $this->size; + } + + public function getSizeFormatted(): string + { + if ($this->isDir) { + return '-'; + } + if ($this->size < 1024) { + return $this->size . 'B'; + } + if ($this->size < 1024 * 1024) { + return round($this->size / 1024) . 'KB'; + } + if ($this->size < 1024 * 1024 * 1024) { + return round($this->size / 1024 / 1024) . 'MB'; + } + return round($this->size / 1024 / 1024 / 1024) . 'GB'; + } + + public function getHash(): string + { + return $this->hash; + } + + public function isDir(): bool + { + return $this->isDir; + } + + public function getIcon(): string + { + if ($this->isDir) { + return 'folder'; + } + + $extension = pathinfo($this->name, PATHINFO_EXTENSION); + + switch ($extension) { + case 'md5'; + case 'sha1'; + case 'sha256'; + case 'sha512': + return 'document-check'; + case 'jar': + return 'command-line'; + } + + return 'document'; + } + + public function toLivewire() + { + return ['name' => $this->name, 'modified' => $this->modified, 'size' => $this->size, 'hash' => $this->hash]; + } + + public static function fromLivewire($value) + { + return new static($value['name'], $value['modified'], $value['size'], $value['hash'], $value['hash'] == '-'); + } + + public static function fromPath(string $path) + { + $isDir = is_dir($path); + return new static( + basename($path), + Carbon::createFromTimestamp(filemtime($path)), + filesize($path), + $isDir ? '-' : hash_file('sha256', $path), + $isDir + ); + } +} diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 0000000..e17e126 --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,11 @@ +first(); + return $user && Hash::check($password, $user->password); +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 7b162da..cbc3269 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,7 +11,9 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware) { - // + $middleware->validateCsrfTokens(except: [ + 'maven/*' + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/composer.json b/composer.json index 8ab4951..95ed0d1 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "livewire/volt": "^1.7.0" }, "require-dev": { + "barryvdh/laravel-ide-helper": "^3.6", "fakerphp/faker": "^1.23", "laravel/pail": "^1.2.2", "laravel/pint": "^1.18", @@ -30,7 +31,10 @@ "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" - } + }, + "files": [ + "app/helpers.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/composer.lock b/composer.lock index c1b6e13..0dd945f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1736795086e26511063792d7b78ce663", + "content-hash": "e241680189ba678523598973715e2d72", "packages": [ { "name": "brick/math", @@ -6290,6 +6290,300 @@ } ], "packages-dev": [ + { + "name": "barryvdh/laravel-ide-helper", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "b106f7ee85f263c4f103eca49e7bf3862c2e5e75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/b106f7ee85f263c4f103eca49e7bf3862c2e5e75", + "reference": "b106f7ee85f263c4f103eca49e7bf3862c2e5e75", + "shasum": "" + }, + "require": { + "barryvdh/reflection-docblock": "^2.4", + "composer/class-map-generator": "^1.0", + "ext-json": "*", + "illuminate/console": "^11.15 || ^12", + "illuminate/database": "^11.15 || ^12", + "illuminate/filesystem": "^11.15 || ^12", + "illuminate/support": "^11.15 || ^12", + "php": "^8.2" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "friendsofphp/php-cs-fixer": "^3", + "illuminate/config": "^11.15 || ^12", + "illuminate/view": "^11.15 || ^12", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^9.2 || ^10", + "phpunit/phpunit": "^10.5 || ^11.5.3", + "spatie/phpunit-snapshot-assertions": "^4 || ^5", + "vimeo/psalm": "^5.4", + "vlucas/phpdotenv": "^5" + }, + "suggest": { + "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\LaravelIdeHelper\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", + "keywords": [ + "autocomplete", + "codeintel", + "dev", + "helper", + "ide", + "laravel", + "netbeans", + "phpdoc", + "phpstorm", + "sublime" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-10T09:11:07+00:00" + }, + { + "name": "barryvdh/reflection-docblock", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/ReflectionDocBlock.git", + "reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/d103774cbe7e94ddee7e4870f97f727b43fe7201", + "reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.14|^9" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Barryvdh": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.0" + }, + "time": "2025-07-17T06:07:30+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/2373419b7709815ed323ebf18c3c72d03ff4a8a6", + "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-11-19T10:41:15+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "fakerphp/faker", "version": "v1.24.1", diff --git a/resources/css/app.css b/resources/css/app.css index 6bc66a3..37ef668 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -23,6 +23,7 @@ --color-zinc-900: var(--color-stone-900); --color-zinc-950: var(--color-stone-950); + --color-accent-light: var(--color-sky-400); --color-accent: var(--color-sky-600); --color-accent-content: var(--color-sky-600); --color-accent-foreground: var(--color-white); @@ -32,6 +33,7 @@ @layer theme { .dark { + --color-accent-light: var(--color-sky-300); --color-accent: var(--color-sky-600); --color-accent-content: var(--color-sky-400); --color-accent-foreground: var(--color-white); diff --git a/resources/js/app.js b/resources/js/app.js index e69de29..a42123b 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -0,0 +1,3 @@ +window.copyToClipboard = function (text) { + navigator.clipboard.writeText(text.replaceAll(' ', '%20')); +} diff --git a/resources/views/components/layouts/public/maven-header.blade.php b/resources/views/components/layouts/public/maven-header.blade.php new file mode 100644 index 0000000..4dbb6a5 --- /dev/null +++ b/resources/views/components/layouts/public/maven-header.blade.php @@ -0,0 +1,19 @@ + + + + @include('partials.head') + + + + + + + + Maven + + + {{ $slot }} + + @fluxScripts + + diff --git a/resources/views/livewire/attachments/attachment-manager.blade.php b/resources/views/livewire/attachments/attachment-manager.blade.php index e10d478..6caaa00 100644 --- a/resources/views/livewire/attachments/attachment-manager.blade.php +++ b/resources/views/livewire/attachments/attachment-manager.blade.php @@ -28,9 +28,5 @@ function attachmentSelected(filepath) { // Dispatch input event, as otherwise Livewire does not read the new value filenameField.dispatchEvent(new Event('input')); } - - function copyToClipboard(text) { - navigator.clipboard.writeText(text.replaceAll(' ', '%20')); - } diff --git a/resources/views/livewire/maven/directory-index.blade.php b/resources/views/livewire/maven/directory-index.blade.php new file mode 100644 index 0000000..1f19eac --- /dev/null +++ b/resources/views/livewire/maven/directory-index.blade.php @@ -0,0 +1,57 @@ + +
+
+ @if(!$isRoot) + Index of {{ $path }} + + Parent Directory + + @else + Root index + @endif + + + + + + + + + + + + + + + + + + + @foreach($files as $file) + + + + + + + + @endforeach + +
NameModifiedSizeHash
+ + + + {{ $file->getName() }} + + {{ $file->getModified()->format('j M Y \\a\\t H:i') }}{{ $file->getSizeFormatted() }} +
+

{{ strlen($file->getHash()) > 8 ? substr($file->getHash(), 0, 8) . '...' : $file->getHash() }}

+ @if($file->getHash() != '-') + + @endif +
+
+
+
+
diff --git a/routes/maven.php b/routes/maven.php new file mode 100644 index 0000000..24f6807 --- /dev/null +++ b/routes/maven.php @@ -0,0 +1,51 @@ +where('path', '.*')->name('maven.download'); + +// redirects to download route if path points to file +Route::get('maven{path?}', DirectoryIndex::class) + ->where('path', '.*') + ->name('maven.directory-index'); + +// route used by publishing plugin to upload maven files +// TODO: permissions for maven publishing +Route::put('maven/{path}', function ($path) { + if (!authenticate_http_user()) { + return response('Unauthorized', 401); + } + // Get the file from the put request + $file = fopen('php://input', 'r'); + if (!$file) { + return response('Error reading file', 500); + } + $path = Storage::path("maven/$path"); + // Make relevant directories if they do not already exist + if (!is_dir(dirname($path))) { + mkdir(substr($path, 0, strrpos($path, '/') + 1), 0755, true); + } + if (!upload_to_maven($file, $path)) { + fclose($file); + return response('Error uploading file', 500); + } + fclose($file); + return response('File successfully uploaded', 200); +})->where('path', '.*'); + diff --git a/routes/web.php b/routes/web.php index 0ca63b0..bb8fa1f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -49,3 +49,4 @@ }); require __DIR__.'/auth.php'; +require __DIR__.'/maven.php';