Compare commits

..

3 Commits

Author SHA1 Message Date
1d5ee292a9 updates 2023-11-14 11:10:49 +10:00
6f53c7ea6f cleanuo 2023-11-14 09:40:58 +10:00
7d9b6793d3 cleanup 2023-11-14 09:40:05 +10:00
296 changed files with 3392 additions and 19779 deletions

View File

@@ -3,7 +3,6 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_URL_API="${APP_URL}/api/"
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
@@ -30,7 +29,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
@@ -38,24 +37,11 @@ MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_PUBLIC_ACCESS_KEY_ID=
AWS_PUBLIC_SECRET_ACCESS_KEY=
AWS_PUBLIC_DEFAULT_REGION="us-west-002"
AWS_PUBLIC_BUCKET=
AWS_PUBLIC_USE_PATH_STYLE_ENDPOINT=false
AWS_PUBLIC_ENDPOINT=
AWS_PUBLIC_URL=
AWS_PRIVATE_ACCESS_KEY_ID=
AWS_PRIVATE_SECRET_ACCESS_KEY=
AWS_PRIVATE_DEFAULT_REGION="us-west-002"
AWS_PRIVATE_BUCKET=
AWS_PRIVATE_USE_PATH_STYLE_ENDPOINT=false
AWS_PRIVATE_ENDPOINT=
AWS_PRIVATE_URL=
CLOUDFLARE_ZONE_ID=
CLOUDFLARE_API_KEY=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
@@ -65,6 +51,7 @@ PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"

View File

@@ -1,59 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
APP_DEBUG=true
APP_URL=http://127.0.0.1
APP_URL_API="${APP_URL}/api/"
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
BROADCAST_DRIVER=log
CACHE_DRIVER=array
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_HOST=null
MAIL_PORT=null
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
CONTACT_ADDRESS="hello@stemmechanics.com.au"
CONTACT_SUBJECT="Contact from website"
STORAGE_LOCAL_URL="${APP_URL}/api/media/%ID%/download"
STORAGE_PUBLIC_URL="${APP_URL}/uploads/%NAME%"

2
.gitattributes vendored
View File

@@ -1,4 +1,4 @@
* text=auto
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css

View File

@@ -1,42 +0,0 @@
name: Laravel
on:
push:
branches: ["main"]
jobs:
laravel-tests:
runs-on: ubuntu-latest
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
- uses: actions/checkout@v3
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
run: composer install -q --no-interaction --no-progress --prefer-dist
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Create Database
run: |
mkdir -p database
touch database/database.sqlite
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: vendor/bin/phpunit
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: "16.x"
- name: Install dependencies
run: npm ci
- name: Run Vue tests
env:
LARAVEL_BYPASS_ENV_CHECK: "1"
run: npm run test

View File

@@ -1,24 +0,0 @@
name: renovate
on:
workflow_dispatch:
schedule:
- cron: "@daily"
push:
branches:
- main
jobs:
renovate:
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:37.440.7
steps:
- uses: actions/checkout@v4
- run: renovate
working-directory: ${{ gitea.workspace }}
env:
RENOVATE_CONFIG_FILE: "renovate-config.json"
LOG_LEVEL: "info"
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}

276
.gitignore vendored
View File

