From 63c64545f012faff29db7cf8d514e812e061592c Mon Sep 17 00:00:00 2001
From: Peter Wolkiewicz <peter@boomstraat.de>
Date: Nov 30 2021 15:22:28 +0000
Subject: [PATCH 1/2] Readme updated


---

diff --git a/README.md b/README.md
index e69de29..d7602c2 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,12 @@
+<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400"></a></p>
+
+<p align="center">
+<a href="https://travis-ci.org/laravel/framework"><img src="https://travis-ci.org/laravel/framework.svg" alt="Build Status"></a>
+<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
+<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
+<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
+</p>
+
+## License
+
+The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

From 7d8a3dc7f4ce4344fd8643d35204b420c5aaccef Mon Sep 17 00:00:00 2001
From: Peter Wolkiewicz <peter@boomstraat.de>
Date: Dec 03 2021 07:02:17 +0000
Subject: [PATCH 2/2] Base ACL added


---

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3f2f395
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+/node_modules
+/public/hot
+/public/storage
+/storage/*.key
+/vendor
+/.idea
+/.vagrant
+.php_cs.cache
+.phpunit.result.cache
+.DS_Store
+.env
+composer.lock
+Homestead.json
+Homestead.yaml
+npm-debug.log
+yarn-error.log
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..8e2a4c2
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,28 @@
+{
+    "name": "orendo/larabase",
+    "description": "Laravel 8 Base Provider",
+    "type": "library",
+    "license": "MIT",
+    "autoload": {
+        "psr-4": {
+            "Orendo\\Larabase\\": "src/"
+        }
+    },
+    "authors": [
+        {
+            "name": "Peter Wolkiewicz",
+            "email": "peter.wolkiewicz@orendo.de"
+        }
+    ],
+    "require": {
+        "inertiajs/inertia-laravel": "^0.4.5",
+        "tightenco/ziggy": "^1.4"
+    },
+    "extra": {
+        "laravel": {
+            "providers": [
+                "Orendo\\Larabase\\LarabaseServiceProvider"
+            ]
+        }
+    }
+}
diff --git a/src/Controllers/DashboardController.php b/src/Controllers/DashboardController.php
new file mode 100644
index 0000000..ccc54b4
--- /dev/null
+++ b/src/Controllers/DashboardController.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Orendo\Larabase\Controllers;
+
+use App\Http\Controllers\Controller;
+
+class DashboardController extends Controller
+{
+    public function index()
+    {
+        return 'index';
+    }
+}
diff --git a/src/Controllers/MediaController.php b/src/Controllers/MediaController.php
new file mode 100644
index 0000000..579b47c
--- /dev/null
+++ b/src/Controllers/MediaController.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Orendo\Larabase\Controllers;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use Inertia\Inertia;
+use Orendo\Larabase\Models\Media;
+
+class MediaController extends Controller
+{
+    public function index()
+    {
+        $media = Media::all();
+
+        return Inertia::render('Media/Index', [
+            'media' => $media
+        ]);
+    }
+
+    public function store(Request $request)
+    {
+        $this->validate($request, [
+            'name' => 'required|alpha'
+        ]);
+
+        $media = new Media();
+        $media->type = 'file';
+        $media->name = $request->name;
+        $media->title = $request->name;
+        $media->path = $request->name;
+        $media->save();
+
+        return redirect()->route('media.index');
+    }
+}
diff --git a/src/LarabaseServiceProvider.php b/src/LarabaseServiceProvider.php
new file mode 100644
index 0000000..b57e16b
--- /dev/null
+++ b/src/LarabaseServiceProvider.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace Orendo\Larabase;
+
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Routing\Router;
+use Illuminate\Support\Facades\Route;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\ServiceProvider;
+use Orendo\Larabase\Middleware\HandleInertiaRequests;
+use Orendo\Larabase\Middleware\RoleMiddleware;
+use Orendo\Larabase\Models\Permission;
+
+class LarabaseServiceProvider extends ServiceProvider
+{
+    /**
+     * register
+     *
+     *
+     * @return void
+     */
+    public function register(): Void
+    {
+        $this->registerFilesystem();
+    }
+
+    /**
+     * boot
+     *
+     *
+     * @return Void
+     */
+    public function boot(): Void
+    {
+        $this->registerConfig();
+
+        $this->registerRoutes();
+
+        $this->registerMiddleware();
+
+        $this->registerMigrations();
+
+        $this->registerArtisanCommands();
+
+        $this->publishAssets();
+
+        if ($this->checkTables('permissions')) {
+            $this->registerPermissions();
+        }
+    }
+
+    /**
+     * registerRoutes
+     *
+     *
+     * @return Void
+     */
+    private function registerRoutes(): Void
+    {
+        Route::group($this->routeConfiguration(), function () {
+            $this->loadRoutesFrom(__DIR__.'/routes/web.php');
+        });
+    }
+
+    /**
+     * setPermissions
+     *
+     *
+     * @return Void
+     */
+    private function registerPermissions(): Void
+    {
+        foreach ($this->getPermissions() as $permission) {
+            Gate::define($permission->name, function ($user) use ($permission) {
+                return $user->hasRole($permission->roles);
+            });
+        }
+    }
+
+    /**
+     * registerMiddleware
+     *
+     *
+     * @return Void
+     */
+    private function registerMiddleware(): Void
+    {
+        $router = $this->app->make(Router::class);
+        $router->aliasMiddleware('role', RoleMiddleware::class);
+        $router->pushMiddlewareToGroup('web', HandleInertiaRequests::class);
+    }
+
+    /**
+     * setConfigOptions
+     *
+     *
+     * @return void
+     */
+    private function registerConfig(): void
+    {
+        // Set Logging to Daily Channel
+        config(['logging.default' => 'daily']);
+
+        // Set Hashing Driver
+        config(['hashing.driver' => 'bcrypt']);
+
+        // Set Custom User Model`
+        config(['auth.providers.users.model' => \Orendo\Larabase\Models\User::class]);
+
+        config(['filesystems.links' => [
+            public_path('media') => storage_path('app/public/media'),
+        ]]);
+    }
+
+    /**
+     * registerMigrations
+     *
+     *
+     * @return Void
+     */
+    public function registerMigrations(): Void
+    {
+        $this->loadMigrationsFrom(__DIR__.'/migrations');
+    }
+
+    /**
+     * registerCommands
+     *
+     *
+     * @return Void
+     */
+    public function registerArtisanCommands(): Void
+    {
+        if ($this->app->runningInConsole()) {
+            $this->commands([
+                /* SetupAppCommand::class, */
+                /* SetupUserCommand::class, */
+                /* PermissionCommand::class, */
+            ]);
+        }
+    }
+
+    /**
+     * registerFilesystem
+     *
+     *
+     * @return Void
+     */
+    public function registerFilesystem(): Void
+    {
+        $this->mergeConfigFrom(
+            __DIR__.'/config/filesystem.php',
+            'filesystems.disks'
+        );
+    }
+
+    /**
+     * publishAssets
+     *
+     *
+     * @return Void
+     */
+    public function publishAssets(): Void
+    {
+        $this->publishes([
+            __DIR__ . '/assets/css' => resource_path('css/'),
+            __DIR__ . '/assets/js' => resource_path('js/'),
+            __DIR__ . '/assets/tailwind.config.js' => base_path('tailwind.config.js'),
+            __DIR__ . '/assets/webpack.mix.js' => base_path('webpack.mix.js'),
+            __DIR__ . '/assets/package.json' => base_path('package.json'),
+            __DIR__ . '/config/acl.php' => config_path('acl.php'),
+            __DIR__ . '/config/menu.php' => config_path('menu.php'),
+        ]);
+    }
+
+    /**
+     * routeConfiguration
+     *
+     *
+     * @return Array
+     */
+    private function routeConfiguration(): array
+    {
+        return [
+            'middleware' => 'web'
+        ];
+    }
+
+    /**
+     * getPermissions
+     *
+     *
+     * @return Collection
+     */
+    private function getPermissions(): Collection
+    {
+        return Permission::all();
+    }
+
+    /**
+     * checkTables
+     *
+     * @param string $table
+     */
+    private function checkTables(string $table): bool
+    {
+        if (!Schema::hasTable($table)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/Middleware/HandleInertiaRequests.php b/src/Middleware/HandleInertiaRequests.php
new file mode 100644
index 0000000..bb110c2
--- /dev/null
+++ b/src/Middleware/HandleInertiaRequests.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Orendo\Larabase\Middleware;
+
+use Illuminate\Http\Request;
+use Inertia\Middleware;
+
+class HandleInertiaRequests extends Middleware
+{
+    /**
+     * The root template that's loaded on the first page visit.
+     *
+     * @see https://inertiajs.com/server-side-setup#root-template
+     * @var string
+     */
+    protected $rootView = 'app';
+
+    /**
+     * Determines the current asset version.
+     *
+     * @see https://inertiajs.com/asset-versioning
+     * @param  \Illuminate\Http\Request  $request
+     * @return string|null
+     */
+    public function version(Request $request)
+    {
+        return parent::version($request);
+    }
+
+    /**
+     * Defines the props that are shared by default.
+     *
+     * @see https://inertiajs.com/shared-data
+     * @param  \Illuminate\Http\Request  $request
+     * @return array
+     */
+    public function share(Request $request)
+    {
+        return array_merge(parent::share($request), [
+            'menu' => config('menu')
+        ]);
+    }
+}
diff --git a/src/Middleware/RoleMiddleware.php b/src/Middleware/RoleMiddleware.php
new file mode 100644
index 0000000..15d0f02
--- /dev/null
+++ b/src/Middleware/RoleMiddleware.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Orendo\Larabase\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+
+class RoleMiddleware
+{
+    public function handle(Request $request, Closure $next, $role)
+    {
+        $user = $request->user();
+
+        if ($user->hasRole($role)) {
+            return $next($request);
+        }
+
+        return abort('403', 'This action is unauthorized.');
+    }
+}
diff --git a/src/Models/Media.php b/src/Models/Media.php
new file mode 100644
index 0000000..cc17fc3
--- /dev/null
+++ b/src/Models/Media.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Orendo\Larabase\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Media extends Model
+{
+    use HasFactory;
+}
diff --git a/src/Models/Permission.php b/src/Models/Permission.php
new file mode 100644
index 0000000..5cff983
--- /dev/null
+++ b/src/Models/Permission.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Orendo\Larabase\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+
+class Permission extends Model
+{
+    use HasFactory;
+
+    /**
+     * fillable
+     *
+     * @var array
+     */
+    protected $fillable = [
+        'name', 'label'
+    ];
+
+    /**
+     * roles
+     *
+     *
+     * @return BelongsToMany
+     */
+    public function roles(): BelongsToMany
+    {
+        return $this->belongsToMany(Role::class);
+    }
+}
diff --git a/src/Models/Role.php b/src/Models/Role.php
new file mode 100644
index 0000000..4d16b46
--- /dev/null
+++ b/src/Models/Role.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Orendo\Larabase\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+
+class Role extends Model
+{
+    use HasFactory;
+
+    /**
+     * fillable
+     *
+     * @var mixed
+     */
+    protected $fillable = [
+        'name', 'label'
+    ];
+
+    /**
+     * permissions
+     *
+     *
+     * @return BelongsToMany
+     */
+    public function permissions(): BelongsToMany
+    {
+        return $this->belongsToMany(Permission::class);
+    }
+
+    /**
+     * hasPermission
+     *
+     * @param string $permission
+     *
+     * @return bool
+     */
+    public function hasPermission(string $permission): Bool
+    {
+        return $this->permissions->contains('name', $permission) ?? true;
+    }
+
+    /**
+     * givePermissionTo
+     *
+     * @param string $permission
+     *
+     * @return Model
+     */
+    public function givePermissionTo(string $permission): Model
+    {
+        return $this->permissions()->save(
+            Permission::whereName($permission)->firstOrFail()
+        );
+    }
+
+    /**
+     * removePermissionFrom
+     *
+     * @param string $permission
+     *
+     * @return int
+     */
+    public function removePermissionFrom(string $permission): Int
+    {
+        return $this->permissions()->detach(
+            Permission::whereName($permission)->firstOrFail()
+        );
+    }
+}
diff --git a/src/Models/User.php b/src/Models/User.php
new file mode 100644
index 0000000..b86e9a8
--- /dev/null
+++ b/src/Models/User.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Orendo\Larabase\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+
+class User extends Model
+{
+    use HasFactory;
+    use Notifiable;
+
+    protected $dates = [
+        'last_login'
+    ];
+
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [
+        'name',
+        'email',
+        'password',
+    ];
+
+    /**
+     * The attributes that should be hidden for arrays.
+     *
+     * @var array
+     */
+    protected $hidden = [
+        'password',
+        'remember_token',
+    ];
+
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = [
+        'email_verified_at' => 'datetime',
+    ];
+
+    /**
+     * roles
+     *
+     *
+     * @return BelongsToMany
+     */
+    public function roles(): BelongsToMany
+    {
+        return $this->belongsToMany(Role::class);
+    }
+
+    /**
+     * isActive
+     *
+     *
+     * @return bool
+     */
+    public function isActive(): Bool
+    {
+        if ($this->is_active) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * hasRole
+     *
+     * @param mixed $role
+     *
+     * @return bool
+     */
+    public function hasRole($role): Bool
+    {
+        if (is_string($role)) {
+            return $this->roles->contains('name', $role);
+        }
+        foreach ($role as $value) {
+            if ($this->hasRole($value['name'])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * giveRoleTo
+     *
+     * @param string $role
+     *
+     * @return Role
+     */
+    public function giveRoleTo(string $role): Role
+    {
+        return $this->roles()->save(
+            Role::whereName($role)->firstOrFail()
+        );
+    }
+
+    /**
+     * removeRoleFrom
+     *
+     * @param string $role
+     *
+     * @return int
+     */
+    public function removeRoleFrom(string $role): Int
+    {
+        return $this->roles()->detach(
+            Role::whereName($role)->firstOrFail()
+        );
+    }
+}
diff --git a/src/assets/css/app.css b/src/assets/css/app.css
new file mode 100644
index 0000000..b97d40a
--- /dev/null
+++ b/src/assets/css/app.css
@@ -0,0 +1,41 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+
+.bs-notify {
+    display: block;
+    box-sizing: border-box;
+    text-align: left;
+    font-size: 16px;
+    padding: 10px;
+    margin: 0 5px 5px;
+
+    color: white;
+    background: #44A4FC;
+    border-left: 5px solid #187FE7;
+}
+
+.bs-notify.warn {
+    background: #ffb648;
+    border-left-color: #f48a06;
+}
+
+.bs-notify.error {
+    background: #E54D42;
+    border-left-color: #B82E24;
+}
+
+.bs-notify.success {
+    background: #68CD86;
+    border-left-color: #42A85F;
+}
+
+.vn-fade-enter-active, .vn-fade-leave-active, .vn-fade-move  {
+    transition: all .5s;
+}
+
+.vn-fade-enter, .vn-fade-leave-to {
+    opacity: 0;
+}
+
diff --git a/src/assets/js/app.js b/src/assets/js/app.js
new file mode 100644
index 0000000..82ca0a4
--- /dev/null
+++ b/src/assets/js/app.js
@@ -0,0 +1,32 @@
+require('./bootstrap');
+
+import Vue from 'vue';
+import { createInertiaApp } from '@inertiajs/inertia-vue';
+import { Link } from '@inertiajs/inertia-vue';
+import Notifications from 'vue-notification';
+import { InertiaProgress } from '@inertiajs/progress';
+import { ZiggyVue } from 'ziggy';
+import { Ziggy } from 'ziggy';
+
+Vue.use(ZiggyVue, Ziggy);
+Vue.use(Notifications);
+
+const files = require.context('./components/global/', true, /\.vue$/i)
+files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
+
+Vue.component('Link', Link);
+
+InertiaProgress.init({
+    delay: 250,
+    color: '#003E6E',
+    showSpinner: true,
+});
+
+createInertiaApp({
+  resolve: name => require(`./components/${name}`),
+  setup({ el, App, props }) {
+    new Vue({
+      render: h => h(App, props),
+    }).$mount(el)
+  },
+})
diff --git a/src/assets/js/bootstrap.js b/src/assets/js/bootstrap.js
new file mode 100644
index 0000000..6922577
--- /dev/null
+++ b/src/assets/js/bootstrap.js
@@ -0,0 +1,28 @@
+window._ = require('lodash');
+
+/**
+ * We'll load the axios HTTP library which allows us to easily issue requests
+ * to our Laravel back-end. This library automatically handles sending the
+ * CSRF token as a header based on the value of the "XSRF" token cookie.
+ */
+
+window.axios = require('axios');
+
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+
+/**
+ * Echo exposes an expressive API for subscribing to channels and listening
+ * for events that are broadcast by Laravel. Echo and event broadcasting
+ * allows your team to easily build robust real-time web applications.
+ */
+
+// import Echo from 'laravel-echo';
+
+// window.Pusher = require('pusher-js');
+
+// window.Echo = new Echo({
+//     broadcaster: 'pusher',
+//     key: process.env.MIX_PUSHER_APP_KEY,
+//     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
+//     forceTLS: true
+// });
diff --git a/src/assets/js/components/Dashboard/Dashboard.vue b/src/assets/js/components/Dashboard/Dashboard.vue
new file mode 100644
index 0000000..38cac38
--- /dev/null
+++ b/src/assets/js/components/Dashboard/Dashboard.vue
@@ -0,0 +1,22 @@
+<template>
+    <div>
+        Dashboard
+    </div>
+</template>
+
+<script>
+import Layout from './layouts/AdminLayout.vue';
+
+export default {
+    layout: (h, page) => h(Layout, [page]),
+    data () {
+        return {
+            //
+        }
+    }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/assets/js/components/Dashboard/layouts/AdminLayout.vue b/src/assets/js/components/Dashboard/layouts/AdminLayout.vue
new file mode 100644
index 0000000..6072155
--- /dev/null
+++ b/src/assets/js/components/Dashboard/layouts/AdminLayout.vue
@@ -0,0 +1,16 @@
+<template>
+<div class="flex flex-col h-full min-h-full bg-gray-100 dark:bg-blue-700 dark:text-white">
+    <header class="sticky top-0 z-50 flex flex-col">
+        <admin-nav></admin-nav>
+        <notifications classes="bs-notify" />
+    </header>
+    <main class="flex flex-1">
+        <admin-side-bar></admin-side-bar><slot />
+    </main>
+    <footer class="sticky bottom-0 z-50 flex flex-col">
+        <div class="py-2 pr-3 text-right bg-white dark:text-white dark:bg-blue-800">
+            Footer
+        </div>
+    </footer>
+</div>
+</template>
diff --git a/src/assets/js/components/Error.vue b/src/assets/js/components/Error.vue
new file mode 100644
index 0000000..fd8d965
--- /dev/null
+++ b/src/assets/js/components/Error.vue
@@ -0,0 +1,32 @@
+<template>
+  <div>
+    <H1>{{ title }}</H1>
+    <div>{{ description }}</div>
+  </div>
+</template>
+
+<script>
+export default {
+    props: {
+        status: Number,
+    },
+    computed: {
+        title() {
+            return {
+            503: '503: Service Unavailable',
+            500: '500: Server Error',
+            404: '404: Page Not Found',
+            403: '403: Forbidden',
+        }[this.status]
+    },
+    description() {
+      return {
+        503: 'Sorry, we are doing some maintenance. Please check back soon.',
+        500: 'Whoops, something went wrong on our servers.',
+        404: 'Sorry, the page you are looking for could not be found.',
+        403: 'Sorry, you are forbidden from accessing this page.',
+      }[this.status]
+    },
+  },
+}
+</script>
diff --git a/src/assets/js/components/Media/Index.vue b/src/assets/js/components/Media/Index.vue
new file mode 100644
index 0000000..2a624b0
--- /dev/null
+++ b/src/assets/js/components/Media/Index.vue
@@ -0,0 +1,29 @@
+<template>
+<div>
+    <div v-for="value in media" :key="value.id">
+        {{ value.name }}
+    </div>
+</div>
+</template>
+
+<script>
+import Layout from './../layouts/Layout';
+
+export default {
+    layout: (h, page) => h(Layout, [page]),
+    data () {
+        return {
+            //
+        }
+    },
+    props: ['media'],
+    methods: {
+        //
+    }
+}
+</script>
+
+<style scoped>
+
+</style>
+
diff --git a/src/assets/js/components/Profile/Index.vue b/src/assets/js/components/Profile/Index.vue
new file mode 100644
index 0000000..2092b2a
--- /dev/null
+++ b/src/assets/js/components/Profile/Index.vue
@@ -0,0 +1,56 @@
+<template>
+    <div class="flex flex-col w-1/3">
+    <p>Profile</p>
+    <div>
+        <input type="text" v-model="media" class="border" />
+        <span v-if="errors.name">{{ errors.name }}</span>
+    </div>
+    <button @click="notify()">Send</button>
+</div>
+</template>
+
+<script>
+import Layout from './../layouts/Layout';
+
+export default {
+    layout: (h, page) => h(Layout, [page]),
+    data () {
+        return {
+            media: ''
+        }
+    },
+    props: {
+        errors: Object
+    },
+    methods: {
+        notify() {
+
+            this.$inertia.post('/media/store',
+               {
+                    name: this.media
+               },
+               {
+                   onSuccess: () => {
+                        this.$notify({
+                            type: 'success',
+                            title: 'Important message',
+                            text: 'Hello user! This is a notification!'
+                        });
+                   },
+                    onError: () => {
+                        this.$notify({
+                            type: 'error',
+                            title: 'Important message',
+                            text: 'Hello user! This is a notification!'
+                        });
+                    },
+               });
+        }
+    }
+}
+</script>
+
+<style scoped>
+
+</style>
+
diff --git a/src/assets/js/components/Welcome.vue b/src/assets/js/components/Welcome.vue
new file mode 100644
index 0000000..20d0a73
--- /dev/null
+++ b/src/assets/js/components/Welcome.vue
@@ -0,0 +1,23 @@
+<template>
+<div>
+    Welcome
+</div>
+</template>
+
+<script>
+import Layout from './layouts/Layout';
+
+export default {
+    layout: (h, page) => h(Layout, [page]),
+    data () {
+        return {
+            //
+        }
+    }
+}
+</script>
+
+<style scoped>
+
+</style>
+
diff --git a/src/assets/js/components/global/AdminNav.vue b/src/assets/js/components/global/AdminNav.vue
new file mode 100644
index 0000000..e5b820b
--- /dev/null
+++ b/src/assets/js/components/global/AdminNav.vue
@@ -0,0 +1,20 @@
+<template>
+<nav class="flex justify-between px-12 py-4 bg-white dark:bg-blue-800 dark:text-white">
+    <div>
+        <Link :href="route('dashboard.index')">Schreibtisch</Link>
+    </div>
+    <button @click="toggleDarkMode()"><i class="fas fa-adjust"></i></button>
+</nav>
+</template>
+
+<script>
+export default {
+    name: 'AdminNav',
+    methods: {
+        toggleDarkMode() {
+            let root = document.documentElement;
+            root.classList.toggle("dark");
+        }
+    }
+}
+</script>
diff --git a/src/assets/js/components/global/AdminSideBar.vue b/src/assets/js/components/global/AdminSideBar.vue
new file mode 100644
index 0000000..164328f
--- /dev/null
+++ b/src/assets/js/components/global/AdminSideBar.vue
@@ -0,0 +1,28 @@
+<template>
+    <aside class="p-4 bg-white dark:bg-blue-800 dark:text-white">
+        <div v-for="menu in menus">
+            <span class="text-xs text-gray-500">{{ menu.title }}</span>
+            <ul>
+                <li v-for="item in menu.items">{{ item.title }}</li>
+            </ul>
+        </div>
+    </aside>
+</template>
+
+<script>
+export default {
+    name: 'AdminSideBar',
+    data () {
+        return {
+            menus: Array
+        }
+    },
+    created () {
+        this.menus = this.$page.props.menu.section;
+    }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/assets/js/components/global/MainNav.vue b/src/assets/js/components/global/MainNav.vue
new file mode 100644
index 0000000..d5c6bca
--- /dev/null
+++ b/src/assets/js/components/global/MainNav.vue
@@ -0,0 +1,22 @@
+<template>
+<nav class="flex justify-between px-12">
+    <div>
+        <Link :href="route('base')">Startseite</Link>
+        <Link :href="route('profile')">Profil</Link>
+        <Link :href="route('media.index')">Media</Link>
+    </div>
+    <button @click="toggleDarkMode()"><i class="fas fa-adjust"></i></button>
+</nav>
+</template>
+
+<script>
+export default {
+    name: 'MainNav',
+    methods: {
+        toggleDarkMode() {
+            let root = document.documentElement;
+            root.classList.toggle("dark");
+        }
+    }
+}
+</script>
diff --git a/src/assets/js/components/layouts/Layout.vue b/src/assets/js/components/layouts/Layout.vue
new file mode 100644
index 0000000..9cc8e09
--- /dev/null
+++ b/src/assets/js/components/layouts/Layout.vue
@@ -0,0 +1,22 @@
+<template>
+<div class="flex flex-col h-full min-h-full bg-gray-100 dark:bg-gray-800 dark:text-white">
+    <header class="sticky top-0 z-50 flex flex-col">
+        <main-nav></main-nav>
+        <notifications classes="bs-notify" />
+    </header>
+    <main class="flex flex-col flex-1">
+        <slot />
+    </main>
+    <footer class="sticky bottom-0 z-50 flex flex-col">
+        <div class="py-2 pr-3 text-right text-white bg-gray-800">
+            Footer
+        </div>
+    </footer>
+</div>
+</template>
+
+<script>
+export default {
+    //
+}
+</script>
diff --git a/src/assets/package.json b/src/assets/package.json
new file mode 100644
index 0000000..5f99006
--- /dev/null
+++ b/src/assets/package.json
@@ -0,0 +1,31 @@
+{
+    "private": true,
+    "scripts": {
+        "dev": "npm run development",
+        "development": "mix",
+        "watch": "mix watch",
+        "watch-poll": "mix watch -- --watch-options-poll=1000",
+        "hot": "mix watch --hot",
+        "prod": "npm run production",
+        "production": "mix --production"
+    },
+    "devDependencies": {
+        "axios": "^0.21",
+        "laravel-mix": "^6.0.6",
+        "lodash": "^4.17.19",
+        "postcss": "^8.4.4",
+        "vue-loader": "^15.9.7",
+        "vue-template-compiler": "^2.6.14"
+    },
+    "dependencies": {
+        "@inertiajs/inertia": "^0.10.1",
+        "@inertiajs/inertia-vue": "^0.7.2",
+        "@inertiajs/progress": "^0.2.6",
+        "autoprefixer": "^10.4.0",
+        "tailwindcss": "^2.2.19",
+        "vue": "^2.6.14",
+        "vue-confirm-dialog": "^1.0.2",
+        "vue-notification": "^1.3.20",
+        "vuex": "^3.6.2"
+    }
+}
diff --git a/src/assets/postcss.config.js b/src/assets/postcss.config.js
new file mode 100644
index 0000000..33ad091
--- /dev/null
+++ b/src/assets/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}
diff --git a/src/assets/tailwind.config.js b/src/assets/tailwind.config.js
new file mode 100644
index 0000000..9303e4f
--- /dev/null
+++ b/src/assets/tailwind.config.js
@@ -0,0 +1,31 @@
+const colors = require('tailwindcss/colors')
+
+module.exports = {
+  purge: [
+    './resources/**/*.blade.php',
+    './resources/**/*.js',
+    './resources/**/*.vue',
+  ],
+  darkMode: 'class', // false or 'media' or 'class'
+  theme: {
+    extend: {},
+    colors: {
+      transparent: 'transparent',
+      current: 'currentColor',
+      gray: colors.coolGray,
+      red: colors.rose,
+      yellow: colors.amber,
+      green: colors.lime,
+      blue: colors.sky,
+      indigo: colors.indigo,
+      purple: colors.purple,
+      pink: colors.pink,
+      white: colors.white,
+      black: colors.black
+    }
+  },
+  variants: {
+    extend: {},
+  },
+  plugins: [],
+}
\ No newline at end of file
diff --git a/src/assets/views/app.blade.php b/src/assets/views/app.blade.php
new file mode 100644
index 0000000..3b41331
--- /dev/null
+++ b/src/assets/views/app.blade.php
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="h-full">
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
+    <link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/js/all.min.js" integrity="sha512-fzff82+8pzHnwA1mQ0dzz9/E0B+ZRizq08yZfya66INZBz86qKTCt9MLU0NCNIgaMJCgeyhujhasnFUsYMsi0Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+    <script src="{{ mix('/js/app.js') }}" defer></script>
+    @routes
+</head>
+<body class="h-full">
+    @inertia
+</body>
+</html>
diff --git a/src/assets/webpack.mix.js b/src/assets/webpack.mix.js
new file mode 100644
index 0000000..7473a59
--- /dev/null
+++ b/src/assets/webpack.mix.js
@@ -0,0 +1,22 @@
+const mix = require('laravel-mix');
+const path = require('path');
+
+/*
+ |--------------------------------------------------------------------------
+ | Mix Asset Management
+ |--------------------------------------------------------------------------
+ |
+ | Mix provides a clean, fluent API for defining some Webpack build steps
+ | for your Laravel applications. By default, we are compiling the CSS
+ | file for the application as well as bundling up all the JS files.
+ |
+ */
+
+mix.js('resources/js/app.js', 'public/js')
+    .vue()
+    .postCss('resources/css/app.css', 'public/css', [
+        require('tailwindcss'),
+    ])
+    .alias({
+    ziggy: path.resolve('vendor/tightenco/ziggy/dist/vue'), // or 'vendor/tightenco/ziggy/dist/vue' if you're using the Vue plugin
+});
diff --git a/src/config/acl.php b/src/config/acl.php
new file mode 100644
index 0000000..183e6c0
--- /dev/null
+++ b/src/config/acl.php
@@ -0,0 +1,62 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Application Role
+    |--------------------------------------------------------------------------
+    |
+    |
+     */
+
+    'default_application_role' => 'worker',
+
+    'default_admin_role' => 'admin',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Application Permissions
+    |--------------------------------------------------------------------------
+    |
+    |
+     */
+
+    'permissions' => [
+        [
+            'name' => 'show_dashboard',
+            'label' => 'Schreibtisch anzeigen'
+        ],
+        [
+            'name' => 'edit_dashboard',
+            'label' => 'Schreibtisch bearbeiten'
+        ],
+        [
+            'name' => 'delete_dashboard',
+            'label' => 'Schreibtisch löschen'
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Application Roles
+    |--------------------------------------------------------------------------
+    |
+    |
+     */
+
+    'roles' => [
+        [
+            'name' => 'worker',
+            'label' => 'Mitarbeiter'
+        ],
+        [
+            'name' => 'supervisor',
+            'label' => 'Vorgesetzter'
+        ],
+        [
+            'name' => 'admin',
+            'label' => 'Administrator'
+        ]
+    ],
+];
diff --git a/src/config/filesystem.php b/src/config/filesystem.php
new file mode 100644
index 0000000..c75b9c3
--- /dev/null
+++ b/src/config/filesystem.php
@@ -0,0 +1,19 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Public Filesystem
+    |--------------------------------------------------------------------------
+    |
+    |
+     */
+
+    'media' => [
+        'driver' => 'local',
+        'root' => storage_path('app/public/media'),
+        'url' => env('APP_URL'). '/media',
+        'visibility' => 'public',
+    ]
+];
diff --git a/src/config/menu.php b/src/config/menu.php
new file mode 100644
index 0000000..63f392e
--- /dev/null
+++ b/src/config/menu.php
@@ -0,0 +1,63 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Menu
+    |--------------------------------------------------------------------------
+    |
+    |
+     */
+
+    'section' =>
+    [
+        [
+            'title' => 'Schreibtisch',
+            'priority' => 0,
+            'items' =>
+                [
+                    [
+                        'title' => 'Übersicht',
+                        'url_name' => 'dashboard_index',
+                        'component' => 'Dashboard'
+                    ],
+                ],
+        ],
+        [
+            'title' => 'Media',
+            'priority' => 99,
+            'items' =>
+                [
+                    [
+                        'title' => 'Browser',
+                        'url_name' => 'media_index',
+                        'component' => 'MediaBrowser'
+                    ],
+                ],
+        ],
+        [
+            'title' => 'System',
+            'priority' => 100,
+            'items' =>
+                [
+                    [
+                        'title' => 'Benutzerkonten',
+                        'url_name' => 'account_index',
+                        'component' => 'UserAccount'
+                    ],
+                    [
+                        'title' => 'Berechtigungen',
+                        'url_name' => 'permissions_index',
+                        'component' => 'Permissions'
+                    ],
+                    [
+                        'title' => 'Logs',
+                        'url_name' => 'log_viewer_index',
+                        'component' => 'LogViewer'
+                    ],
+                ]
+        ]
+    ]
+
+];
diff --git a/src/migrations/2021_12_01_141626_add_user_table_columns.php b/src/migrations/2021_12_01_141626_add_user_table_columns.php
new file mode 100644
index 0000000..090f0d3
--- /dev/null
+++ b/src/migrations/2021_12_01_141626_add_user_table_columns.php
@@ -0,0 +1,36 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddUserTableColumns extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->boolean('is_active')->after('id')->default(0);
+            $table->boolean('is_confirmed')->after('is_active')->default(0);
+            $table->string('email_token')->after('email')->nullable();
+            $table->string('avatar')->after('password')->nullable();
+            $table->datetime('last_login')->after('remember_token')->nullable();
+            $table->string('last_ip')->after('last_login')->nullable();
+        });
+
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        //
+    }
+}
diff --git a/src/migrations/2021_12_01_141724_create_roles_table.php b/src/migrations/2021_12_01_141724_create_roles_table.php
new file mode 100644
index 0000000..a38bac3
--- /dev/null
+++ b/src/migrations/2021_12_01_141724_create_roles_table.php
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateRolesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('roles', function (Blueprint $table) {
+            $table->bigIncrements('id');
+            $table->string('name');
+            $table->string('label')->nullable();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('roles');
+    }
+}
diff --git a/src/migrations/2021_12_01_141738_create_permissions_table.php b/src/migrations/2021_12_01_141738_create_permissions_table.php
new file mode 100644
index 0000000..acf2ad5
--- /dev/null
+++ b/src/migrations/2021_12_01_141738_create_permissions_table.php
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreatePermissionsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('permissions', function (Blueprint $table) {
+            $table->bigIncrements('id');
+            $table->string('name');
+            $table->string('label')->nullable();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('permissions');
+    }
+}
diff --git a/src/migrations/2021_12_01_141809_create_role_user_table.php b/src/migrations/2021_12_01_141809_create_role_user_table.php
new file mode 100644
index 0000000..98adc37
--- /dev/null
+++ b/src/migrations/2021_12_01_141809_create_role_user_table.php
@@ -0,0 +1,44 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateRoleUserTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('role_user', function (Blueprint $table) {
+            $table->bigInteger('user_id')->unsigned();
+            $table->bigInteger('role_id')->unsigned();
+
+            $table->foreign('user_id')
+                ->references('id')
+                ->on('users')
+                ->onDelete('cascade');
+
+            $table->foreign('role_id')
+                ->references('id')
+                ->on('roles')
+                ->onDelete('cascade');
+
+
+            $table->primary(['user_id', 'role_id']);
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('role_user');
+    }
+}
diff --git a/src/migrations/2021_12_01_141833_create_permission_role_table.php b/src/migrations/2021_12_01_141833_create_permission_role_table.php
new file mode 100644
index 0000000..ac8227d
--- /dev/null
+++ b/src/migrations/2021_12_01_141833_create_permission_role_table.php
@@ -0,0 +1,43 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreatePermissionRoleTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('permission_role', function (Blueprint $table) {
+            $table->bigInteger('role_id')->unsigned();
+            $table->bigInteger('permission_id')->unsigned();
+
+            $table->foreign('role_id')
+                ->references('id')
+                ->on('roles')
+                ->onDelete('cascade');
+
+            $table->foreign('permission_id')
+                ->references('id')
+                ->on('permissions')
+                ->onDelete('cascade');
+
+            $table->primary(['role_id', 'permission_id']);
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('permission_role');
+    }
+}
diff --git a/src/migrations/2021_12_01_144427_create_media_table.php b/src/migrations/2021_12_01_144427_create_media_table.php
new file mode 100644
index 0000000..798a023
--- /dev/null
+++ b/src/migrations/2021_12_01_144427_create_media_table.php
@@ -0,0 +1,40 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateMediaTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('media', function (Blueprint $table) {
+            $table->id();
+            $table->enum('type', ['folder', 'file']);
+            $table->string('name');
+            $table->string('title', 128);
+            $table->string('path');
+            $table->string('size', 24)->nullable();
+            $table->string('dimension', 24)->nullable();
+            $table->string('extension', 12)->nullable();
+            $table->string('meta_title')->nullable();
+            $table->string('meta_alt')->nullable();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('media');
+    }
+}
diff --git a/src/routes/web.php b/src/routes/web.php
new file mode 100644
index 0000000..452bdc9
--- /dev/null
+++ b/src/routes/web.php
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Inertia\Inertia;
+use Orendo\Larabase\Controllers\DashboardController;
+use Orendo\Larabase\Controllers\MediaController;
+
+/*
+|--------------------------------------------------------------------------
+| Dashboard Routes
+|--------------------------------------------------------------------------
+|
+ */
+
+Route::get('/', function () {
+    return Inertia::render('Welcome', [
+        'can' =>
+            ['edit_user' => true]
+    ]);
+})->name('base');
+
+Route::get('/profile', function () {
+    return Inertia::render('Profile/Index', [
+        'can' =>
+            ['edit_user' => false]
+    ]);
+})->name('profile');
+
+Route::get('/media', [MediaController::class, 'index'])->name('media.index');
+Route::post('/media/store', [MediaController::class, 'store'])->name('media.store');
+
+Route::get('/dashboard', function () {
+    return Inertia::render('Dashboard/Dashboard');
+})->name('dashboard.index');