@@ -1,262 +1,20 @@
### Composer ###
composer.phar
/vendor/
# composer.lock
### Laravel ###
node_modules/
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
# Laravel 4 specific
bootstrap/compiled.php
app/storage/
# Laravel 5 & Lumen specific
public/storage
public/hot*
# Laravel 5 & Lumen specific with changed public path
public_html/storage
public_html/hot
storage/*.key
.env
Homestead.yaml
Homestead.json
/.vagrant
/.phpunit.cache
### macOS ###
# General
/.fleet
/.idea
/.vscode
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Vue ###
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# TODO: where does this rule come from?
docs/_book
# TODO: where does this rule come from?
test/
### Vuejs ###
# Recommended template: Node.gitignore
dist/
### This Project ###
/public/uploads
/public/build
*.key
### TinyMCE ###
/public/tinymce
!/public/tinymce/skins/ui/stemmech/
### Synk ###
.dccache
### TempCodeRunner ###
tempCodeRunnerFile.*
### PHPUnit ###
.phpunit.result.cache
.gitignore
### Codesniffer ###
phpcs.phar
phpcbf.phar
### PHPStorm ###
.idea/

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.css": "tailwindcss"
}
}

View File

@@ -27,7 +27,7 @@ Laravel has the most extensive and thorough [documentation](https://laravel.com/
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
@@ -49,24 +49,6 @@ We would like to extend our thanks to the following sponsors for funding Laravel
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Code Style
This project uses [Laravel Pint](https://laravel.com/docs/pint) for code styling. Pint is an opinionated PHP code style fixer for minimalists, built on top of PHP-CS-Fixer.
To automatically fix code style issues, run:
```bash
composer pint
```
To check for code style issues without fixing them:
```bash
composer pint-test
```
The code style configuration can be found in `pint.json`.
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).

View File

@@ -1,7 +0,0 @@
### Get media items
GET http://127.0.0.1:8001/media
Accept: application/json
### Get media item
GET http://127.0.0.1:8001/media/SC-After-Dark.png
Accept: application/json

27
app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class FileInvalidException extends Exception
{
/**
* The error code of the exception.
*
* @var int
*/
protected $code;
/**
* The error message of the exception.
*
* @var string
*/
protected $message;
/**
* Create a new exception instance.
*
* @param string $message
* @param int $code
* @return void
*/
public function __construct(string $message, int $code = 0)
{
$this->message = $message;
$this->code = $code;
parent::__construct($message, $code);
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class FileTooLargeException extends Exception
{
/**
* The error code of the exception.
*
* @var int
*/
protected $code;
/**
* The error message of the exception.
*
* @var string
*/
protected $message;
/**
* Create a new exception instance.
*
* @param string $message
* @param int $code
* @return void
*/
public function __construct(string $message, int $code = 0)
{
$this->message = $message;
$this->code = $code;
parent::__construct($message, $code);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@@ -1,181 +0,0 @@
<?php
namespace App;
use DateTime;
use Illuminate\Support\Facades\Log;
class Helpers
{
/**
* Get the maximum upload size in bytes.
*/
public static function getMaxUploadSize(): int
{
return min(
self::stringToBytes(ini_get('post_max_size')),
self::stringToBytes(ini_get('upload_max_filesize'))
);
}
public static function stringToBytes(string $val): int
{
if (empty($val)) {
$val = 0;
}
$val = trim($val);
$last = strtolower($val[strlen($val) - 1]);
$val = floatval($val);
switch ($last) {
case 'g':
$val *= (1024 * 1024 * 1024); //1073741824
break;
case 'm':
$val *= (1024 * 1024); //1048576
break;
case 'k':
$val *= 1024;
break;
}
return $val;
}
public static function bytesToString(int|float|string $bytes): string
{
if (!is_numeric($bytes)) {
return '0 bytes';
}
$units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
for ($i = 0; $bytes > 1024; $i++) {
$bytes /= 1024;
}
return round($bytes, 2) . ' ' . $units[$i];
}
public static function arrayToString(array $array, string $separator = ','): string
{
return implode($separator, array_map(function ($item) use ($separator) {
if (str_contains($item, $separator)) {
return '"' . str_replace('"', '\\"', $item) . '"';
} else {
return $item;
}
}, $array));
}
public static function stringToArray(string $string, string $separator = ','): array
{
return array_map(function ($item) {
// Remove quotes and unescape any escaped quotes within the string
return str_replace('\\"', '"', trim($item, '"'));
}, explode($separator, $string));
}
public static function timestampNoSeconds(string $timestamp): string
{
if(empty($timestamp)) {
return '';
}
$datetime = new DateTime($timestamp);
return $datetime->format('Y-m-d\TH:i');
}
public static function isUnderAge(mixed $ages): bool
{
if(!is_string($ages)) {
return true;
}
preg_match('/\d+/', $ages, $matches);
if (empty($matches)) {
return true;
}
$firstNumber = $matches[0];
return ($firstNumber <= 8);
}
public static function createTimeDurationStr(string $startStr, string $endStr): array
{
try {
$start = new DateTime($startStr);
$end = new DateTime($endStr);
if ($start->format('Y-m-d') === $end->format('Y-m-d')) {
return [
$start->format('l j M Y'),
$start->format('g:i a') . ' - ' . $end->format('g:i a')
];
} else {
return [
$start->format('D j/m/Y') . ' - ' . $end->format('D j/m/Y')
];
}
} catch(\Exception $e) {
return ['Error parsing date'];
}
}
public static function matchesMimeType(string $mimeType, string|array $patterns): bool
{
if (is_string($patterns)) {
$patterns = [$patterns];
}
foreach ($patterns as $pattern) {
$pattern = str_replace('\*', '.*', preg_quote($pattern, '/'));
$regex = '/^' . $pattern . '$/';
if (preg_match($regex, $mimeType) === 1) {
return true;
}
}
return false;
}
public static function findMatchingMimeTypeKey(string $mimeType, array $patterns): string|bool
{
$match = '';
foreach ($patterns as $key => $value) {
$keys = explode(',', $key);
foreach($keys as $key) {
$pattern = str_replace('\*', '.*', preg_quote($key, '/'));
$regex = '/^' . $pattern . '$/';
if (preg_match($regex, $mimeType) === 1) {
if($match !== $mimeType) {
$match = $key;
}
}
}
}
if($match !== '') {
return $match;
}
return false;
}
public static function cleanFileName(string $name): string
{
$name = strtolower($name);
$name = mb_ereg_replace('/^\.+/', '', $name);
$name = mb_ereg_replace("([\s_])", '-', $name);
$name = mb_ereg_replace("([^\w\s\d\-_.])", '', $name);
$name = mb_ereg_replace("([\.]{2,})", '', $name);
$name = mb_ereg_replace("([\-]{2,})", '-', $name);
return $name;
}
public static function filenameToTitle(string $filename): string
{
$title = pathinfo($filename, PATHINFO_FILENAME);
$title = str_replace(['-', '_', '.'], ' ', $title);
$title = ucwords($title);
return $title;
}
}

View File

@@ -1,240 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers;
use App\Jobs\SendEmail;
use App\Mail\UserEmailUpdateRequest;
use App\Models\User;
use App\Providers\QRCodeProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuth;
class AccountController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Display the specified resource.
*/
public function show(User $user)
{
return view('account', compact('user'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request)
{
$user = auth()->user();
$validator = Validator::make($request->all(), [
'firstname' => 'required',
'surname' => 'required',
'email' => ['required', 'email', 'unique:users,email,' . $user->id],
'phone' => 'required',
'shipping_address' => 'required_with:shipping_city,shipping_postcode,shipping_country,shipping_state',
'shipping_city' => 'required_with:shipping_address,shipping_postcode,shipping_country,shipping_state',
'shipping_postcode' => 'required_with:shipping_address,shipping_city,shipping_country,shipping_state',
'shipping_country' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_state',
'shipping_state' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_country',
'billing_address' => 'required_with:billing_city,billing_postcode,billing_country,billing_state',
'billing_city' => 'required_with:billing_address,billing_postcode,billing_country,billing_state',
'billing_postcode' => 'required_with:billing_address,billing_city,billing_country,billing_state',
'billing_country' => 'required_with:billing_address,billing_city,billing_postcode,billing_state',
'billing_state' => 'required_with:billing_address,billing_city,billing_postcode,billing_country',
], [
'firstname.required' => __('validation.custom_messages.firstname_required'),
'surname.required' => __('validation.custom_messages.surname_required'),
'email.required' => __('validation.custom_messages.email_required'),
'email.email' => __('validation.custom_messages.email_invalid'),
'phone.required' => __('validation.custom_messages.phone_required'),
'shipping_address.required' => __('validation.custom_messages.shipping_address_required'),
'shipping_city.required' => __('validation.custom_messages.shipping_city_required'),
'shipping_postcode.required' => __('validation.custom_messages.shipping_postcode_required'),
'shipping_country.required' => __('validation.custom_messages.shipping_country_required'),
'shipping_state.required' => __('validation.custom_messages.shipping_state_required'),
'billing_address.required' => __('validation.custom_messages.billing_address_required'),
'billing_city.required' => __('validation.custom_messages.billing_city_required'),
'billing_postcode.required' => __('validation.custom_messages.billing_postcode_required'),
'billing_country.required' => __('validation.custom_messages.billing_country_required'),
'billing_state.required' => __('validation.custom_messages.billing_state_required'),
]);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
$userData = $request->all();
$newEmail = $userData['email'];
unset($userData['email']);
if (strtolower($user->email) !== strtolower($newEmail)) {
$user->tokens()->where('type', 'email-update')->delete();
$token = $user->tokens()->create([
'type' => 'email-update',
'data' => [
'email' => $newEmail,
],
'expires_at' => now()->addMinutes(30),
]);
dispatch(new SendEmail($user->email, new UserEmailUpdateRequest($token->id, $user->email, $newEmail)))->onQueue('mail');
}
$userData['subscribed'] = ($request->get('subscribed', false) === 'on');
$user->update($userData);
$user->save();
session()->flash('message', 'Your account details have been saved');
session()->flash('message-title', 'Details updated');
session()->flash('message-type', 'success');
return redirect()->back();
}
/**
* Remove the specified resource from storage.
*/
public function destroy()
{
/** @var User $user */
$user = auth()->user();
auth()->logout();
$user->delete();
session()->flash('message', 'Your account has been deleted');
session()->flash('message-title', 'Account Deleted');
session()->flash('message-type', 'success');
return redirect()->route('index');
}
public static function getTFAInstance()
{
$tfa = new TwoFactorAuth(new QRCodeProvider(), 'STEMMechanics', 6, 30, Algorithm::Sha512);
$tfa->ensureCorrectTime();
return $tfa;
}
public function show_tfa()
{
$user = auth()->user();
if ($user->tfa_secret === null) {
$tfa = self::getTFAInstance();
$secret = $tfa->createSecret();
return response()->json([
'secret' => $secret,
]);
} else {
abort(404);
}
}
public function show_tfa_image(Request $request)
{
$user = auth()->user();
if ($user->tfa_secret === null && $request->has('secret')) {
$tfa = self::getTFAInstance();
$qrCodeProvider = new QRCodeProvider();
$qrCode = $qrCodeProvider->getQRCodeImage(
$tfa->getQRText($user->email, $request->get('secret')),
200
);
return response()->stream(function () use ($qrCode) {
echo $qrCode;
}, 200, ['Content-Type' => $qrCodeProvider->getMimeType()]);
} else {
abort(404);
}
}
public function post_tfa(Request $request)
{
$user = auth()->user();
if ($user->tfa_secret === null && $request->has('secret') && $request->has('code')) {
$secret = $request->get('secret');
$code = $request->get('code');
$tfa = self::getTFAInstance();
if ($tfa->verifyCode($secret, $code, 4)) {
$user->tfa_secret = $secret;
$user->save();
$codes = $user->generateBackupCodes();
return response()->json([
'success' => true,
'codes' => $codes
]);
} else {
return response()->json([
'success' => false,
]);
}
} else {
abort(403);
}
}
public function destroy_tfa(Request $request)
{
$user = auth()->user();
if ($user->tfa_secret !== null) {
$user->tfa_secret = null;
$user->save();
$user->backupCodes()->delete();
return response()->json([
'success' => true,
]);
} else {
abort(403);
}
}
public function post_tfa_reset_backup_codes(Request $request)
{
$user = auth()->user();
if ($user->tfa_secret !== null) {
$codes = $user->generateBackupCodes();
return response()->json([
'success' => true,
'codes' => $codes
]);
} else {
abort(403);
}
}
}

View File

@@ -1,319 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\SendEmail;
use App\Mail\UserEmailUpdateConfirm;
use App\Mail\UserLogin;
use App\Mail\UserLoginBackupCode;
use App\Mail\UserRegister;
use App\Mail\UserWelcome;
use App\Models\Token;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
class AuthController extends Controller
{
/**
* Show the login form or if token present, process the login
*
* @param Request $request
* @return View|RedirectResponse
*/
public function showLogin(Request $request): View|RedirectResponse
{
if (auth()->check()) {
return redirect()->action([HomeController::class, 'index']);
}
$token = $request->query('token');
if ($token) {
return $this->LoginByToken($token);
}
return view('auth.login');
}
/**
* Process the login form
*
* @param Request $request
* @return View|RedirectResponse
*/
public function postLogin(Request $request): View|RedirectResponse
{
$request->validate([
'email' => 'required|email',
'captcha' => 'required_captcha',
], [
'email.required' => __('validation.custom_messages.email_required'),
'email.email' => __('validation.custom_messages.email_invalid'),
]);
$forceEmailLogin = false;
if($request->has('code')) {
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
if($user) {
$tfa = AccountController::getTFAInstance();
if ($request->code && $tfa->verifyCode($user->tfa_secret, $request->code, 4)) {
$data = ['url' => session()->pull('url.intended', null)];
return $this->loginByUser($user, $data);
}
}
return view('auth.login-2fa', ['email' => $request->email])->withErrors([
'code' => 'The 2FA code is not valid',
]);
}
if($request->has('backup_code')) {
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
if($user) {
if($user->verifyBackupCode($request->backup_code)) {
$data = ['url' => session()->pull('url.intended', null)];
dispatch(new SendEmail($user->email, new UserLoginBackupCode($user->email)))->onQueue('mail');
return $this->loginByUser($user, $data);
}
}
return view('auth.login-2fa', ['email' => $request->email, 'method' => 'backup'])->withErrors([
'backup_code' => 'The backup code is not valid',
]);
}
if($request->has('method')) {
if($request->get('method') === 'email') {
$forceEmailLogin = true;
} else {
abort(404);
}
}
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
if ($user) {
if (!$forceEmailLogin && $user->tfa_secret !== null) {
return view('auth.login-2fa', ['user' => $user]);
}
$token = $user->tokens()->create([
'type' => 'login',
'data' => ['url' => session()->pull('url.intended', null)],
]);
dispatch(new SendEmail($user->email, new UserLogin($token->id, $user->getName(), $user->email)))->onQueue('mail');
return view('auth.login-link');
}
session()->flash('status', 'not-found');
return view('auth.login');
}
/**
* Process the login by token
*
* @param string $tokenStr
* @return View|RedirectResponse
*/
public function loginByToken(string $tokenStr): View|RedirectResponse
{
$token = Token::where('id', $tokenStr)
->where('type', 'login')
->where('expires_at', '>', now())
->first();
if ($token) {
$user = $token->user;
if($user) {
$token->delete();
return $this->loginByUser($user, $token->data);
}
}
session()->flash('message', 'That token has expired or is invalid');
session()->flash('message-title', 'Log in failed');
session()->flash('message-type', 'danger');
return view('auth.login');
}
/**
* Process the login by user
*
* @param User $user
* @param array $data
* @return RedirectResponse
*/
public function loginByUser(User $user, array $data = [])
{
$url = null;
if($data && isset($data->url) && $data->url) {
$url = $data->url;
}
Auth::login($user);
session()->flash('message', 'You have been logged in');
session()->flash('message-title', 'Logged in');
session()->flash('message-type', 'success');
if($url) {
return redirect($url);
}
return redirect()->action([HomeController::class, 'index']);
}
/**
* Process the user logout
*
* @return RedirectResponse
*/
public function logout(): RedirectResponse
{
auth()->logout();
session()->flash('message', 'You have been logged out');
session()->flash('message-title', 'Logged out');
session()->flash('message-type', 'warning');
return redirect()->route('index');
}
/**
* Show the registration form or if token present, process the registration
*
* @param Request $request
* @return View|RedirectResponse
*/
public function showRegister(Request $request): View|RedirectResponse
{
if (auth()->check()) {
return redirect()->route('index');
}
$tokenStr = $request->query('token');
if ($tokenStr) {
$token = Token::where('id', $tokenStr)
->where('type', 'register')
->where('expires_at', '>', now())
->first();
if ($token) {
$user = $token->user;
if ($user) {
$user->email_verified_at = now();
$user->save();
$user->tokens()->where('type', 'register')->delete();
dispatch(new SendEmail($user->email, new UserWelcome($user->email)))->onQueue('mail');
$this->loginByUser($user);
return redirect()->route('index');
}
}
session()->flash('message', 'That token has expired or is invalid');
session()->flash('message-title', 'Registration failed');
session()->flash('message-type', 'danger');
}
return view('auth.register');
}
/**
* Process the registration form
*
* @param Request $request
* @return View|RedirectResponse
*/
public function postRegister(Request $request): View|RedirectResponse
{
$request->validate([
'email' => 'required|email',
'captcha' => 'required_captcha',
], [
'email.required' => __('validation.custom_messages.email_required'),
'email.email' => __('validation.custom_messages.email_invalid')
]);
$key = $request->get('name', '');
$passHoneypot = ($key === 'AC9E94587F163AD93174FBF3DFDF9645B886960F2F8DD6D60F81CDB6DCDA3BC33');
$user = User::where('email', $request->email)->first();
if($user) {
if($user->email_verified_at !== null) {
return redirect()->back()->withInput()->withErrors([
'email' => __('validation.custom_messages.email_exists'),
]);
}
} else if($passHoneypot) {
$user = User::create([
'email' => $request->email,
]);
}
if($passHoneypot) {
Log::channel('honeypot')->info('Valid key used for registration using email: ' . $request->email . ', ip address: ' . $request->ip() . ', user agent: ' . $request->userAgent());
$user->tokens()->where('type', 'register')->delete();
$token = $user->tokens()->create([
'type' => 'register',
'data' => ['url' => session()->pull('url.intended', null)],
]);
dispatch(new SendEmail($user->email, new UserRegister($token->id, $user->email)))->onQueue('mail');
} else {
Log::channel('honeypot')->info('Invalid key used for registration using email: ' . $request->email . ', ip address: ' . $request->ip() . ', user agent: ' . $request->userAgent() . ', key: ' . $key);
}
return view('auth.register-link');
}
/**
* Confirm the user email update.
*
* @param Request $request
* @return RedirectResponse
*/
public function updateEmail(Request $request): RedirectResponse
{
$tokenStr = $request->query('token');
$token = Token::where('id', $tokenStr)
->where('type', 'email-update')
->where('expires_at', '>', now())
->first();
if($token && $token->user) {
if($token->data && isset($token->data['email'])) {
$user = $token->user;
$user->email = $token->data['email'];
$user->email_verified_at = now();
$user->save();
$user->tokens()->where('type', 'email-update')->delete();
session()->flash('message', 'Your email has been updated');
session()->flash('message-title', 'Email updated');
session()->flash('message-type', 'success');
dispatch(new SendEmail($user->email, new UserEmailUpdateConfirm($user->email)))->onQueue('mail');
return redirect()->route('index');
}
}
session()->flash('message', 'That token has expired or is invalid');
session()->flash('message-title', 'Email update failed');
session()->flash('message-type', 'danger');
return redirect()->route('index');
}
}

View File

@@ -2,7 +2,11 @@
namespace App\Http\Controllers;
abstract class Controller
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
//
use AuthorizesRequests, ValidatesRequests;
}

View File

@@ -1,20 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Workshop;
class HomeController extends Controller
{
public function index()
{
$posts = Post::query()->orderBy('created_at', 'desc')->limit(4)->get();
$workshops = Workshop::query()->where('starts_at', '>', now())->where('status', '!=', 'private')->orderBy('starts_at', 'asc')->limit(4)->get();
return view('home', [
'posts' => $posts,
'workshops' => $workshops,
]);
}
}

View File

@@ -1,102 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Location;
use App\Models\Post;
use Illuminate\Http\Request;
class LocationController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$query = Location::query();
if($request->has('search')) {
$query->where('name', 'like', '%' . $request->search . '%');
$query->orWhere('address', 'like', '%' . $request->search . '%');
}
$locations = $query->orderBy('name')->paginate(12)->onEachSide(1);
return view('admin.location.index', [
'locations' => $locations
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.location.edit');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'address_url' => 'nullable|url',
], [
// 'firstname.required' => __('validation.custom_messages.firstname_required'),
// 'surname.required' => __('validation.custom_messages.surname_required'),
]);
Location::create(array_merge(
$request->all(),
));
session()->flash('message', 'Location has been created');
session()->flash('message-title', 'Location created');
session()->flash('message-type', 'success');
return redirect()->route('admin.location.index');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Location $location)
{
return view('admin.location.edit', ['location' => $location]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Location $location)
{
$request->validate([
'name' => 'required',
'address_url' => 'url',
], [
// 'firstname.required' => __('validation.custom_messages.firstname_required'),
// 'surname.required' => __('validation.custom_messages.surname_required'),
]);
$location->update($request->all());
session()->flash('message', 'Location has been updated');
session()->flash('message-title', 'Location updated');
session()->flash('message-type', 'success');
return redirect()->route('admin.location.index');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Location $location)
{
$location->delete();
session()->flash('message', 'Location has been deleted');
session()->flash('message-title', 'Location deleted');
session()->flash('message-type', 'danger');
return redirect()->route('admin.location.index');
}
}

View File

@@ -1,549 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Exceptions\FileInvalidException;
use App\Exceptions\FileTooLargeException;
use App\Helpers;
use App\Models\Media;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class MediaController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
if(!$request->wantsJson()) {
abort(404);
}
$media = $this->getMedia($request);
return response()->json($media);
}
public function admin_index(Request $request)
{
$media = $this->getMedia($request);
return view('admin.media.index', [
'media' => $media,
]);
}
public function getMedia(Request $request)
{
$query = Media::query();
$perPage = $request->input('per_page', 25);
if(!empty($request->get('search'))) {
$query->where(function($query) use ($request) {
$query->where('title', 'like', '%' . $request->search . '%');
$query->orWhere('name', 'like', '%' . $request->search . '%');
});
}
if($request->has('mime_type')) {
$mime_types = explode(',', $request->mime_type);
$query->where(function ($query) use ($mime_types) {
foreach ($mime_types as $mime_type) {
$mime_type = str_replace('*', '%', $mime_type);
$query->orWhere('mime_type', 'like', $mime_type);
}
});
}
$media = $query->orderBy('created_at', 'desc');
if($request->wantsJson() && !(empty($request->input('selected'))) && empty($request->get('search')) && !$request->has('page')) {
$selected = $request->input('selected')[0];
$selectedMedia = $media->get();
$selectedMediaIndex = $selectedMedia->search(function ($item) use ($selected) {
return $item->name == $selected;
});
if ($selectedMediaIndex !== false) {
$page = intdiv($selectedMediaIndex, $perPage) + 1;
$request->merge(['page' => $page]);
}
}
$media = $media->paginate($perPage)->onEachSide(1);
// Transform the 'password' field of each item in the collection
$media->getCollection()->transform(function ($item) {
$item->password = $item->password ? 'yes' : null;
return $item;
});
return $media;
}
/**
* Display the specified resource.
*/
public function show(Request $request, Media $media)
{
if(!$request->wantsJson()) {
abort(404);
}
return response()->json($media);
}
/**
* Show the form for creating a new resource.
*/
public function admin_create()
{
return view('admin.media.edit');
}
/**
* Store a newly created resource in storage.
*/
public function admin_store(Request $request)
{
$file = null;
$cleanupPath = null;
// Check if the endpoint received a file...
if($request->hasFile('file')) {
try {
$file = $this->upload($request);
if(is_array($file) && !empty($file['chunk'])) {
return response()->json([
'message' => 'Chunk stored',
'upload_token' => $file['token'] ?? null,
]);
} else if(!$file) {
return response()->json([
'message' => 'An error occurred processing the file.',
'errors' => [
'file' => 'An error occurred processing the file.'
]
], 422);
}
if(!$request->has('title')) {
return response()->json([
'message' => 'The file ' . $file->getClientOriginalName() . ' has been uploaded',
'file' => [
'name' => $file->getClientOriginalName(),
'size' => $file->getSize(),
'mime_type' => $file->getMimeType()
]
]);
}
} catch(\Exception $e) {
return response()->json([
'message' => $e->getMessage(),
'errors' => [
'file' => $e->getMessage()
]
], 422);
}
// else check if it received a file name of a previous upload...
} else if($request->has('upload_token') || $request->has('file')) {
$uploadToken = $request->input('upload_token', $request->input('file'));
$chunkUploads = session()->get('chunk_uploads', []);
if(!is_string($uploadToken) || !isset($chunkUploads[$uploadToken])) {
return response()->json([
'message' => 'Could not find the referenced file on the server.',
'errors' => [
'file' => 'Could not find the referenced file on the server.'
]
], 422);
}
$tempFileName = $chunkUploads[$uploadToken];
if(!file_exists($tempFileName)) {
return response()->json([
'message' => 'Could not find the referenced file on the server.',
'errors' => [
'file' => 'Could not find the referenced file on the server ('.$tempFileName.').'
]
], 422);
}
$fileMime = mime_content_type($tempFileName);
if($fileMime === false) {
$fileMime = 'application/octet-stream';
}
$fileName = $request->input('filename', 'upload');
$fileName = Helpers::cleanFileName($fileName);
if ($fileName === '') {
$fileName = 'upload';
}
$file = new UploadedFile($tempFileName, $fileName, $fileMime, null, true);
$cleanupPath = $tempFileName;
unset($chunkUploads[$uploadToken]);
session()->put('chunk_uploads', $chunkUploads);
}
// Check there is an actual file
if(!$file) {
return response()->json([
'message' => 'A file is required.',
'errors' => [
'file' => 'A file is required.'
]
], 422);
}
if(!$request->has('title')) {
return response()->json([
'message' => 'A title is required',
'errors' => [
'title' => 'A title is required'
]
], 422);
}
$fileName = $file->getClientOriginalName();
$name = pathinfo($fileName, PATHINFO_FILENAME);
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
$name = Helpers::cleanFileName($name);
if(Media::find($name . '.' . $extension) !== null) {
$increment = 1;
$name = preg_replace('/-\d+$/', '', $name);
while(Media::find($name . '-' . $increment . '.' . $extension) !== null) {
$increment++;
}
$fileName = $name . '-' . $increment . '.' . $extension;
}
$hash = hash_file('sha256', $file->path());
$storage = Storage::disk('media');
$exists = $storage->exists($hash);
if(!$exists) {
if($file->storeAs('/', $hash, 'media') === false) {
if($request->wantsJson()) {
return response()->json([
'message' => 'A server error occurred uploading the file.',
], 500);
} else {
session()->flash('message', 'A server error occurred uploading the file.');
session()->flash('message-title', 'Upload failed');
session()->flash('message-type', 'danger');
return redirect()->back();
}
}
}
$media = Media::Create([
'title' => $request->get('title', Helpers::filenameToTitle($fileName)),
'user_id' => auth()->id(),
'name' => $fileName,
'size' => $file->getSize(),
'mime_type' => $file->getMimeType(),
'hash' => $hash
]);
if(!$exists) {
$media->generateVariants(false);
} else {
// find media with the same hash that also has variants and copy them
$mediaWithVariants = Media::where('hash', $hash)->where('variants', '!=', '')->orderBy('created_at')->first();
if($mediaWithVariants) {
$media->variants = $mediaWithVariants->variants;
$media->save();
}
}
if(is_string($cleanupPath)) {
$realPath = realpath($cleanupPath);
$tempDir = realpath(sys_get_temp_dir());
if($realPath !== false && $tempDir !== false && str_starts_with($realPath, $tempDir . DIRECTORY_SEPARATOR)) {
@unlink($realPath);
}
}
if($request->wantsJson()) {
return response()->json([
'message' => 'File has been uploaded',
'name' => $media->name,
'size' => $media->size,
'mime_type' => $media->mime_type
]);
} else {
session()->flash('message', 'Media has been uploaded');
session()->flash('message-title', 'Media uploaded');
session()->flash('message-type', 'success');
return redirect()->route('admin.media.index');
}
}
/**
* Show the form for editing the specified resource.
*/
public function admin_edit(Media $media)
{
return view('admin.media.edit', ['medium' => $media]);
}
/**
* Update the specified resource in storage.
*/
public function admin_update(Request $request, Media $media)
{
$max_size = Helpers::getMaxUploadSize();
$validator = Validator::make($request->all(), [
'title' => 'required',
// 'file' => 'nullable|file|max:' . (max(round($max_size / 1024),0)),
], [
'title.required' => __('validation.custom_messages.title_required'),
// 'file.required' => __('validation.custom_messages.file_required'),
// 'file.file' => __('validation.custom_messages.file_file'),
// 'file.max' => __('validation.custom_messages.file_max', ['max' => Helpers::bytesToString($max_size)])
]);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
$mediaData = $request->all();
// $file = null;
// if($request->has('file')) {
// $file = $request->file('file');
//
// $name = $file->getClientOriginalName();
// $name = Helpers::cleanFileName($name);
// if ($name !== $media->name) {
// if (Media::find($name) !== null) {
// $increment = 2;
// while (Media::find($name . '-' . $increment) !== null) {
// $increment++;
// }
//
// $name = $name . '-' . $increment;
// }
// }
//
// $hash = hash_file('sha256', $file->path());
//
// $storage = Storage::disk('media');
// if (!$storage->exists($hash)) {
// if ($file->storeAs('/', $hash, 'media') === false) {
// session()->flash('message', 'A server error occurred uploading the file.');
// session()->flash('message-title', 'Upload failed');
// session()->flash('message-type', 'danger');
// return redirect()->back();
// }
// }
//
// $mediaData['name'] = $name;
// $mediaData['size'] = $file->getSize();
// $mediaData['mime_type'] = $file->getMimeType();
// $mediaData['hash'] = $hash;
// }
if($request->get('password_clear') === 'on') {
$mediaData['password'] = null;
} else {
$password = $request->get('password');
if($password !== null && $password !== '') {
$mediaData['password'] = password_hash($request->get('password'), PASSWORD_DEFAULT);
} else {
unset($mediaData['password']);
}
}
$media->update($mediaData);
// if($file) {
// $media->generateVariants(false);
// unlink($file);
// }
session()->flash('message', 'Media has been updated');
session()->flash('message-title', 'Media updated');
session()->flash('message-type', 'success');
return redirect()->route('admin.media.index');
}
/**
* Remove the specified resource from storage.
*/
public function admin_destroy(Request $request, Media $media)
{
$media->delete();
session()->flash('message', 'Media has been deleted');
session()->flash('message-title', 'Media deleted');
session()->flash('message-type', 'danger');
if($request->wantsJson()) {
return response()->json([
'success' => true,
'redirect' => route('admin.media.index'),
]);
}
return redirect()->route('admin.media.index');
}
/**
* @throws FileInvalidException
* @throws FileTooLargeException
*/
private function upload(Request $request)
{
$max_size = Helpers::getMaxUploadSize();
$file = $request->file('file');
if(!$file->isValid()) {
throw new FileInvalidException('The file is invalid');
}
$fileName = $request->input('filename', $file->getClientOriginalName());
$fileName = Helpers::cleanFileName($fileName);
if ($fileName === '') {
$extension = strtolower($file->getClientOriginalExtension());
$fileName = 'upload' . ($extension !== '' ? '.' . $extension : '');
}
if(($request->has('filestart') || $request->has('fileappend')) && $request->has('filesize')) {
$fileSize = $request->get('filesize');
if($fileSize > $max_size) {
throw new FileTooLargeException('The file is larger than the maximum size allowed of ' . Helpers::bytesToString($max_size));
}
$chunkUploads = session()->get('chunk_uploads', []);
$uploadToken = $request->input('upload_token');
if($request->has('filestart')) {
$uploadToken = bin2hex(random_bytes(16));
$tempFilePath = tempnam(sys_get_temp_dir(), 'chunk-' . Auth::id() . '-');
if($tempFilePath === false) {
throw new FileInvalidException('Unable to create a temporary upload file.');
}
$chunkUploads[$uploadToken] = $tempFilePath;
session()->put('chunk_uploads', $chunkUploads);
} else {
if(!is_string($uploadToken) || !isset($chunkUploads[$uploadToken])) {
throw new FileInvalidException('Invalid upload token.');
}
$tempFilePath = $chunkUploads[$uploadToken];
}
$filemode = 'a';
if($request->has('filestart')) {
$filemode = 'w';
}
// Append the chunk to the temporary file
$fp = fopen($tempFilePath, $filemode);
if ($fp) {
fwrite($fp, file_get_contents($file->getRealPath()));
fclose($fp);
}
// Check if the upload is complete
if (filesize($tempFilePath) >= $fileSize) {
$fileMime = mime_content_type($tempFilePath);
if($fileMime === false) {
$fileMime = 'application/octet-stream';
}
if(is_string($uploadToken) && isset($chunkUploads[$uploadToken])) {
unset($chunkUploads[$uploadToken]);
session()->put('chunk_uploads', $chunkUploads);
}
return new UploadedFile($tempFilePath, $fileName, $fileMime, null, true);
} else {
return [
'chunk' => true,
'token' => $uploadToken,
];
}
}
return $file;
}
public function download(Request $request, Media $media)
{
$file = $media->path();
if($file === null) {
abort(404, 'File not found');
}
if($media->password !== null && !Auth::user()?->isAdmin()) {
if(!$request->has('password')) {
return view('media-password');
} else {
$password = $request->get('password');
if($password === '' || $password === null) {
return view('media-password', [
'error' => 'Password is required',
]);
}
if(!password_verify(base64_decode($password), $media->password)) {
return view('media-password', [
'error' => 'Password is incorrect',
]);
}
}
}
$variant = '';
$download = false;
$variants = array_keys($media->getVariantTypes());
$query = $request->getQueryString();
if($query !== '') {
$queryList = explode('&', $query);
foreach($queryList as $queryItem) {
$parts = explode('=', $queryItem);
if($variant === '' && in_array($parts[0], $variants) && ($parts[1] === '' || filter_var($parts[1], FILTER_VALIDATE_BOOLEAN))) {
$variant = $parts[0];
}
if($parts[0] === 'download' && ($parts[1] === '' || filter_var($parts[1], FILTER_VALIDATE_BOOLEAN))) {
$download = true;
}
}
}
$mime_type = $media->mime_type;
$name = $media->name;
if($variant !== '') {
$variantFile = $media->getClosestVariant($variant);
$file = $variantFile['file'];
$mime_type = $variantFile['mime_type'];
$name = $variantFile['name'];
}
$headers = [
'Content-Type' => $mime_type,
'Content-Disposition' => ($download ? 'attachment; ' : '') . 'filename="' . $name . '"',
];
return response()->file($file, $headers);
}
}

View File

@@ -1,147 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers;
use App\Models\Media;
use App\Models\Post;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$query = Post::query();
$query->where('status', 'published');
if($request->has('search')) {
$query->where('title', 'like', '%' . $request->search . '%');
$query->orWhere('content', 'like', '%' . $request->search . '%');
}
$posts = $query->orderBy('created_at', 'desc')->paginate(12)->onEachSide(1);
return view('post.index', [
'posts' => $posts
]);
}
/**
* Display a listing of the resource.
*/
public function admin_index(Request $request)
{
$query = Post::query();
if($request->has('search')) {
$query->where('title', 'like', '%' . $request->search . '%');
$query->orWhere('content', 'like', '%' . $request->search . '%');
}
$posts = $query->orderBy('created_at', 'desc')->paginate(12)->onEachSide(1);
return view('admin.post.index', [
'posts' => $posts
]);
}
/**
* Show the form for creating a new resource.
*/
public function admin_create()
{
return view('admin.post.edit');
}
/**
* Store a newly created resource in storage.
*/
public function admin_store(Request $request)
{
$request->validate([
'title' => 'required',
'content' => 'required',
'hero_media_name' => 'required|exists:media,name',
], [
'title.required' => __('validation.custom_messages.title_required'),
'content.required' => __('validation.custom_messages.content_required'),
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
]);
$postData = $request->all();
$postData['user_id'] = auth()->user()->id;
$post = Post::create($postData);
$post->updateFiles($request->input('files'));
$post->updateFiles($request->input('gallery'), 'gallery');
$post->updateFiles($request->input('videos'), 'videos');
session()->flash('message', 'Post has been created');
session()->flash('message-title', 'Post created');
session()->flash('message-type', 'success');
return redirect()->route('admin.post.index');
}
/**
* Display the specified resource.
*/
public function show(Post $post)
{
return view('post.show', ['post' => $post]);
}
/**
* Show the form for editing the specified resource.
*/
public function admin_edit(Post $post)
{
return view('admin.post.edit', ['post' => $post]);
}
/**
* Update the specified resource in storage.
*/
public function admin_update(Request $request, Post $post)
{
$request->validate([
'title' => 'required',
'content' => 'required',
'hero_media_name' => 'required|exists:media,name',
], [
'title.required' => __('validation.custom_messages.title_required'),
'content.required' => __('validation.custom_messages.content_required'),
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
]);
$postData = $request->all();
$post->update($postData);
$post->updateFiles($request->input('files'));
$post->updateFiles($request->input('gallery'), 'gallery');
$post->updateFiles($request->input('videos'), 'videos');
session()->flash('message', 'Post has been updated');
session()->flash('message-title', 'Post updated');
session()->flash('message-type', 'success');
return redirect()->route('admin.post.index');
}
/**
* Remove the specified resource from storage.
*/
public function admin_destroy(Post $post)
{
$post->delete();
session()->flash('message', 'Post has been deleted');
session()->flash('message-title', 'Post deleted');
session()->flash('message-type', 'danger');
return redirect()->route('admin.post.index');
}
}

View File

@@ -1,57 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Workshop;
use Carbon\Carbon;
use Illuminate\Http\Request;
class SearchController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$search = $request->get('q', '');
$search_words = explode(' ', $search); // Split the search query into words[1]
$workshopQuery = Workshop::query()->where('status', '!=', 'draft');
$workshopQuery->where(function ($query) use ($search_words) {
foreach ($search_words as $word) {
$query->orWhere(function ($subQuery) use ($word) {
$subQuery->where('title', 'like', '%' . $word . '%')
->orWhere('content', 'like', '%' . $word . '%')
->orWhereHas('location', function ($locationQuery) use ($word) {
$locationQuery->where('name', 'like', '%' . $word . '%');
});
});
}
});
$workshops = $workshopQuery->orderBy('starts_at', 'desc')
->paginate(6, ['*'], 'workshop');
// $postQuery = Post::query()->where('status', 'published');
// $postQuery->where(function ($query) use ($search_words) {
// foreach ($search_words as $word) {
// $query->where(function ($subQuery) use ($word) {
// $subQuery->where('title', 'like', '%' . $word . '%')
// ->orWhere('content', 'like', '%' . $word . '%');
// });
// }
// });
//
// $posts = $postQuery->orderBy('created_at', 'desc')
// ->paginate(6, ['*'], 'post')
// ->onEachSide(1);
return view('search', [
'workshops' => $workshops,
// 'posts' => $posts,
'search' => $search,
]);
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\EmailSubscriptions;
use App\Models\SentEmail;
use Illuminate\Http\Request;
class SubscribeController extends Controller
{
/**
* Display a listing of the resource.
*/
public function destroy($email)
{
$emailModel = SentEmail::where('id', $email)->first();
if (!$emailModel) {
// Email not found, redirect to home page with a message
return redirect()->route('index')->with([
'message' => 'The unsubscribe link is invalid or has expired.',
'message-title' => 'Invalid Unsubscribe Link',
'message-type' => 'warning'
]);
}
// Existing unsubscribe logic
$subscriptions = EmailSubscriptions::where('email', $emailModel->recipient)->get();
if ($subscriptions->isEmpty()) {
session()->flash('message', 'You are already unsubscribed.');
session()->flash('message-title', 'Already Unsubscribed');
session()->flash('message-type', 'info');
} else {
EmailSubscriptions::where('email', $emailModel->recipient)->delete();
session()->flash('message', 'You have been successfully unsubscribed.');
session()->flash('message-title', 'Unsubscribed');
session()->flash('message-type', 'success');
}
return redirect()->route('index');
}
}

View File

@@ -1,65 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Ticket;
use Illuminate\Http\Request;
class TicketController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Ticket $ticket)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Ticket $ticket)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Ticket $ticket)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Ticket $ticket)
{
//
}
}

View File

@@ -1,167 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$query = User::query();
if($request->has('search')) {
$query->where('firstname', 'like', '%' . $request->search . '%');
$query->orWhere('surname', 'like', '%' . $request->search . '%');
$query->orWhere('phone', 'like', '%' . $request->search . '%');
$query->orWhere('email', 'like', '%' . $request->search . '%');
}
$users = $query->orderBy('created_at', 'desc')->paginate(12)->onEachSide(1);
return view('admin.user.index', [
'users' => $users
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.user.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'firstname' => '',
'surname' => '',
'email' => 'email|unique:users',
'phone' => '',
'shipping_address' => 'required_with:shipping_city,shipping_postcode,shipping_country,shipping_state',
'shipping_city' => 'required_with:shipping_address,shipping_postcode,shipping_country,shipping_state',
'shipping_postcode' => 'required_with:shipping_address,shipping_city,shipping_country,shipping_state',
'shipping_country' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_state',
'shipping_state' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_country',
'billing_address' => 'required_with:billing_city,billing_postcode,billing_country,billing_state',
'billing_city' => 'required_with:billing_address,billing_postcode,billing_country,billing_state',
'billing_postcode' => 'required_with:billing_address,billing_city,billing_country,billing_state',
'billing_country' => 'required_with:billing_address,billing_city,billing_postcode,billing_state',
'billing_state' => 'required_with:billing_address,billing_city,billing_postcode,billing_country',
], [
'firstname.required' => __('validation.custom_messages.firstname_required'),
'surname.required' => __('validation.custom_messages.surname_required'),
'email.required' => __('validation.custom_messages.email_required'),
'email.email' => __('validation.custom_messages.email_invalid'),
'phone.required' => __('validation.custom_messages.phone_required'),
'shipping_address.required' => __('validation.custom_messages.shipping_address_required'),
'shipping_city.required' => __('validation.custom_messages.shipping_city_required'),
'shipping_postcode.required' => __('validation.custom_messages.shipping_postcode_required'),
'shipping_country.required' => __('validation.custom_messages.shipping_country_required'),
'shipping_state.required' => __('validation.custom_messages.shipping_state_required'),
'billing_address.required' => __('validation.custom_messages.billing_address_required'),
'billing_city.required' => __('validation.custom_messages.billing_city_required'),
'billing_postcode.required' => __('validation.custom_messages.billing_postcode_required'),
'billing_country.required' => __('validation.custom_messages.billing_country_required'),
'billing_state.required' => __('validation.custom_messages.billing_state_required'),
]);
User::create($request->all());
session()->flash('message', 'User has been created');
session()->flash('message-title', 'User created');
session()->flash('message-type', 'success');
return redirect()->route('admin.user.index');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(User $user)
{
return view('admin.user.edit', compact('user'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, User $user)
{
$request->validate([
'firstname' => '',
'surname' => '',
'email' => ['email', Rule::unique('users')->ignore($user->id)],
'phone' => '',
'shipping_address' => 'required_with:shipping_city,shipping_postcode,shipping_country,shipping_state',
'shipping_city' => 'required_with:shipping_address,shipping_postcode,shipping_country,shipping_state',
'shipping_postcode' => 'required_with:shipping_address,shipping_city,shipping_country,shipping_state',
'shipping_country' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_state',
'shipping_state' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_country',
'billing_address' => 'required_with:billing_city,billing_postcode,billing_country,billing_state',
'billing_city' => 'required_with:billing_address,billing_postcode,billing_country,billing_state',
'billing_postcode' => 'required_with:billing_address,billing_city,billing_country,billing_state',
'billing_country' => 'required_with:billing_address,billing_city,billing_postcode,billing_state',
'billing_state' => 'required_with:billing_address,billing_city,billing_postcode,billing_country',
], [
'firstname.required' => __('validation.custom_messages.firstname_required'),
'surname.required' => __('validation.custom_messages.surname_required'),
'email.required' => __('validation.custom_messages.email_required'),
'email.email' => __('validation.custom_messages.email_invalid'),
'phone.required' => __('validation.custom_messages.phone_required'),
'shipping_address.required' => __('validation.custom_messages.shipping_address_required'),
'shipping_city.required' => __('validation.custom_messages.shipping_city_required'),
'shipping_postcode.required' => __('validation.custom_messages.shipping_postcode_required'),
'shipping_country.required' => __('validation.custom_messages.shipping_country_required'),
'shipping_state.required' => __('validation.custom_messages.shipping_state_required'),
'billing_address.required' => __('validation.custom_messages.billing_address_required'),
'billing_city.required' => __('validation.custom_messages.billing_city_required'),
'billing_postcode.required' => __('validation.custom_messages.billing_postcode_required'),
'billing_country.required' => __('validation.custom_messages.billing_country_required'),
'billing_state.required' => __('validation.custom_messages.billing_state_required'),
]);
$user->update($request->all());
session()->flash('message', 'User details have been updated');
session()->flash('message-title', 'Details updated');
session()->flash('message-type', 'success');
return redirect()->back();
}
/**
* Remove the specified resource from storage.
*/
public function destroy(User $user)
{
if($user->id !== '1') {
$user->delete();
session()->flash('message', 'User has been deleted');
session()->flash('message-title', 'User deleted');
session()->flash('message-type', 'success');
} else {
session()->flash('message', 'You cannot delete the main admin user');
session()->flash('message-title', 'User not deleted');
session()->flash('message-type', 'error');
}
return redirect()->route('admin.user.index');
}
}

View File

@@ -1,209 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Workshop;
use Carbon\Carbon;
use Illuminate\Http\Request;
class WorkshopController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$query = Workshop::query();
$query = $query->where('starts_at', '>=', Carbon::now()->subDays(8))
->orderBy('starts_at', 'asc');
$workshops = $query->paginate(12);
return view('workshop.index', [
'workshops' => $workshops
]);
}
/**
* Display a listing of the resource.
*/
public function past_index()
{
$query = Workshop::query();
$query = $query->where('starts_at', '<', Carbon::now())
->orderBy('starts_at', 'desc');
$workshops = $query->paginate(12);
return view('workshop.index', [
'workshops' => $workshops
]);
}
/**
* Display a listing of the resource.
*/
public function admin_index(Request $request)
{
$query = Workshop::query();
if($request->has('search')) {
$query->where('title', 'like', '%' . $request->search . '%');
$query->orWhere('content', 'like', '%' . $request->search . '%');
}
$workshops = $query->orderBy('starts_at', 'desc')->paginate(12)->onEachSide(1);
return view('admin.workshop.index', [
'workshops' => $workshops
]);
}
/**
* Show the form for creating a new resource.
*/
public function admin_create()
{
return view('admin.workshop.edit');
}
/**
* Store a newly created resource in storage.
*/
public function admin_store(Request $request)
{
$request->validate([
'title' => 'required',
'content' => 'required',
'starts_at' => 'required',
'ends_at' => 'required|after:starts_at',
'publish_at' => 'required',
'closes_at' => 'required',
'status' => 'required',
'hero_media_name' => 'required|exists:media,name',
'registration_data' => 'required_unless:registration,none',
], [
'title.required' => __('validation.custom_messages.title_required'),
'content.required' => __('validation.custom_messages.content_required'),
'starts_at.required' => __('validation.custom_messages.starts_at_required'),
'ends_at.required' => __('validation.custom_messages.ends_at_required'),
'ends_at.after' => __('validation.custom_messages.ends_at_after'),
'publish_at.required' => __('validation.custom_messages.publish_at_required'),
'closes_at.required' => __('validation.custom_messages.closes_at_required'),
'status.required' => __('validation.custom_messages.status_required'),
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
'hero_media_name.exists' => __('validation.custom_messages.hero_media_name_exists'),
'registration_data.required_unless' => __('validation.custom_messages.registration_data_required_unless'),
]);
$workshopData = $request->all();
$workshopData['user_id'] = auth()->user()->id;
if($workshopData['status'] === 'open' && Carbon::parse($workshopData['starts_at'])->lt(Carbon::now())) {
$workshopData['status'] = 'closed';
}
$workshop = Workshop::create($workshopData);
$workshop->updateFiles($request->input('files'));
session()->flash('message', 'Workshop has been created');
session()->flash('message-title', 'Workshop created');
session()->flash('message-type', 'success');
return redirect()->route('admin.workshop.index');
}
/**
* Display the specified resource.
*/
public function show(Workshop $workshop)
{
if(!auth()->user()?->admin && $workshop->status == 'draft') {
abort(404);
}
return view('workshop.show', ['workshop' => $workshop]);
}
/**
* Show the form for editing the specified resource.
*/
public function admin_edit(Workshop $workshop)
{
return view('admin.workshop.edit', ['workshop' => $workshop]);
}
/**
* Update the specified resource in storage.
*/
public function admin_update(Request $request, Workshop $workshop)
{
$request->validate([
'title' => 'required',
'content' => 'required',
'starts_at' => 'required',
'ends_at' => 'required|after:starts_at',
'publish_at' => 'required',
'closes_at' => 'required',
'status' => 'required',
'hero_media_name' => 'required|exists:media,name',
'registration_data' => 'required_unless:registration,none',
], [
'title.required' => __('validation.custom_messages.title_required'),
'content.required' => __('validation.custom_messages.content_required'),
'starts_at.required' => __('validation.custom_messages.starts_at_required'),
'ends_at.required' => __('validation.custom_messages.ends_at_required'),
'ends_at.after' => __('validation.custom_messages.ends_at_after'),
'publish_at.required' => __('validation.custom_messages.publish_at_required'),
'closes_at.required' => __('validation.custom_messages.closes_at_required'),
'status.required' => __('validation.custom_messages.status_required'),
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
'hero_media_name.exists' => __('validation.custom_messages.hero_media_name_exists'),
'registration_data.required_unless' => __('validation.custom_messages.registration_data_required_unless'),
]);
$workshopData = $request->all();
if($workshopData['status'] === 'open' && Carbon::parse($workshopData['starts_at'])->lt(Carbon::now())) {
$workshopData['status'] = 'closed';
}
$workshop->update($workshopData);
$workshop->updateFiles($request->input('files'));
session()->flash('message', 'Workshop has been updated');
session()->flash('message-title', 'Workshop updated');
session()->flash('message-type', 'success');
return redirect()->route('admin.workshop.index');
}
/**
* Remove the specified resource from storage.
*/
public function admin_destroy(Workshop $workshop)
{
$workshop->delete();
session()->flash('message', 'Workshop has been deleted');
session()->flash('message-title', 'Workshop deleted');
session()->flash('message-type', 'danger');
return redirect()->route('admin.workshop.index');
}
/**
* Duplicate the specified resource.
*/
public function admin_duplicate(Workshop $workshop)
{
$newWorkshop = $workshop->replicate();
$newWorkshop->title = $newWorkshop->title . ' (copy)';
$newWorkshop->status = 'draft';
$newWorkshop->save();
foreach($workshop->files as $file) {
$newWorkshop->files()->attach($file->name);
}
session()->flash('message', 'Workshop has been duplicated');
session()->flash('message-title', 'Workshop duplicated');
session()->flash('message-type', 'success');
return redirect()->route('admin.workshop.edit', $newWorkshop);
}
}

68
app/Http/Kernel.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@@ -1,34 +0,0 @@
<?php
namespace App\Http\Middleware;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class Admin
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
/* @var User $user */
$user = Auth::user();
if ($user) {
if($user->admin == 1) {
return $next($request);
}
abort(403, 'Forbidden');
}
session()->put('url.intended', url()->current());
return redirect()->route('login');
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts(): array
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@@ -1,207 +0,0 @@
<?php
namespace App\Jobs\Media;
use App\Models\Media;
use App\Helpers;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFMpeg;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Imagick\Driver;
class GenerateVariants implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Media ID
*
* @var String
*/
public $media_name;
/**
* Overwrite existing
*
* @var bool
*/
public $overwrite;
/**
* Create a new job instance.
*
* @param Media $media The media to process
*/
public function __construct(Media $media, bool $overwrite = true)
{
$this->media_name = $media->name;
$this->overwrite = $overwrite;
}
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [new WithoutOverlapping($this->media_name)];
}
/**
* Execute the job.
*/
public function handle(): void
{
$media = Media::find($this->media_name);
if ($media === null) {
return;
}
if(Storage::disk('media')->exists($media->hash) === false) {
return;
}
$variantData = $media->getVariantTypes($matchingMimeType);
if(count($variantData) === 0) {
return;
}
$temp = $media->getAsTempFile();
if($temp === null) {
return;
}
$tempDir = pathinfo($temp, PATHINFO_DIRNAME);
$media->deleteAllVariants();
/* Images */
if($matchingMimeType === 'image/*') {
$manager = new ImageManager(new Driver());
$image = $manager->read($temp);
$isPortrait = $image->height() > $image->width();
foreach ($variantData as $variantName => $size) {
$image = $manager->read($temp);
if($isPortrait === true) {
$width = $size['height'];
$height = $size['width'];
} else {
$width = $size['width'];
$height = $size['height'];
}
if($variantName !== 'scaled' && ($image->height() < $height || $image->width() < $width)) {
continue;
}
$image->scaleDown($width, $height);
$variantFile = $tempDir . '/' . $media->hash . '-' . $variantName . '.webp';
$image->save($variantFile, quality: 75);
$media->addVariant($variantName, 'image/webp', 'webp', $variantFile);
unset($variantFile);
}//end foreach
} else if($matchingMimeType === 'text/plain') {
/* Text */
$width = $variantData['thumbnail']['width'];
$height = $variantData['thumbnail']['height'];
$manager = new ImageManager(new Driver());
$image = $manager->create($width, $height)->fill('fff');
// Read the first few lines of the text file
$numLines = 5;
$text = file_get_contents($temp);
$lines = explode("\n", $text);
$previewText = implode("\n", array_slice($lines, 0, $numLines));
// Center the text on the image
$fontSize = 8;
$textColor = '#000000'; // Black text color
// Calculate the position to start drawing the text
$x = 10; // Left padding
$y = 10; // Top padding
// Draw the text on the canvas with text wrapping
$lines = explode("\n", wordwrap($previewText, 30, "\n", true));
foreach ($lines as $line) {
$image->text($line, $x, $y, function ($font) use ($fontSize, $textColor) {
$font->file(1);
$font->size($fontSize);
$font->color($textColor);
});
// Move to the next line
$y += ($fontSize + 4); // Add some vertical spacing between lines (adjust as needed)
}
$variantFile = $tempDir . '/' . $media->hash . '-thumbnail.webp';
$image->save($variantFile, quality: 75);
$media->addVariant('thumbnail', 'image/webp', 'webp', $variantFile);
unset($variantFile);
} else if($matchingMimeType === 'application/pdf') {
/* PDF */
$width = $variantData['thumbnail']['width'];
$height = $variantData['thumbnail']['height'];
$manager = new ImageManager(new Driver());
$imagick = new \Imagick();
$imagick->readImage($temp . '[0]'); // Read the first page of the PDF
$imagick->setImageFormat('png');
$image = $manager->read($imagick);
$image->scaleDown($width, $height);
$variantFile = $tempDir . '/' . $media->hash . '-thumbnail.webp';
$image->save($variantFile, quality: 75);
$media->addVariant('thumbnail', 'image/webp', 'webp', $variantFile);
unset($variantFile);
} else if($matchingMimeType === 'video/*') {
/* Video */
$tempImage = $tempDir . '/' . $media->hash . '-temp-frame.jpg';
$variantFile = $tempDir . '/' . $media->hash . '-thumbnail.webp';
try {
$ffmpeg = FFMpeg::create();
$video = $ffmpeg->open($temp);
$frame = $video->frame(TimeCode::fromSeconds(5));
$frame->save($tempImage);
$width = $variantData['thumbnail']['width'];
$height = $variantData['thumbnail']['height'];
$manager = new ImageManager(new Driver());
$image = $manager->read($tempImage);
$image->scaleDown($width, $height);
$image->save($variantFile, quality: 75);
$media->addVariant('thumbnail', 'image/webp', 'webp', $variantFile);
unset($variantFile);
} catch (\Exception $e) {
Log::error($e);
}
if(file_exists($tempImage)) {
unlink($tempImage);
}
}
$media->status = 'ready';
$media->save();
}
}

View File

@@ -1,67 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\SentEmail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class SendEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Mail to receipt
*
* @var string
*/
public $to;
/**
* Mailable item
*
* @var Mailable
*/
public $mailable;
/**
* Create a new job instance.
*
* @param string $to The email receipient.
* @param Mailable $mailable The mailable.
* @return void
*/
public function __construct(string $to, Mailable $mailable)
{
$this->to = $to;
$this->mailable = $mailable;
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
// Record sent email
$sentEmail = SentEmail::create([
'recipient' => $this->to,
'mailable_class' => get_class($this->mailable)
]);
// Add unsubscribe link if mailable supports it
if (method_exists($this->mailable, 'withUnsubscribeLink')) {
$unsubscribeLink = route('unsubscribe', ['email' => $sentEmail->id]);
$this->mailable->withUnsubscribeLink($unsubscribeLink);
}
Mail::to($this->to)->send($this->mailable);
}
}

View File

@@ -1,104 +0,0 @@
<?php
namespace App\Livewire;
use App\Jobs\SendEmail;
use Carbon\Carbon;
use Livewire\Component;
use App\Models\EmailSubscriptions;
use App\Mail\UserWelcome;
class EmailSubscribe extends Component
{
public string $email = '';
public bool $success = false;
public string $message = '';
public string $trap = '';
public int $renderedAt; // unix timestamp
protected $rules = [
'email' => 'required|email|max:255',
];
public function mount()
{
$this->renderedAt = now()->timestamp;
}
public function subscribe(): void
{
$this->validate();
// 1. Honeypot - if this hidden field is filled, treat as success but do nothing
if (! empty($this->trap)) {
$this->reset(['email', 'trap']);
$this->success = true;
$this->message = 'Thanks, you have been subscribed to our newsletter.';
return;
}
// 2. Block submits in first 10 seconds after render
if (now()->timestamp - $this->renderedAt < 4) {
$this->success = false;
$this->message = 'That was a bit quick. Please wait a few seconds and try again.';
return;
}
// 3. Enforce 30 seconds between attempts per session
$lastAttempt = session('subscribe_last_attempt'); // int timestamp or null
if (! is_int($lastAttempt)) {
$lastAttempt = null;
}
$now = time();
if ($lastAttempt && ($now - $lastAttempt) < 20) {
$this->success = false;
$this->message = 'Please wait a little before trying again.';
return;
}
session(['subscribe_last_attempt' => $now]);
// 4. Limit to 5 attempts per session (your existing logic)
$attempts = session('subscribe_attempts', 0);
if ($attempts >= 5) {
$this->success = false;
$this->message = 'Too many attempts. Please try again in a little while.';
return;
}
session(['subscribe_attempts' => $attempts + 1]);
// Look up existing subscription by email
$subscription = EmailSubscriptions::where('email', $this->email)->first();
// If already confirmed, do not create a new record or resend confirmation
if ($subscription && $subscription->confirmed) {
// Optionally you could set a different flag or message here
$this->success = false;
$this->message = 'That email is already subscribed to our newsletter.';
} else {
// If no subscription exists, create a new unconfirmed one
if (!$subscription) {
$subscription = EmailSubscriptions::create([
'email' => $this->email,
'confirmed' => Carbon::now()
]);
$subscription->save();
}
dispatch(new SendEmail($subscription->email, new UserWelcome($subscription->email)))->onQueue('mail');
$this->success = true;
$this->message = 'Thanks, you have been subscribed to our newsletter.';
}
$this->reset(['email', 'trap']);
}
public function render()
{
return view('livewire.email-subscribe');
}
}

View File

@@ -1,58 +0,0 @@
<?php
namespace App\Mail;
use App\Models\Workshop;
use App\Traits\HasUnsubscribeLink;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
class UpcomingWorkshops extends Mailable
{
use Queueable, SerializesModels, HasUnsubscribeLink;
public $subject;
public $email;
public $workshops;
public function __construct($email, $subject = 'Upcoming Workshops 🌟')
{
$this->subject = $subject;
$this->email = $email;
$this->workshops = $this->getUpcomingWorkshops();
}
private function getUpcomingWorkshops()
{
$startDate = Carbon::now()->addDays(3);
$endDate = Carbon::now()->addDays(42);
return Workshop::select('workshops.*', 'locations.name as location_name')
->join('locations', 'workshops.location_id', '=', 'locations.id')
->whereIn('workshops.status', ['open','scheduled'])
->whereBetween('workshops.starts_at', [$startDate, $endDate])
->where('locations.name', 'not like', '%private%')
->orderBy('locations.name')
->orderBy('workshops.starts_at')
->get();
}
public function build()
{
// Bail if there are no upcoming workshops
if ($this->workshops->isEmpty()) {
return false;
}
return $this
->subject($this->subject)
->markdown('emails.upcoming-workshops')
->with([
'email' => $this->email,
'workshops' => $this->workshops,
'unsubscribeLink' => $this->unsubscribeLink
]);
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserEmailUpdateConfirm extends Mailable
{
use Queueable, SerializesModels;
public $email;
public function __construct($email)
{
$this->email = $email;
}
public function build()
{
return $this
->subject('Your STEMMechanics account has been updated 👍')
->markdown('emails.email-update-confirm')
->with([
'email' => $this->email,
]);
}
}

View File

@@ -1,36 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserEmailUpdateRequest extends Mailable
{
use Queueable, SerializesModels;
public $token;
public $email;
public $newEmail;
public function __construct($token, $email, $newEmail)
{
$this->token = $token;
$this->email = $email;
$this->newEmail = $newEmail;
}
public function build()
{
return $this
->subject('Almost There! Confirm Your New Email Address 👍')
->markdown('emails.email-update-request')
->with([
'update_url' => route('update.email', ['token' => $this->token]),
'email' => $this->email,
'newEmail' => $this->newEmail,
]);
}
}

View File

@@ -1,36 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserLogin extends Mailable
{
use Queueable, SerializesModels;
public $token;
public $username;
public $email;
public function __construct($token, $username, $email)
{
$this->token = $token;
$this->username = $username;
$this->email = $email;
}
public function build()
{
return $this
->subject('Here\'s your login link 🤫')
->markdown('emails.login')
->with([
'login_url' => route('login', ['token' => $this->token]),
'username' => $this->username,
'email' => $this->email,
]);
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace App\Mail;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Spatie\LaravelPdf\Facades\Pdf;
class UserLoginBackupCode extends Mailable
{
use Queueable, SerializesModels;
public $email;
public function __construct($email)
{
$this->email = $email;
}
public function build()
{
return $this
->subject('Hey, did you recently log in?')
->markdown('emails.login-backup-code')
->with([
'email' => $this->email,
]);
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace App\Mail;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Spatie\LaravelPdf\Facades\Pdf;
class UserLoginTFADisabled extends Mailable
{
use Queueable, SerializesModels;
public $email;
public function __construct($email)
{
$this->email = $email;
}
public function build()
{
return $this
->subject('Two-factor authentication disabled on your account')
->markdown('emails.login-tfa-disabled')
->with([
'email' => $this->email,
]);
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace App\Mail;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Spatie\LaravelPdf\Facades\Pdf;
class UserLoginTFAEnabled extends Mailable
{
use Queueable, SerializesModels;
public $email;
public function __construct($email)
{
$this->email = $email;
}
public function build()
{
return $this
->subject('Two-factor authentication enabled on your account')
->markdown('emails.login-tfa-enabled')
->with([
'email' => $this->email,
]);
}
}

View File

@@ -1,33 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserRegister extends Mailable
{
use Queueable, SerializesModels;
public $token;
public $email;
public function __construct($token, $email)
{
$this->token = $token;
$this->email = $email;
}
public function build()
{
return $this
->subject('Almost There! Just One More Step to Join Us 🚀')
->markdown('emails.register')
->with([
'register_url' => route('register', ['token' => $this->token]),
'email' => $this->email,
]);
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace App\Mail;
use App\Traits\HasUnsubscribeLink;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserWelcome extends Mailable
{
use Queueable, SerializesModels, HasUnsubscribeLink;
public $email;
public function __construct($email)
{
$this->email = $email;
}
public function build()
{
return $this
->subject('Welcome to STEMMechanics 🌟')
->markdown('emails.welcome')
->with([
'email' => $this->email,
'unsubscribeLink' => $this->unsubscribeLink
]);
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class EmailSubscriptions extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'email',
'confirmed'
];
}

View File

@@ -1,14 +0,0 @@
<?php
namespace App\Models;
use App\Traits\UUID;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
use HasFactory, UUID;
protected $fillable = ['name', 'address', 'address_url', 'url'];
}

View File

@@ -1,404 +0,0 @@
<?php
namespace App\Models;
use App\Helpers;
use App\Jobs\Media\GenerateVariants;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use function PHPUnit\Framework\stringStartsWith;
class Media extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'title',
'mime_type',
'size',
'user_id',
'hash',
'password',
'status'
];
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = 'name';
/**
* The key type for the model.
*
* @var string
*/
protected $keyType = 'string';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'variants' => 'array'
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected $appends = [
'url',
'thumbnail',
'file_type'
];
/**
* Media variant details.
*
* @var int[][][]
*/
protected static $variants = [
'image/*' => [
'thumbnail' => ['width' => 250, 'height' => 250],
'sm' => ['width' => 300, 'height' => 225],
'md' => ['width' => 768, 'height' => 576],
'lg' => ['width' => 1024, 'height' => 768],
'xl' => ['width' => 1536, 'height' => 1152],
'2xl' => ['width' => 2048, 'height' => 1536],
'scaled' => ['width' => 2560, 'height' => 1920]
],
'text/plain' => [
'thumbnail' => ['width' => 250, 'height' => 250]
],
'application/pdf' => [
'thumbnail' => ['width' => 250, 'height' => 250]
],
'video/*' => [
'thumbnail' => ['width' => 250, 'height' => 250]
],
];
public static function boot()
{
parent::boot();
static::deleting(function($media) {
$hash = $media->hash;
if(Media::where('hash', $hash)->count() > 1) {
return;
}
$disk = Storage::disk('media');
if($disk->exists($hash)) {
$disk->delete($hash);
}
$media->deleteAllVariants();
});
}
/**
* Get the URL of the media.
*/
public function getUrlAttribute(): string
{
return Storage::disk('media')->url($this->name);
}
public function url($variant, $strict = false): string
{
if(!$strict) {
$data = $this->getClosestVariant($variant);
} else {
if($this->variants === null || !array_key_exists($variant, $this->variants)) {
return '';
}
$data = [
'variant' => $variant,
'name' => pathinfo($this->name, PATHINFO_FILENAME) . '-' . $variant . '.' . $this->variants[$variant]['extension'],
'mime_type' => $this->variants[$variant]['mime_type'],
'file' => $this->path() . '-' . $variant
];
}
return Storage::disk('media')->url($this->name) . ($data['variant'] !== '' ? '?' . $data['variant'] : '');
}
/**
* Get the thumbnail of the media.
*/
public function getThumbnailAttribute(): string
{
if($this->password === null || Auth::user()?->isAdmin()) {
if ($this->hasVariant('thumbnail')) {
$url = $this->url('thumbnail', true);
if ($url !== '') {
return $url;
}
}
}
$thumbnail = '/thumbnails/' . pathinfo($this->name, PATHINFO_EXTENSION) . '.webp';
if(file_exists(public_path($thumbnail))) {
return asset($thumbnail);
}
return asset('/thumbnails/unknown.webp');
}
public function getFileTypeAttribute(): string
{
$extension = strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
if(str_starts_with($this->mime_type, 'image/')) {
return 'Image (' . $extension . ')';
} else if(str_starts_with($this->mime_type, 'video/')) {
return 'Video (' . $extension . ')';
} else if(str_starts_with($this->mime_type, 'audio/')) {
return 'Audio (' . $extension . ')';
} else if($this->mime_type === 'application/pdf') {
return 'PDF Document';
} else if($this->mime_type === 'text/plain') {
return 'Text Document';
} else if($extension === 'sb3') {
return 'Scratch 3 Project';
} else if($extension === 'stopmotionstudio' || $extension === 'stopmotionstudiomobile') {
return 'Stop Motion Studio Project';
}
return 'File (' . $extension . ')';
}
/**
* Get the user that owns the media.
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* Get all the models attached to the media.
*/
public function mediable()
{
return $this->morphTo();
}
/**
* Get the media as a temp file.
*
* @return string|null The temporary file path or null.
*/
public function getAsTempFile(): string|null
{
if($this->hash === null) {
return null;
}
$file = tempnam(sys_get_temp_dir(), 'media_');
$disk = Storage::disk('media');
if($disk->exists($this->hash) === false) {
return null;
}
$stream = $disk->getDriver()->readStream($this->hash);
is_resource($stream) && file_put_contents($file, stream_get_contents($stream), FILE_APPEND);
return $file;
}
/**
* Set the media from a file.
*
* @param string $file The file to set.
*/
public function storeFromTempFile(string $file): void
{
Storage::disk('media')->put($this->name, fopen($file, 'r+'));
}
/**
* Generate variants for this media.
*
* @return void
*/
public function generateVariants(bool $overwrite = true): void
{
$this->status = 'processing';
$this->save();
dispatch(new GenerateVariants($this, $overwrite))->onQueue('media');
}
public function path(): string|null
{
$disk = Storage::disk('media');
if(!$disk->exists($this->hash)) {
return null;
}
return $disk->path($this->hash);
}
/**
* Add a variant to the media.
*
* @param string $name The name of the variant.
* @param string $mime_type The mime type of the variant.
* @param string $file The file to store.
*
* @return void
*/
public function addVariant(string $name, string $mime_type, string $extension, string $file): void
{
$name = strtolower($name);
$storage = Storage::disk('media');
if (isset($this->variants[$name])) {
if ($storage->exists($this->hash . '-' . $name)) {
$storage->delete($this->hash . '-_' . $name);
}
}
$storage->putFileAs('/', $file, $this->hash . '-' . $name);
$variants = $this->variants;
$variants[$name] = [
'mime_type' => $mime_type,
'extension' => $extension
];
$this->variants = $variants;
$this->save();
}
/**
* Does a variant of the media exist.
*
* @param string $variant The variant to check.
*
* @return bool True if the variant exists, false otherwise.
*/
public function hasVariant($variant): bool
{
$variant = strtolower($variant);
$storage = Storage::disk('media');
return $storage->exists($this->hash . '-' . $variant);
}
/**
* Delete a variant of the media.
*
* @param string $variant The variant to delete.
*
* @return void
*/
public function deleteVariant($variant): void
{
$variant = strtolower($variant);
$storage = Storage::disk('media');
if(isset($this->variants[$variant])) {
if($storage->exists($this->hash . '-' . $variant)) {
$storage->delete($this->hash . '-' . $variant);
}
}
unset($this->variants[$variant]);
$this->save();
}
/**
* Delete all variants of the media.
*
* @return void
*/
public function deleteAllVariants(): void
{
$storage = Storage::disk('media');
if($this->variants === null) {
return;
}
foreach($this->variants as $variant => $file) {
if($storage->exists($this->hash . '-' . $variant)) {
$storage->delete($this->hash . '-' . $variant);
}
}
$this->variants = null;
$this->save();
}
/**
* Get the variant types for the media.
*
* @param string $matchingKey The matching key.
*
* @return array The variant types.
*/
public function getVariantTypes(&$matchingKey = null)
{
$key = Helpers::findMatchingMimeTypeKey($this->mime_type, Media::$variants);
if($key === false) {
$matchingKey = null;
return [];
}
$matchingKey = $key;
return Media::$variants[$key];
}
public function getClosestVariant($key)
{
$variants = $this->getVariantTypes();
if($this->variants && count($variants) > 0) {
$found = false;
foreach ($variants as $variant => $data) {
if($variant === $key) {
$found = true;
}
if($found && array_key_exists($variant, $this->variants)) {
return [
'variant' => $variant,
'name' => pathinfo($this->name, PATHINFO_FILENAME) . '-' . $variant . '.' . $this->variants[$variant]['extension'],
'mime_type' => $this->variants[$variant]['mime_type'],
'file' => $this->path() . '-' . $variant
];
}
}
}
return [
'variant' => null,
'name' => $this->name,
'mime_type' => $this->mime_type,
'file' => $this->path()
];
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace App\Models;
use App\Helpers;
use App\Traits\HasFiles;
use App\Traits\Slug;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory, Slug, HasFiles;
protected $fillable = ['title', 'content', 'user_id', 'status', 'published_at', 'hero_media_name'];
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
public function hero()
{
return $this->belongsTo(Media::class, 'hero_media_name');
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class SentEmail extends Model
{
protected $fillable = ['recipient', 'mailable_class'];
public $incrementing = false;
protected $keyType = 'string';
/**
* Boot function from Laravel.
*
* @return void
*/
protected static function boot(): void
{
parent::boot();
static::creating(function ($model) {
if (empty($model->{$model->getKeyName()}) === true) {
$model->{$model->getKeyName()} = strtolower(Str::random(15));
}
});
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model
{
use HasFactory;
}

View File

@@ -1,87 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Token extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'user_id',
'type',
'data',
'expires_at',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'expires_at' => 'datetime',
'data' => 'array',
];
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
/**
* The primary key for the model is incrementing.
*
* @var bool $incrementing
*/
public $incrementing = false;
/**
* The primary key type for the model.
*
* @var string
*/
public $keyType = 'string';
/**
* The "booted" method of the model.
*
* @return void
*/
public static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->{$model->getKeyName()}) === true) {
do {
$newToken = Str::random(48);
} while (self::where($model->getKeyName(), $newToken)->exists());
$model->{$model->getKeyName()} = $newToken;
}
if (empty($model->expires_at) === true) {
$model->expires_at = now()->addMinutes(10);
}
});
}
/**
* Get the user that the token belongs to.
*
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -2,20 +2,15 @@
namespace App\Models;
use App\Jobs\SendEmail;
use App\Mail\UserLoginTFADisabled;
use App\Mail\UserLoginTFAEnabled;
use App\Traits\UUID;
use Illuminate\Contracts\Auth\MustVerifyEmail;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
class User extends Authenticatable
{
use HasFactory, Notifiable, UUID;
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
@@ -23,26 +18,9 @@ class User extends Authenticatable implements MustVerifyEmail
* @var array<int, string>
*/
protected $fillable = [
'admin',
'firstname',
'surname',
'name',
'email',
'phone',
'shipping_address',
'shipping_address2',
'shipping_city',
'shipping_postcode',
'shipping_state',
'shipping_country',
'billing_address',
'billing_address2',
'billing_city',
'billing_postcode',
'billing_state',
'billing_country',
'subscribed',
'tfa_secret',
'agree_tos',
'password',
];
/**
@@ -53,177 +31,15 @@ class User extends Authenticatable implements MustVerifyEmail
protected $hidden = [
'password',
'remember_token',
'tfa_secret'
];
/**
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected $appends = [
'subscribed',
'email_update_pending'
];
public static function boot()
{
parent::boot();
static::updating(function ($user) {
if ($user->isDirty('email')) {
EmailSubscriptions::where('email', $user->getOriginal('email'))->update(['email' => $user->email]);
// remove duplicate email subscriptions, favoring those with confirmed dates
$subscriptions = EmailSubscriptions::where('email', $user->email)->orderBy('created_at', 'asc')->get();
$confirmed = EmailSubscriptions::where('email', $user->email)->whereNotNull('confirmed')->orderBy('confirmed', 'asc')->first();
if($subscriptions->count() > 1) {
// if there is a confirmed, then delete all the others
if($confirmed) {
$subscriptions->each(function($subscription) use ($confirmed) {
if($subscription->id !== $confirmed->id) {
$subscription->delete();
}
});
} else {
// if there is no confirmed, then delete all but the most recent
$subscriptions->each(function($subscription) use ($subscriptions) {
if($subscription->id !== $subscriptions->last()->id) {
$subscription->delete();
}
});
}
}
}
if ($user->isDirty('tfa_secret')) {
if($user->tfa_secret === null) {
$user->backupCodes()->delete();
dispatch(new SendEmail($user->email, new UserLoginTFADisabled($user->email)))->onQueue('mail');
} else {
dispatch(new SendEmail($user->email, new UserLoginTFAEnabled($user->email)))->onQueue('mail');
}
}
});
static::deleting(function ($user) {
EmailSubscriptions::where('email', $user->email)->delete();
});
}
/**
* Get the tokens for the user.
*
* @return HasMany
*/
public function tokens(): HasMany
{
return $this->hasMany(Token::class);
}
/**
* Get the calculated name of the user.
*
* @return string
*/
public function getName(): string
{
$name = '';
if($this->firstname || $this->surname) {
$name = implode(' ', [$this->firstname, $this->surname]);
} else {
$name = substr($this->email, 0, strpos($this->email, '@'));
}
return $name;
}
public function tickets()
{
return $this->hasMany(Ticket::class);
}
public function getSubscribedAttribute()
{
return EmailSubscriptions::where('email', $this->email)
->whereNotNull('confirmed')
->exists();
}
public function setSubscribedAttribute($value)
{
if ($value) {
$subscription = EmailSubscriptions::where('email', $this->email)->first();
if ($subscription) {
if($subscription->confirmed === null) {
$subscription->update(['confirmed' => now()]);
$subscription->save();
}
} else {
EmailSubscriptions::Create([
'email' => $this->email,
'confirmed' => now()
]);
}
} else {
EmailSubscriptions::where('email', $this->email)->delete();
}
}
public function getEmailUpdatePendingAttribute()
{
$emailUpdate = $this->tokens()->where('type', 'email-update')->where('expires_at', '>', now())->first();
return $emailUpdate ? $emailUpdate->data['email'] : null;
}
public function isAdmin(): bool
{
return $this->admin === 1;
}
public function backupCodes()
{
return $this->hasMany(UserBackupCode::class);
}
public function generateBackupCodes()
{
$this->backupCodes()->delete();
$codes = [];
for ($i = 0; $i < 10; $i++) {
$code = strtoupper(bin2hex(random_bytes(4)));
$codes[] = $code;
UserBackupCode::create([
'user_id' => $this->id,
'code' => $code,
]);
}
return $codes;
}
public function verifyBackupCode($code)
{
$backupCodes = $this->backupCodes()->get();
foreach ($backupCodes as $backupCode) {
if (Hash::check($code, $backupCode->code)) {
$backupCode->delete();
return true;
}
}
return false;
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
class UserBackupCode extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'user_id',
'code'
];
/**
* Set the code attribute and automatically hash the code.
*
* @param string $value
* @return void
*/
public function setCodeAttribute($value)
{
$this->attributes['code'] = Hash::make($value);
}
/**
* Verify the given code against the stored hashed code.
*
* @param string $value
* @return bool
*/
public function verify($value)
{
return Hash::check($value, $this->code);
}
}

View File

@@ -1,52 +0,0 @@
<?php
namespace App\Models;
use App\Traits\HasFiles;
use App\Traits\Slug;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Workshop extends Model
{
use HasFactory, Slug, HasFiles;
protected $fillable = [
'title',
'content',
'starts_at',
'ends_at',
'publish_at',
'closes_at',
'status',
'price',
'ages',
'registration',
'registration_data',
'location_id',
'user_id',
'hero_media_name'
];
protected $casts = [
'starts_at' => 'datetime',
'ends_at' => 'datetime',
'publish_at' => 'datetime',
'closes_at' => 'datetime',
];
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
public function hero()
{
return $this->belongsTo(Media::class, 'hero_media_name');
}
public function location()
{
return $this->belongsTo(Location::class, 'location_id');
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -21,15 +19,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
Blade::directive('includeSVG', function ($arguments) {
list($path, $styles) = array_pad(explode(',', str_replace(['(', ')', ' ', "'"], '', $arguments), 2), 2, '');
$svgContent = file_get_contents(public_path($path));
$svgContent = str_replace('<svg ', '<svg style="'.$styles.'" ', $svgContent);
return $svgContent;
});
//
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Providers;
// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
//
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
//
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@@ -1,62 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
class CaptchaServiceProvider extends ServiceProvider
{
private string $captchaKey = '6Lc6BIAUAAAAAABZzv6J9ZQ7J9Zzv6J9ZQ7J9Zzv';
private int $timeThreshold = 750;
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
Blade::directive('captcha', function () {
return <<<EOT
<input type="text" name="captcha" autocomplete="off" style="position:absolute;left:-9999px;top:-9999px">
<script>
document.addEventListener('DOMContentLoaded', function() {
const errors = {!! json_encode(\$errors->getMessages()) !!};
if(errors && errors.captcha && errors.captcha.length) {
SM.alert('', errors.captcha[0], 'danger');
}
});
</script>
EOT;
});
Blade::directive('captchaScripts', function () {
return <<<EOT
<script>
document.addEventListener('DOMContentLoaded', function() {
window.setTimeout(function() {
const captchaList = document.querySelectorAll('input[name="captcha"]');
captchaList.forEach(function(captcha) {
if(captcha.value === '') {
captcha.value = '$this->captchaKey';
}
});
}, $this->timeThreshold);
});
</script>
EOT;
});
Validator::extend('required_captcha', function ($attribute, $value, $parameters, $validator) {
return $value === $this->captchaKey;
}, 'The form captcha failed to validate. Please try again.');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*/
public function boot(): void
{
//
}
/**
* Determine if events and listeners should be automatically discovered.
*/
public function shouldDiscoverEvents(): bool
{
return false;
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace App\Providers;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
class QRCodeProvider implements IQRCodeProvider
{
public function getMimeType(): string
{
return 'image/svg+xml';
}
public function getQRCodeImage(string $qrText, int $size): string
{
$options = new QROptions;
$options->outputBase64 = false;
$options->imageTransparent = true;
return (new QRCode($options))->render($qrText);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to your application's "home" route.
*
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, and other route configuration.
*/
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace App\Traits;
use App\Helpers;
use App\Models\Media;
use Illuminate\Support\Str;
trait HasFiles
{
public function files($collection = null)
{
// return $this->morphToMany(Media::class, 'mediable')
// ->wherePivot('collection', $collection);
return $this->morphToMany(Media::class, 'mediable')
->selectRaw("*, CASE WHEN password IS NULL THEN NULL ELSE 'yes' END AS password")
->wherePivot('collection', $collection);
}
public function updateFiles($files, $collection = null): void
{
if($files === null) {
$files = '';
}
if (is_string($files)) {
$files = Helpers::stringToArray($files);
}
if (is_array($files)) {
// Remove duplicates from the array
$files = array_unique($files);
// Detach all existing attachments
$this->files($collection)->detach();
foreach ($files as $fileName) {
$media = Media::find($fileName);
if ($media) {
$this->files($collection)->attach($media->name, ['collection' => $collection]);
}
}
}
}
}

View File

@@ -1,14 +0,0 @@
<?php
namespace App\Traits;
trait HasUnsubscribeLink
{
protected $unsubscribeLink;
public function withUnsubscribeLink($link)
{
$this->unsubscribeLink = $link;
return $this;
}
}

View File

@@ -1,88 +0,0 @@
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait Slug
{
protected $appendsSlug = ['slug'];
/**
* Boot function from Laravel.
*
* @return void
*/
protected static function bootSlug(): void
{
static::creating(function ($model) {
if (empty($model->{$model->getKeyName()}) === true) {
$model->{$model->getKeyName()} = strtolower(Str::random(11));
}
});
}
/**
* Initialize the trait.
*
* @return void
*/
public function initializeSlug(): void
{
$this->appends = array_merge($this->appends ?? [], $this->appendsSlug);
}
/**
* Get the value indicating whether the IDs are incrementing.
*
* @return boolean
*/
public function getIncrementing(): bool
{
return false;
}
/**
* Get the auto-incrementing key type.
*
* @return string
*/
public function getKeyType(): string
{
return 'string';
}
/**
* Get the route key for the model.
*
* @return string
*/
public function getRouteKey()
{
return $this->slug;
}
/**
* Resolve a route binding.
*
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
$id = last(explode('-', $value));
return $this->findOrFail($id);
}
/**
* Get the slug attribute.
*
* @return string
*/
public function getSlugAttribute()
{
return Str::slug($this->title) . '-' . $this->id;
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait UUID
{
/**
* Boot function from Laravel.
*
* @return void
*/
protected static function bootUUID(): void
{
static::creating(function ($model) {
if (empty($model->{$model->getKeyName()}) === true) {
$model->{$model->getKeyName()} = Str::uuid()->toString();
}
});
}
/**
* Get the value indicating whether the IDs are incrementing.
*
* @return boolean
*/
public function getIncrementing(): bool
{
return false;
}
/**
* Get the auto-incrementing key type.
*
* @return string
*/
public function getKeyType(): string
{
return 'string';
}
}

50
artisan Normal file → Executable file
View File

@@ -1,15 +1,53 @@
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any of our classes manually. It's great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
$status = (require_once __DIR__.'/bootstrap/app.php')
->handleCommand(new ArgvInput);
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);

View File

@@ -1,19 +1,55 @@
<?php
use App\Http\Middleware\Admin;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias(['admin' => Admin::class]);
})
->withExceptions(function (Exceptions $exceptions) {
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
})->create();
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

View File

@@ -1,6 +0,0 @@
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\CaptchaServiceProvider::class,
];

View File

@@ -5,25 +5,21 @@
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.4",
"ext-imagick": "*",
"chillerlan/php-qrcode": "^5.0",
"gehrisandro/tailwind-merge-laravel": "^1.2",
"intervention/image": "^3.5",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.9",
"livewire/livewire": "^4.1",
"php-ffmpeg/php-ffmpeg": "^1.2",
"robthree/twofactorauth": "^3.0"
"php": "^8.1",
"guzzlehttp/guzzle": "^7.2",
"itsgoingd/clockwork": "^5.1",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
"fakerphp/faker": "^1.23",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"phpunit/phpunit": "^12.5",
"spatie/laravel-ignition": "^2.4"
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
},
"autoload": {
"psr-4": {
@@ -49,15 +45,7 @@
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pint": [
"./vendor/bin/pint"
],
"pint-test": [
"./vendor/bin/pint --test"
"@php artisan key:generate --ansi"
]
},
"extra": {
@@ -66,9 +54,6 @@
}
},
"config": {
"platform": {
"php": "8.4.17"
},
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,

6009
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,8 @@
<?php
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
return [
/*
@@ -7,9 +10,9 @@ return [
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
| any other location as required by the application or its packages.
|
*/
@@ -48,24 +51,26 @@ return [
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => env('APP_TIMEZONE', 'UTC'),
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
@@ -73,37 +78,53 @@ return [
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'locale' => 'en',
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', env('APP_PREVIOUS_KEYS', ''))
),
],
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
@@ -119,9 +140,49 @@ return [
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
'driver' => 'file',
// 'store' => 'redis',
],
'notice' => env('APP_NOTICE', '')
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => ServiceProvider::defaultProviders()->merge([
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
])->toArray(),
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => Facade::defaultAliases()->merge([
// 'Example' => App\Facades\Example::class,
])->toArray(),
];

View File

@@ -7,15 +7,15 @@ return [
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
'guard' => 'web',
'passwords' => 'users',
],
/*
@@ -25,11 +25,11 @@ return [
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
| here which uses session storage and the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
@@ -47,12 +47,12 @@ return [
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
@@ -62,7 +62,7 @@ return [
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
'model' => App\Models\User::class,
],
// 'users' => [
@@ -76,9 +76,9 @@ return [
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
@@ -93,7 +93,7 @@ return [
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
@@ -105,11 +105,11 @@ return [
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
'password_timeout' => 10800,
];

71
config/broadcasting.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

View File

@@ -9,13 +9,13 @@ return [
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_STORE', 'database'),
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
@@ -26,13 +26,17 @@ return [
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane", "null"
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
'serialize' => false,
@@ -40,9 +44,9 @@ return [
'database' => [
'driver' => 'database',
'table' => env('DB_CACHE_TABLE', 'cache'),
'connection' => env('DB_CACHE_CONNECTION'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'table' => 'cache',
'connection' => null,
'lock_connection' => null,
],
'file' => [
@@ -72,8 +76,8 @@ return [
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
'connection' => 'cache',
'lock_connection' => 'default',
],
'dynamodb' => [
@@ -96,8 +100,8 @@ return [
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
| stores there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/

34
config/cors.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

View File

@@ -10,22 +10,26 @@ return [
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
@@ -33,7 +37,7 @@ return [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
@@ -41,35 +45,15 @@ return [
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
@@ -81,13 +65,13 @@ return [
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
@@ -96,13 +80,13 @@ return [
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
@@ -118,14 +102,11 @@ return [
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
@@ -134,7 +115,7 @@ return [
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/

View File

@@ -9,7 +9,7 @@ return [
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
| based disks are available to your application. Just store away!
|
*/
@@ -20,9 +20,9 @@ return [
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been set up for each driver as an example of the required values.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
@@ -36,24 +36,25 @@ return [
'throw' => false,
],
'media' => [
'public' => [
'driver' => 'local',
'root' => storage_path('app/media'),
'url' => env('APP_URL').'/media/download',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
// 's3' => [
// 'driver' => 's3',
// 'key' => env('AWS_ACCESS_KEY_ID'),
// 'secret' => env('AWS_SECRET_ACCESS_KEY'),
// 'region' => env('AWS_DEFAULT_REGION'),
// 'bucket' => env('AWS_BUCKET'),
// 'url' => env('AWS_URL'),
// 'endpoint' => env('AWS_ENDPOINT'),
// 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
// 'throw' => false,
// ],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
],
@@ -69,7 +70,7 @@ return [
*/
'links' => [
// public_path('media') => storage_path('app/media'),
public_path('storage') => storage_path('app/public'),
],
];

View File

@@ -1,80 +0,0 @@
<?php
use Spatie\FlareClient\FlareMiddleware\AddGitInformation;
use Spatie\FlareClient\FlareMiddleware\RemoveRequestIp;
use Spatie\FlareClient\FlareMiddleware\CensorRequestBodyFields;
use Spatie\FlareClient\FlareMiddleware\CensorRequestHeaders;
use Spatie\LaravelIgnition\FlareMiddleware\AddDumps;
use Spatie\LaravelIgnition\FlareMiddleware\AddEnvironmentInformation;
use Spatie\LaravelIgnition\FlareMiddleware\AddExceptionInformation;
use Spatie\LaravelIgnition\FlareMiddleware\AddJobs;
use Spatie\LaravelIgnition\FlareMiddleware\AddLogs;
use Spatie\LaravelIgnition\FlareMiddleware\AddQueries;
use Spatie\LaravelIgnition\FlareMiddleware\AddNotifierName;
return [
/*
|
|--------------------------------------------------------------------------
| Flare API key
|--------------------------------------------------------------------------
|
| Specify Flare's API key below to enable error reporting to the service.
|
| More info: https://flareapp.io/docs/general/projects
|
*/
'key' => env('FLARE_KEY'),
/*
|--------------------------------------------------------------------------
| Middleware
|--------------------------------------------------------------------------
|
| These middleware will modify the contents of the report sent to Flare.
|
*/
'flare_middleware' => [
RemoveRequestIp::class,
AddGitInformation::class,
AddNotifierName::class,
AddEnvironmentInformation::class,
AddExceptionInformation::class,
AddDumps::class,
AddLogs::class => [
'maximum_number_of_collected_logs' => 200,
],
AddQueries::class => [
'maximum_number_of_collected_queries' => 200,
'report_query_bindings' => true,
],
AddJobs::class => [
'max_chained_job_reporting_depth' => 5,
],
CensorRequestBodyFields::class => [
'censor_fields' => [
'password',
'password_confirmation',
],
],
CensorRequestHeaders::class => [
'headers' => [
'API-KEY',
]
]
],
/*
|--------------------------------------------------------------------------
| Reporting log statements
|--------------------------------------------------------------------------
|
| If this setting is `false` log statements won't be sent as events to Flare,
| no matter which error level you specified in the Flare log channel.
|
*/
'send_logs_as_events' => true,
];

54
config/hashing.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon", "argon2id"
|
*/
'driver' => 'bcrypt',
/*
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12),
'verify' => true,
],
/*
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'argon' => [
'memory' => 65536,
'threads' => 1,
'time' => 4,
'verify' => true,
],
];

View File

@@ -1,277 +0,0 @@
<?php
use Spatie\Ignition\Solutions\SolutionProviders\BadMethodCallSolutionProvider;
use Spatie\Ignition\Solutions\SolutionProviders\MergeConflictSolutionProvider;
use Spatie\Ignition\Solutions\SolutionProviders\UndefinedPropertySolutionProvider;
use Spatie\LaravelIgnition\Recorders\DumpRecorder\DumpRecorder;
use Spatie\LaravelIgnition\Recorders\JobRecorder\JobRecorder;
use Spatie\LaravelIgnition\Recorders\LogRecorder\LogRecorder;
use Spatie\LaravelIgnition\Recorders\QueryRecorder\QueryRecorder;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\DefaultDbNameSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\GenericLaravelExceptionSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\IncorrectValetDbCredentialsSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\InvalidRouteActionSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingAppKeySolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingColumnSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingImportSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingLivewireComponentSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingMixManifestSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\MissingViteManifestSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\RunningLaravelDuskInProductionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\TableNotFoundSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\UndefinedViewVariableSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\UnknownValidationSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\ViewNotFoundSolutionProvider;
use Spatie\LaravelIgnition\Solutions\SolutionProviders\OpenAiSolutionProvider;
return [
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking any edit button.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug", "phpstorm-remote"
|
*/
'editor' => env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Theme
|--------------------------------------------------------------------------
|
| Here you may specify which theme Ignition should use.
|
| Supported: "light", "dark", "auto"
|
*/
'theme' => env('IGNITION_THEME', 'auto'),
/*
|--------------------------------------------------------------------------
| Sharing
|--------------------------------------------------------------------------
|
| You can share local errors with colleagues or others around the world.
| Sharing is completely free and doesn't require an account on Flare.
|
| If necessary, you can completely disable sharing below.
|
*/
'enable_share_button' => env('IGNITION_SHARING_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Register Ignition commands
|--------------------------------------------------------------------------
|
| Ignition comes with an additional make command that lets you create
| new solution classes more easily. To keep your default Laravel
| installation clean, this command is not registered by default.
|
| You can enable the command registration below.
|
*/
'register_commands' => env('REGISTER_IGNITION_COMMANDS', false),
/*
|--------------------------------------------------------------------------
| Solution Providers
|--------------------------------------------------------------------------
|
| You may specify a list of solution providers (as fully qualified class
| names) that shouldn't be loaded. Ignition will ignore these classes
| and possible solutions provided by them will never be displayed.
|
*/
'solution_providers' => [
// from spatie/ignition
BadMethodCallSolutionProvider::class,
MergeConflictSolutionProvider::class,
UndefinedPropertySolutionProvider::class,
// from spatie/laravel-ignition
IncorrectValetDbCredentialsSolutionProvider::class,
MissingAppKeySolutionProvider::class,
DefaultDbNameSolutionProvider::class,
TableNotFoundSolutionProvider::class,
MissingImportSolutionProvider::class,
InvalidRouteActionSolutionProvider::class,
ViewNotFoundSolutionProvider::class,
RunningLaravelDuskInProductionProvider::class,
MissingColumnSolutionProvider::class,
UnknownValidationSolutionProvider::class,
MissingMixManifestSolutionProvider::class,
MissingViteManifestSolutionProvider::class,
MissingLivewireComponentSolutionProvider::class,
UndefinedViewVariableSolutionProvider::class,
GenericLaravelExceptionSolutionProvider::class,
OpenAiSolutionProvider::class,
],
/*
|--------------------------------------------------------------------------
| Ignored Solution Providers
|--------------------------------------------------------------------------
|
| You may specify a list of solution providers (as fully qualified class
| names) that shouldn't be loaded. Ignition will ignore these classes
| and possible solutions provided by them will never be displayed.
|
*/
'ignored_solution_providers' => [
],
/*
|--------------------------------------------------------------------------
| Runnable Solutions
|--------------------------------------------------------------------------
|
| Some solutions that Ignition displays are runnable and can perform
| various tasks. By default, runnable solutions are only enabled when your
| app has debug mode enabled and the environment is `local` or
| `development`.
|
| Using the `IGNITION_ENABLE_RUNNABLE_SOLUTIONS` environment variable, you
| can override this behaviour and enable or disable runnable solutions
| regardless of the application's environment.
|
| Default: env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS')
|
*/
'enable_runnable_solutions' => env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS'),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Ignition will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('IGNITION_REMOTE_SITES_PATH', base_path()),
'local_sites_path' => env('IGNITION_LOCAL_SITES_PATH', ''),
/*
|--------------------------------------------------------------------------
| Housekeeping Endpoint Prefix
|--------------------------------------------------------------------------
|
| Ignition registers a couple of routes when it is enabled. Below you may
| specify a route prefix that will be used to host all internal links.
|
*/
'housekeeping_endpoint_prefix' => '_ignition',
/*
|--------------------------------------------------------------------------
| Settings File
|--------------------------------------------------------------------------
|
| Ignition allows you to save your settings to a specific global file.
|
| If no path is specified, a file with settings will be saved to the user's
| home directory. The directory depends on the OS and its settings but it's
| typically `~/.ignition.json`. In this case, the settings will be applied
| to all of your projects where Ignition is used and the path is not
| specified.
|
| However, if you want to store your settings on a project basis, or you
| want to keep them in another directory, you can specify a path where
| the settings file will be saved. The path should be an existing directory
| with correct write access.
| For example, create a new `ignition` folder in the storage directory and
| use `storage_path('ignition')` as the `settings_file_path`.
|
| Default value: '' (empty string)
*/
'settings_file_path' => '',
/*
|--------------------------------------------------------------------------
| Recorders
|--------------------------------------------------------------------------
|
| Ignition registers a couple of recorders when it is enabled. Below you may
| specify a recorders will be used to record specific events.
|
*/
'recorders' => [
DumpRecorder::class,
JobRecorder::class,
LogRecorder::class,
QueryRecorder::class,
],
/*
* When a key is set, we'll send your exceptions to Open AI to generate a solution
*/
'open_ai_key' => env('IGNITION_OPEN_AI_KEY'),
/*
|--------------------------------------------------------------------------
| Include arguments
|--------------------------------------------------------------------------
|
| Ignition show you stack traces of exceptions with the arguments that were
| passed to each method. This feature can be disabled here.
|
*/
'with_stack_frame_arguments' => true,
/*
|--------------------------------------------------------------------------
| Argument reducers
|--------------------------------------------------------------------------
|
| Ignition show you stack traces of exceptions with the arguments that were
| passed to each method. To make these variables more readable, you can
| specify a list of classes here which summarize the variables.
|
*/
'argument_reducers' => [
\Spatie\Backtrace\Arguments\Reducers\BaseTypeArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\ArrayArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\StdClassArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\EnumArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\ClosureArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\DateTimeArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\DateTimeZoneArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\SymphonyRequestArgumentReducer::class,
\Spatie\LaravelIgnition\ArgumentReducers\ModelArgumentReducer::class,
\Spatie\LaravelIgnition\ArgumentReducers\CollectionArgumentReducer::class,
\Spatie\Backtrace\Arguments\Reducers\StringableArgumentReducer::class,
],
];

View File

@@ -12,9 +12,9 @@ return [
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
@@ -33,7 +33,7 @@ return [
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
'trace' => false,
],
/*
@@ -41,20 +41,20 @@ return [
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', env('LOG_STACK', 'single')),
'channels' => ['single'],
'ignore_exceptions' => false,
],
@@ -69,15 +69,15 @@ return [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'days' => 14,
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
@@ -108,7 +108,7 @@ return [
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'facility' => LOG_USER,
'replace_placeholders' => true,
],
@@ -126,14 +126,6 @@ return [
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
'honeypot' => [
'driver' => 'single',
'path' => storage_path('logs/honeypot.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
],
];

View File

@@ -7,14 +7,13 @@ return [
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
| This option controls the default mailer that is used to send any email
| messages sent by your application. Alternative mailers may be setup
| and used as needed; however, this mailer will be used by default.
|
*/
'default' => env('MAIL_MAILER', 'log'),
'default' => env('MAIL_MAILER', 'smtp'),
/*
|--------------------------------------------------------------------------
@@ -25,22 +24,21 @@ return [
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
| Laravel supports a variety of mail "transport" drivers to be used while
| sending an e-mail. You will specify which one you are using for your
| mailers below. You are free to add additional mailers as required.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "log", "array", "failover", "roundrobin"
| "postmark", "log", "array", "failover"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
@@ -52,9 +50,16 @@ return [
'transport' => 'ses',
],
'mailgun' => [
'transport' => 'mailgun',
// 'client' => [
// 'timeout' => 5,
// ],
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'message_stream_id' => null,
// 'client' => [
// 'timeout' => 5,
// ],
@@ -81,14 +86,6 @@ return [
'log',
],
],
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
],
],
/*
@@ -96,9 +93,9 @@ return [
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
*/
@@ -106,4 +103,24 @@ return [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
|--------------------------------------------------------------------------
|
| If you are using Markdown based email rendering, you may configure your
| theme and component paths here, allowing you to customize the design
| of the emails. Or, you may simply stick with the Laravel defaults!
|
*/
'markdown' => [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
],
],
];

View File

@@ -7,22 +7,22 @@ return [
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
| Laravel's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
'default' => env('QUEUE_CONNECTION', 'sync'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Laravel. You are free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
@@ -36,18 +36,17 @@ return [
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'host' => 'localhost',
'queue' => 'default',
'retry_after' => 90,
'block_for' => 0,
'after_commit' => false,
],
@@ -65,9 +64,9 @@ return [
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
@@ -86,7 +85,7 @@ return [
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'job_batches',
],
@@ -96,16 +95,14 @@ return [
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
| can control which database and table are used to store the jobs that
| have failed. You may change them to any database / table you wish.
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'failed_jobs',
],

83
config/sanctum.php Normal file
View File

@@ -0,0 +1,83 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
],
];

View File

@@ -14,6 +14,13 @@ return [
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
@@ -24,11 +31,4 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

View File

@@ -9,16 +9,16 @@ return [
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
@@ -27,14 +27,13 @@ return [
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
@@ -42,21 +41,21 @@ return [
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
@@ -80,22 +79,22 @@ return [
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
| While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
@@ -121,10 +120,9 @@ return [
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
@@ -140,20 +138,20 @@ return [
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
| your application but you are free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain and all subdomains. Typically, this shouldn't be changed.
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
@@ -179,11 +177,11 @@ return [
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
'http_only' => true,
/*
|--------------------------------------------------------------------------
@@ -192,27 +190,12 @@ return [
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
| will set this value to "lax" since this is a secure default value.
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
'same_site' => 'lax',
];

View File

@@ -1,50 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Console Commands
|--------------------------------------------------------------------------
|
| This option allows you to add additional Artisan commands that should
| be available within the Tinker environment. Once the command is in
| this array you may execute the command in Tinker using its name.
|
*/
'commands' => [
// App\Console\Commands\ExampleCommand::class,
],
/*
|--------------------------------------------------------------------------
| Auto Aliased Classes
|--------------------------------------------------------------------------
|
| Tinker will not automatically alias classes in your vendor namespaces
| but you may explicitly allow a subset of classes to get aliased by
| adding the names of each of those classes to the following list.
|
*/
'alias' => [
//
],
/*
|--------------------------------------------------------------------------
| Classes That Should Not Be Aliased
|--------------------------------------------------------------------------
|
| Typically, Tinker automatically aliases classes as you require them in
| Tinker. However, you may wish to never alias certain classes, which
| you may accomplish by listing the classes in the following array.
|
*/
'dont_alias' => [
'App\Nova',
],
];

36
config/view.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| View Storage Paths
|--------------------------------------------------------------------------
|
| Most templating systems load templates from disk. Here you may specify
| an array of paths that should be checked for your views. Of course
| the usual Laravel view path has already been registered for you.
|
*/
'paths' => [
resource_path('views'),
],
/*
|--------------------------------------------------------------------------
| Compiled View Path
|--------------------------------------------------------------------------
|
| This option determines where all the compiled Blade templates will be
| stored for your application. Typically, this is within the storage
| directory. However, as usual, you are free to change this value.
|
*/
'compiled' => env(
'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views'))
),
];

View File

@@ -1,25 +0,0 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class LocationFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->company(),
'address' => fake()->address(),
'address_url' => fake()->url()
];
}
}

View File

@@ -1,22 +0,0 @@
<?php
namespace Database\Factories;
use App\Models\Media;
use Illuminate\Database\Eloquent\Factories\Factory;
class MediaFactory extends Factory
{
protected $model = Media::class;
public function definition(): array
{
return [
'name' => $this->faker->word() . '.' . $this->faker->fileExtension(),
'title' => $this->faker->sentence,
'mime_type' => $this->faker->mimeType(),
'size' => $this->faker->numberBetween(1000, 1000000),
'user_id' => $this->faker->uuid,
];
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'title' => fake()->sentence(),
'content' => '<p>' . implode('</p><p>', fake()->paragraphs()) . '</p>',
'user_id' => 1,
'status' => 'published',
'published_at' => now(),
'hero_media_name' => 'stemmechanics-logo.png'
];
}
}

View File

@@ -11,6 +11,8 @@ use Illuminate\Support\Str;
*/
class UserFactory extends Factory
{
protected static ?string $password;
/**
* Define the model's default state.
*
@@ -19,24 +21,11 @@ class UserFactory extends Factory
public function definition(): array
{
return [
'firstname' => fake()->firstName(),
'surname' => fake()->lastName(),
'phone' => fake()->phoneNumber(),
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
'shipping_address' => fake()->streetAddress(),
'shipping_city' => fake()->city(),
'shipping_state' => '',
'shipping_postcode' => fake()->postcode(),
'shipping_country' => fake()->country(),
'billing_address' => fake()->streetAddress(),
'billing_city' => fake()->city(),
'billing_state' => '',
'billing_postcode' => fake()->postcode(),
'billing_country' => fake()->country(),
];
}

View File

@@ -1,32 +0,0 @@
<?php
namespace Database\Factories;
use App\Models\Location;
use App\Models\Workshop;
use DateInterval;
use Illuminate\Database\Eloquent\Factories\Factory;
class WorkshopFactory extends Factory
{
protected $model = Workshop::class;
public function definition(): array
{
$startsAt = fake()->dateTimeBetween('now', '+1 year');
return [
'title' => fake()->sentence(),
'content' => '<p>' . implode('</p><p>', fake()->paragraphs()) . '</p>',
'starts_at' => $startsAt,
'ends_at' => $startsAt->add(DateInterval::createFromDateString('2 hours')),
'publish_at' => now(),
'closes_at' => $startsAt->sub(DateInterval::createFromDateString('2 hours')),
'status' => 'open',
'registration' => 'none',
'location_id' => Location::all()->random()->id,
'user_id' => 1,
'hero_media_name' => 'stemmechanics-logo.png'
];
}
}

View File

@@ -1,76 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->boolean('admin')->default(false);
$table->string('firstname')->nullable();
$table->string('surname')->nullable();
$table->string('phone')->nullable();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->rememberToken();
$table->string('home_address')->nullable();
$table->string('home_address2')->nullable();
$table->string('home_city')->nullable();
$table->string('home_state')->nullable();
$table->string('home_postcode')->nullable();
$table->string('home_country')->nullable();
$table->string('billing_address')->nullable();
$table->string('billing_address2')->nullable();
$table->string('billing_city')->nullable();
$table->string('billing_state')->nullable();
$table->string('billing_postcode')->nullable();
$table->string('billing_country')->nullable();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
});
Schema::create('login_tokens', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->string('token')->unique();
$table->string('intended_url')->nullable();
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('login_tokens');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@@ -1,35 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

Some files were not shown because too many files have changed in this diff Show More