Compare commits
399 Commits
feature/re
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 10f5799da2 | |||
| fffcf8347b | |||
| 2b3325af91 | |||
|
|
c9071a3bef | ||
| 3cc06c2465 | |||
| 1243a93020 | |||
|
|
78747e1f64 | ||
| 84f08b548e | |||
| 462bce226d | |||
|
|
2c3ae4ee86 | ||
| f4d6277646 | |||
| 5e47287593 | |||
| dd5eb6782d | |||
| 0312e1dbea | |||
| b29ea655ba | |||
|
|
8d06374bef | ||
| 5ca0afc385 | |||
| cc5a0de05d | |||
| fe84e20645 | |||
| 2ba8881f3b | |||
| ce5c97290f | |||
|
|
0c7407c11b | ||
| fe5ab7b0bf | |||
| 954b83ebba | |||
| 9bfe23df9c | |||
| 5f78a9e500 | |||
| d47672dbe3 | |||
| c3b898d99e | |||
| b13b22c359 | |||
| fa505f56ee | |||
| 464c7d5aa1 | |||
| 260b794111 | |||
| e2567c1b97 | |||
| 7302a36067 | |||
| ac7b02f320 | |||
| 0fbeb192c1 | |||
| 5c3788eb6d | |||
|
|
f01051107e | ||
|
|
cf029bc86e | ||
|
|
17ab10c1c5 | ||
|
|
dc5c387f7a | ||
|
|
3b4d2b2784 | ||
|
|
3b3dc276fc | ||
|
|
ee460ec076 | ||
|
|
24e7c6a008 | ||
|
|
1a6a1dab47 | ||
|
|
c85031ab49 | ||
|
|
24efcab8da | ||
|
|
182e4e8de8 | ||
|
|
6b62e45acf | ||
|
|
a37f744caa | ||
|
|
8eca69335f | ||
|
|
87e70704c1 | ||
|
|
db1821bcef | ||
|
|
62583c4869 | ||
|
|
066b7b1790 | ||
|
|
d38422e16c | ||
|
|
ed22521e6f | ||
|
|
3218f55a31 | ||
|
|
be1fa8f432 | ||
|
|
c0c572397a | ||
|
|
049d4849aa | ||
|
|
e89656367f | ||
|
|
e910ef7e84 | ||
|
|
db0e927056 | ||
|
|
cec55028d6 | ||
|
|
0ae09c40ae | ||
|
|
1cf2ec1ce7 | ||
|
|
693df3a05b | ||
|
|
c0d1913660 | ||
|
|
8c387280a6 | ||
|
|
a1ab7b6257 | ||
|
|
36d4f08aaf | ||
|
|
f7de4f49d3 | ||
|
|
06018c405d | ||
|
|
03c17200a0 | ||
| 519c2a0fec | |||
|
|
2b8788f716 | ||
|
|
72be071536 | ||
| 37b923fbda | |||
|
|
292901a02e | ||
|
|
12a6629a4b | ||
| 9d9a8ed9f5 | |||
| 1a03fed3bd | |||
| 43e66b2004 | |||
| 8babb4c836 | |||
| 4eb3dfbb64 | |||
| 33d390a612 | |||
| cad78c30ae | |||
| f8acdae237 | |||
|
|
63582dc306 | ||
|
|
c96fb95f27 | ||
| 1160c8b077 | |||
| a4b3405f8a | |||
| 9d5396ca9a | |||
| de1483d409 | |||
|
|
a37386565a | ||
|
|
ff8b6549ce | ||
|
|
d550bc60e2 | ||
|
|
307573a3a8 | ||
|
|
d9fc6c95f3 | ||
|
|
a65f0eead6 | ||
| ca025ca2e8 | |||
| 74aef68edc | |||
| c0e595f88a | |||
| aab4bc0d46 | |||
| f2708e1325 | |||
|
|
2e2b70ab7c | ||
|
|
e42d54554a | ||
| 6cb24f1500 | |||
| f7cc086f37 | |||
| 8463da7842 | |||
|
|
322d547c92 | ||
| 30104ece71 | |||
| 44f359ff9c | |||
| 20f36d519a | |||
| e358e9fb5d | |||
| b882d92328 | |||
| 3257aa9ee9 | |||
| 0bcd6f5e86 | |||
| 75d958856a | |||
| 71eb00d010 | |||
| eab3d062f5 | |||
| 1afa22e2f4 | |||
| b85d039c36 | |||
| c1a4fd13d5 | |||
| 9a1ffe835c | |||
| c3b9482d35 | |||
| bc8f9149dc | |||
| c60213257b | |||
| 6a78ba2bb2 | |||
| a5f7ce8393 | |||
| 4e1505c5c2 | |||
| e967bdde71 | |||
| 74e9e39722 | |||
| 0df4033fca | |||
| e02770cc85 | |||
| 3687af2656 | |||
| b168931266 | |||
| b669dd319e | |||
| e37b9a30a4 | |||
| 436d4b8acf | |||
| a2eb1d5d1b | |||
| be4fdb2f80 | |||
| 538f324ff4 | |||
| 59ca73519d | |||
| 6bc2b888a4 | |||
| be8b2d48b3 | |||
| 5f631a5c3d | |||
| fea3756eab | |||
| 6d8db2cd80 | |||
| 9725f4944f | |||
| 9b1b92d0cf | |||
| b10b6b712e | |||
| db018e9120 | |||
| 1444bc9aa4 | |||
| 9e7fc79fa1 | |||
| 06460d9677 | |||
| beed9f9c11 | |||
| 38b3d5d367 | |||
| ad080b19a2 | |||
| 274d9759b6 | |||
| d992570ee8 | |||
| d72c08b4c9 | |||
| 7baea36628 | |||
| b20c79b679 | |||
| 5cbebd8840 | |||
| d36979cbbd | |||
| 1c28cd7902 | |||
| df19e43112 | |||
| 5a65517d2b | |||
| 49eb388041 | |||
| 659ae2e3ac | |||
| 8f8d12065d | |||
| 391b17c1e7 | |||
| 742da4bf17 | |||
| 39ea570f3a | |||
| 714a15e6d0 | |||
| a5e4e93edb | |||
| 5f166deee9 | |||
| 2468bff5fb | |||
| 277805044a | |||
| 680be0535d | |||
| b438846c3c | |||
| 6a76dacdae | |||
| 3358cf8dea | |||
| 7b6e17ba40 | |||
| 3e891912b0 | |||
| 53d0c46aa0 | |||
| e4d5307dfe | |||
| 28aebcfe58 | |||
| cdc5d1e8d3 | |||
| 4c7dadfab0 | |||
| 4ac1322b8c | |||
| 822838fe29 | |||
| fb4c8c240b | |||
| 30e308466b | |||
| e1dcc7452b | |||
| 76690962c5 | |||
| 629ae22b78 | |||
| 1e1df33711 | |||
| f835d4a21b | |||
| 1219c9a02e | |||
| bbffddf9ae | |||
| 74cc11e124 | |||
| b020f5cbb6 | |||
| 09bd9a22c9 | |||
| ebf97a8242 | |||
| 6d51cc1395 | |||
| ededb36856 | |||
| b7ee043bf3 | |||
| 48131d3064 | |||
| d4f3e24c33 | |||
| 0f253e1047 | |||
| 4d2f6de3c8 | |||
| 346c945937 | |||
| 4391abaabb | |||
| 50169e9905 | |||
| 7a6a3ec2d8 | |||
| 5d715b096f | |||
| c696a8bd2e | |||
| d3bf78d5a8 | |||
| cd08c9a5c8 | |||
| bd8f453aea | |||
| c719da2933 | |||
| a5184be13d | |||
| 47a2afff86 | |||
| b8db85abca | |||
| 67df8f85ff | |||
| 6bb45c38a9 | |||
| 02dbacbdc0 | |||
| bea4f8db18 | |||
| 3919a3ce1c | |||
| d1b94f9183 | |||
| bb440497eb | |||
| 4b87c5e112 | |||
| a2def6abc0 | |||
| a5be12aee3 | |||
| e2fed71896 | |||
| befebc44cb | |||
| b4456d7771 | |||
| c3350823dc | |||
| 96ba9edf6a | |||
| 39609edc9e | |||
| c702253837 | |||
| bad020924d | |||
| a57be26b75 | |||
| 1d63186fd5 | |||
| 6e9d14728d | |||
| 7c9dab2cf0 | |||
| 885a909e57 | |||
| a26b669daa | |||
| 3bd3b30609 | |||
| 1201a6f0e6 | |||
| 8a32fc41a8 | |||
| 6131b378b8 | |||
| 2a80a1ad62 | |||
| 4e0e7f6b7b | |||
| 7bc62d5600 | |||
| 6208676207 | |||
| 70f9d736d1 | |||
| 09c49ab279 | |||
| 6ac8fb4f89 | |||
| 3a2735f00d | |||
| 7b89fad650 | |||
| 9bc3e5e963 | |||
| ea10ead824 | |||
| 4a4b42bed0 | |||
| 1053fbc797 | |||
| 988dbd4edc | |||
| 4c9d1667b6 | |||
| 2b924fac4b | |||
| 18e0a2afd2 | |||
| 558a432960 | |||
| f46dbd887b | |||
| 50de666304 | |||
| d592f8bd19 | |||
| d2b69061b5 | |||
| b24fa48d85 | |||
| 28b2ffa4a3 | |||
| 4a45c0f505 | |||
| f7e7ed9d7a | |||
| 232f737a10 | |||
| feecd0d7f5 | |||
| 2d872ae289 | |||
| d6b6cb49cf | |||
| 735d39f52e | |||
| 46faf195a7 | |||
| caff5c8160 | |||
| 39d5b29ed3 | |||
| b4f3fa6d07 | |||
| eae8af936b | |||
| 257e241aea | |||
| 2587ea624a | |||
| 8cd19f6b8a | |||
| 8dea8e5995 | |||
| dedcf1a379 | |||
| 2e0df186c6 | |||
| 2e39b9cb2a | |||
| 0e19f6da87 | |||
| 34092291dd | |||
| 67f2967823 | |||
| 33f0d83cf7 | |||
| 3a35c28f1d | |||
| 2a1db0b088 | |||
| 79fb978127 | |||
| 9e2cfd8abc | |||
| 88889ca329 | |||
| aed29200a7 | |||
| d6b03826c1 | |||
| 0a99b1789b | |||
| 80bc35bf4c | |||
| f0ae8cd7ad | |||
| 6dbc9f0281 | |||
| 88b1dcdfae | |||
| ff811d961f | |||
| 633e0557f6 | |||
| bdf8846278 | |||
| f0d3e739f8 | |||
| 00afc7a1d3 | |||
| 71f048fb00 | |||
| fd72954165 | |||
| da90616aa0 | |||
| 566092a567 | |||
| ef5e35229c | |||
| 005b29ff5a | |||
| 2203731ed8 | |||
| f59265cc00 | |||
| 0eab9ba2ce | |||
| e2f18a87b8 | |||
| 27941b2017 | |||
| b60e368cb9 | |||
| a1d966327f | |||
| 07ab627af4 | |||
| b84f78b00b | |||
| d6a84e2496 | |||
| 3ea9cbda9e | |||
| 363d8cfcdb | |||
| 32f7770e75 | |||
| 53df7d2fbe | |||
| 550bdd8249 | |||
| 55462d0437 | |||
| 5b7da699bd | |||
| 5fbca80a3c | |||
| 4fa1410790 | |||
| 411fc5f37f | |||
| f7c20719f7 | |||
| e5a7aeede8 | |||
|
|
cfbf3a4033 | ||
| 89cda7c415 | |||
| e211436675 | |||
| d1be96a1df | |||
|
|
7e87312d99 | ||
|
|
73c277acfb | ||
| 0728233206 | |||
| b127ce0514 | |||
| 5924222296 | |||
| 6d2c601b5d | |||
| f2fb993596 | |||
| c40232099f | |||
|
|
17d33a9ad2 | ||
|
|
e380bafbb0 | ||
|
|
4c63e68625 | ||
|
|
8b5d150fe2 | ||
|
|
10808f3622 | ||
|
|
37ba8f0f29 | ||
|
|
51fb0150d9 | ||
|
|
03dd12cea1 | ||
|
|
56ae719a2e | ||
|
|
68b7e50b9e | ||
|
|
386e2009c4 | ||
|
|
3da55bca81 | ||
|
|
5d8f1457d4 | ||
|
|
39d9ebc549 | ||
|
|
b2f3664909 | ||
|
|
06c2f0e3d0 | ||
|
|
2d2392c8ae | ||
|
|
5e7061bc61 | ||
|
|
8451a2bd4b | ||
|
|
dd135f10ec | ||
|
|
247eb03cea | ||
|
|
19c16cfbf1 | ||
|
|
456687edc5 | ||
|
|
aab63bb627 | ||
|
|
efb3cac129 | ||
|
|
36314339fb | ||
|
|
c6247e445f | ||
| b028856eb5 | |||
| 1925b2ef0c | |||
|
|
0d1bd4522e | ||
| f2e84b63fa | |||
| ac6257ed6d | |||
| 2486dec824 | |||
| 04e6c0d0fc | |||
| b948c42fe2 | |||
| 5d7be1a482 | |||
| 4e81d06a6e | |||
|
|
0f2400ff2b | ||
| 7ed2332a3e |
27
.env.example
27
.env.example
@@ -3,6 +3,7 @@ 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
|
||||
@@ -29,7 +30,7 @@ REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_HOST=mailhog
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
@@ -37,11 +38,24 @@ 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
|
||||
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=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
@@ -51,7 +65,6 @@ 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}"
|
||||
|
||||
59
.env.testing
Normal file
59
.env.testing
Normal file
@@ -0,0 +1,59 @@
|
||||
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
2
.gitattributes
vendored
@@ -1,4 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
* text=auto
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
|
||||
57
.gitea/workflows/laravel.yml
Normal file
57
.gitea/workflows/laravel.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Laravel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
laravel-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.4"
|
||||
|
||||
- name: Copy .env
|
||||
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
|
||||
|
||||
- name: Install PHP 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: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build frontend
|
||||
run: npm run build
|
||||
|
||||
- name: Run migrations
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: database/database.sqlite
|
||||
run: php artisan migrate --force
|
||||
|
||||
- name: Run PHPUnit
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: database/database.sqlite
|
||||
run: vendor/bin/phpunit
|
||||
24
.gitea/workflows/renovate.yaml
Normal file
24
.gitea/workflows/renovate.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: renovate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "@daily"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/renovatebot/renovate:43.2.4
|
||||
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 }}
|
||||
|
||||
278
.gitignore
vendored
278
.gitignore
vendored
@@ -1,20 +1,264 @@
|
||||
/.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
|
||||
### Composer ###
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# composer.lock
|
||||
|
||||
### Laravel ###
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
|
||||
# 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
|
||||
.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/
|
||||
|
||||
### Deployment ###
|
||||
/deploy.sh
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
}
|
||||
}
|
||||
20
README.md
20
README.md
@@ -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 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.
|
||||
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.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
@@ -49,6 +49,24 @@ 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).
|
||||
|
||||
7
api.http
Normal file
7
api.http
Normal file
@@ -0,0 +1,7 @@
|
||||
### 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
|
||||
@@ -1,27 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
37
app/Exceptions/FileInvalidException.php
Normal file
37
app/Exceptions/FileInvalidException.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
37
app/Exceptions/FileTooLargeException.php
Normal file
37
app/Exceptions/FileTooLargeException.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
181
app/Helpers.php
Normal file
181
app/Helpers.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
240
app/Http/Controllers/AccountController.php
Normal file
240
app/Http/Controllers/AccountController.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
319
app/Http/Controllers/AuthController.php
Normal file
319
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
abstract class Controller
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
//
|
||||
}
|
||||
|
||||
20
app/Http/Controllers/HomeController.php
Normal file
20
app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
102
app/Http/Controllers/LocationController.php
Normal file
102
app/Http/Controllers/LocationController.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
549
app/Http/Controllers/MediaController.php
Normal file
549
app/Http/Controllers/MediaController.php
Normal file
@@ -0,0 +1,549 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
147
app/Http/Controllers/PostController.php
Normal file
147
app/Http/Controllers/PostController.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
57
app/Http/Controllers/SearchController.php
Normal file
57
app/Http/Controllers/SearchController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
44
app/Http/Controllers/SubscribeController.php
Normal file
44
app/Http/Controllers/SubscribeController.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
65
app/Http/Controllers/TicketController.php
Normal file
65
app/Http/Controllers/TicketController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
167
app/Http/Controllers/UserController.php
Normal file
167
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
209
app/Http/Controllers/WorkshopController.php
Normal file
209
app/Http/Controllers/WorkshopController.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
34
app/Http/Middleware/Admin.php
Normal file
34
app/Http/Middleware/Admin.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?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 = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?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 = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?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',
|
||||
];
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?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',
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?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 = [
|
||||
//
|
||||
];
|
||||
}
|
||||
207
app/Jobs/Media/GenerateVariants.php
Normal file
207
app/Jobs/Media/GenerateVariants.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
67
app/Jobs/SendEmail.php
Normal file
67
app/Jobs/SendEmail.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
104
app/Livewire/EmailSubscribe.php
Normal file
104
app/Livewire/EmailSubscribe.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
58
app/Mail/UpcomingWorkshops.php
Normal file
58
app/Mail/UpcomingWorkshops.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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
|
||||
]);
|
||||
}
|
||||
}
|
||||
30
app/Mail/UserEmailUpdateConfirm.php
Normal file
30
app/Mail/UserEmailUpdateConfirm.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
app/Mail/UserEmailUpdateRequest.php
Normal file
36
app/Mail/UserEmailUpdateRequest.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
app/Mail/UserLogin.php
Normal file
36
app/Mail/UserLogin.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
31
app/Mail/UserLoginBackupCode.php
Normal file
31
app/Mail/UserLoginBackupCode.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
31
app/Mail/UserLoginTFADisabled.php
Normal file
31
app/Mail/UserLoginTFADisabled.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
31
app/Mail/UserLoginTFAEnabled.php
Normal file
31
app/Mail/UserLoginTFAEnabled.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
33
app/Mail/UserRegister.php
Normal file
33
app/Mail/UserRegister.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
31
app/Mail/UserWelcome.php
Normal file
31
app/Mail/UserWelcome.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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
|
||||
]);
|
||||
}
|
||||
}
|
||||
19
app/Models/EmailSubscriptions.php
Normal file
19
app/Models/EmailSubscriptions.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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'
|
||||
];
|
||||
}
|
||||
14
app/Models/Location.php
Normal file
14
app/Models/Location.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?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'];
|
||||
}
|
||||
404
app/Models/Media.php
Normal file
404
app/Models/Media.php
Normal file
@@ -0,0 +1,404 @@
|
||||
<?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()
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Models/Post.php
Normal file
26
app/Models/Post.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
30
app/Models/SentEmail.php
Normal file
30
app/Models/SentEmail.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
11
app/Models/Ticket.php
Normal file
11
app/Models/Ticket.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Ticket extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
87
app/Models/Token.php
Normal file
87
app/Models/Token.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,20 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Mail\UserLoginTFADisabled;
|
||||
use App\Mail\UserLoginTFAEnabled;
|
||||
use App\Traits\UUID;
|
||||
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 Laravel\Sanctum\HasApiTokens;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class User extends Authenticatable
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
use HasFactory, Notifiable, UUID;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -18,9 +23,26 @@ class User extends Authenticatable
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'admin',
|
||||
'firstname',
|
||||
'surname',
|
||||
'email',
|
||||
'password',
|
||||
'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',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -31,15 +53,177 @@ class User extends Authenticatable
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'tfa_secret'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
44
app/Models/UserBackupCode.php
Normal file
44
app/Models/UserBackupCode.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
52
app/Models/Workshop.php
Normal file
52
app/Models/Workshop.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@@ -19,6 +21,15 @@ 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?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
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
62
app/Providers/CaptchaServiceProvider.php
Normal file
62
app/Providers/CaptchaServiceProvider.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
23
app/Providers/QRCodeProvider.php
Normal file
23
app/Providers/QRCodeProvider.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
45
app/Traits/HasFiles.php
Normal file
45
app/Traits/HasFiles.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
app/Traits/HasUnsubscribeLink.php
Normal file
14
app/Traits/HasUnsubscribeLink.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait HasUnsubscribeLink
|
||||
{
|
||||
protected $unsubscribeLink;
|
||||
|
||||
public function withUnsubscribeLink($link)
|
||||
{
|
||||
$this->unsubscribeLink = $link;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
88
app/Traits/Slug.php
Normal file
88
app/Traits/Slug.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
42
app/Traits/UUID.php
Normal file
42
app/Traits/UUID.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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
Executable file → Normal file
50
artisan
Executable file → Normal file
@@ -1,53 +1,15 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$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);
|
||||
// Bootstrap Laravel and handle the command...
|
||||
$status = (require_once __DIR__.'/bootstrap/app.php')
|
||||
->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
|
||||
@@ -1,55 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
use App\Http\Middleware\Admin;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
$app = new Illuminate\Foundation\Application(
|
||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||
);
|
||||
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) {
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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;
|
||||
})->create();
|
||||
|
||||
6
bootstrap/providers.php
Normal file
6
bootstrap/providers.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\CaptchaServiceProvider::class,
|
||||
];
|
||||
@@ -5,21 +5,25 @@
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"itsgoingd/clockwork": "^5.1",
|
||||
"laravel/framework": "^10.10",
|
||||
"laravel/sanctum": "^3.3",
|
||||
"laravel/tinker": "^2.8"
|
||||
"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"
|
||||
},
|
||||
"require-dev": {
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -45,7 +49,15 @@
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
"@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"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
@@ -54,6 +66,9 @@
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.4.17"
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
|
||||
5947
composer.lock
generated
5947
composer.lock
generated
File diff suppressed because it is too large
Load Diff
111
config/app.php
111
config/app.php
@@ -1,8 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@@ -10,9 +7,9 @@ return [
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application. This value is used when the
|
||||
| This value is the name of your application, which will be used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| any other location as required by the application or its packages.
|
||||
| other UI elements where an application name needs to be displayed.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -51,26 +48,24 @@ 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
|
||||
| your application so that it is used when running Artisan tasks.
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'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. We have gone
|
||||
| ahead and set this to a sensible default for you out of the box.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -78,53 +73,37 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by the translation service provider. You are free to set this value
|
||||
| to any of the locales which will be supported by the application.
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => 'en',
|
||||
'locale' => env('APP_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.
|
||||
|
|
||||
*/
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'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',
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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!
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -140,49 +119,11 @@ return [
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => 'file',
|
||||
// 'store' => 'redis',
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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(),
|
||||
|
||||
'notice' => env('APP_NOTICE', ''),
|
||||
'version' => env('APP_VERSION', 'dev'),
|
||||
'commit' => env('APP_COMMIT', ''),
|
||||
];
|
||||
|
||||
@@ -7,15 +7,15 @@ return [
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| This option defines the default authentication "guard" and password
|
||||
| reset "broker" for your application. You may change these values
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
'guard' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', '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
|
||||
| here which uses session storage and the Eloquent user provider.
|
||||
| which utilizes session storage plus the Eloquent user provider.
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
@@ -47,12 +47,12 @@ return [
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| sources which represent each model / table. These sources may then
|
||||
| providers to represent the model / table. These providers 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' => App\Models\User::class,
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
@@ -76,9 +76,9 @@ return [
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
| 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' => 'password_reset_tokens',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_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
|
||||
| times out and the user is prompted to re-enter their password via the
|
||||
| window expires and users are asked to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => 10800,
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?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',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -9,13 +9,13 @@ return [
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_DRIVER', 'file'),
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -26,17 +26,13 @@ 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,
|
||||
@@ -44,9 +40,9 @@ return [
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'cache',
|
||||
'connection' => null,
|
||||
'lock_connection' => null,
|
||||
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||
'connection' => env('DB_CACHE_CONNECTION'),
|
||||
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||
],
|
||||
|
||||
'file' => [
|
||||
@@ -76,8 +72,8 @@ return [
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'cache',
|
||||
'lock_connection' => 'default',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
@@ -100,8 +96,8 @@ return [
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
|
||||
| stores there might be other applications using the same cache. For
|
||||
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||
| stores, there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?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,
|
||||
|
||||
];
|
||||
@@ -10,26 +10,22 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for all database work. Of course
|
||||
| you may use many connections at once using the Database library.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'mysql'),
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -37,7 +33,7 @@ return [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'url' => env('DB_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
@@ -45,15 +41,35 @@ return [
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'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'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
@@ -65,13 +81,13 @@ return [
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
@@ -80,13 +96,13 @@ return [
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
@@ -102,11 +118,14 @@ 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 in the database.
|
||||
| the migrations on disk haven't actually been run on the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => 'migrations',
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -115,7 +134,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 APC or Memcached. Laravel makes it easy to dig right in.
|
||||
| such as Memcached. You may define your connection settings here.
|
||||
|
|
||||
*/
|
||||
|
||||
|
||||
@@ -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. Just store away!
|
||||
| based disks are available to your application for file storage.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -20,9 +20,9 @@ return [
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
@@ -36,25 +36,24 @@ return [
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'media' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'root' => storage_path('app/media'),
|
||||
'url' => env('APP_URL').'/media/download',
|
||||
'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,
|
||||
// ],
|
||||
|
||||
],
|
||||
|
||||
@@ -70,7 +69,7 @@ return [
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
// public_path('media') => storage_path('app/media'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
80
config/flare.php
Normal file
80
config/flare.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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,
|
||||
];
|
||||
@@ -1,54 +0,0 @@
|
||||
<?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,
|
||||
],
|
||||
|
||||
];
|
||||
277
config/ignition.php
Normal file
277
config/ignition.php
Normal file
@@ -0,0 +1,277 @@
|
||||
<?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,
|
||||
],
|
||||
];
|
||||
@@ -12,9 +12,9 @@ return [
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,7 @@ return [
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => false,
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -41,20 +41,20 @@ return [
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog",
|
||||
| "custom", "stack"
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single'],
|
||||
'channels' => explode(',', env('LOG_STACK', 'single')),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
@@ -69,15 +69,15 @@ return [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => 14,
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => 'Laravel Log',
|
||||
'emoji' => ':boom:',
|
||||
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
@@ -108,7 +108,7 @@ return [
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => LOG_USER,
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
@@ -126,6 +126,14 @@ 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,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -7,13 +7,14 @@ return [
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'smtp'),
|
||||
'default' => env('MAIL_MAILER', 'log'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -24,21 +25,22 @@ 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 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.
|
||||
| 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.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "log", "array", "failover"
|
||||
| "postmark", "log", "array", "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||
'port' => env('MAIL_PORT', 2525),
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
@@ -50,16 +52,9 @@ return [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'mailgun' => [
|
||||
'transport' => 'mailgun',
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => null,
|
||||
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
@@ -86,6 +81,14 @@ return [
|
||||
'log',
|
||||
],
|
||||
],
|
||||
|
||||
'mailgun' => [
|
||||
'domain' => env('MAILGUN_DOMAIN'),
|
||||
'secret' => env('MAILGUN_SECRET'),
|
||||
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||
'scheme' => 'https',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -93,9 +96,9 @@ return [
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -103,24 +106,4 @@ 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'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -7,22 +7,22 @@ return [
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'sync'),
|
||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||
|
|
||||
@@ -36,17 +36,18 @@ return [
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'jobs',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'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),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => 'localhost',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
@@ -64,9 +65,9 @@ return [
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => 90,
|
||||
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
@@ -85,7 +86,7 @@ return [
|
||||
*/
|
||||
|
||||
'batching' => [
|
||||
'database' => env('DB_CONNECTION', 'mysql'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'job_batches',
|
||||
],
|
||||
|
||||
@@ -95,14 +96,16 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| 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.
|
||||
| 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"
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'mysql'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<?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,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -14,13 +14,6 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'mailgun' => [
|
||||
'domain' => env('MAILGUN_DOMAIN'),
|
||||
'secret' => env('MAILGUN_SECRET'),
|
||||
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||
'scheme' => 'https',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
],
|
||||
@@ -31,4 +24,11 @@ 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'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -9,16 +9,16 @@ return [
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "apc",
|
||||
| "memcached", "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'file'),
|
||||
'driver' => env('SESSION_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -27,13 +27,14 @@ 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 immediately expire on the browser closing, set that option.
|
||||
| to expire immediately when the browser is closed then you may
|
||||
| indicate that via the expire_on_close configuration option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => false,
|
||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -41,21 +42,21 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it is stored. All encryption will be run
|
||||
| automatically by Laravel and you can use the Session like normal.
|
||||
| should be encrypted before it's stored. All encryption is performed
|
||||
| automatically by Laravel and you may use the session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => false,
|
||||
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -79,22 +80,22 @@ return [
|
||||
| Session Database 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => 'sessions',
|
||||
'table' => env('SESSION_TABLE', 'sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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".
|
||||
| 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.
|
||||
|
|
||||
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||
|
|
||||
@@ -120,9 +121,10 @@ return [
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -138,20 +140,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 are free to change this when necessary.
|
||||
| your application, but you're free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => '/',
|
||||
'path' => env('SESSION_PATH', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -177,11 +179,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. You are free to modify this option if needed.
|
||||
| the HTTP protocol. It's unlikely you should disable this option.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => true,
|
||||
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -190,12 +192,27 @@ 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" since this is a secure default value.
|
||||
| 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
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => 'lax',
|
||||
'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),
|
||||
|
||||
];
|
||||
|
||||
50
config/tinker.php
Normal file
50
config/tinker.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,36 +0,0 @@
|
||||
<?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'))
|
||||
),
|
||||
|
||||
];
|
||||
25
database/factories/LocationFactory.php
Normal file
25
database/factories/LocationFactory.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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()
|
||||
];
|
||||
}
|
||||
}
|
||||
22
database/factories/MediaFactory.php
Normal file
22
database/factories/MediaFactory.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
28
database/factories/PostFactory.php
Normal file
28
database/factories/PostFactory.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ use Illuminate\Support\Str;
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
@@ -21,11 +19,24 @@ class UserFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'firstname' => fake()->firstName(),
|
||||
'surname' => fake()->lastName(),
|
||||
'phone' => fake()->phoneNumber(),
|
||||
'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(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
32
database/factories/WorkshopFactory.php
Normal file
32
database/factories/WorkshopFactory.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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'
|
||||
];
|
||||
}
|
||||
}
|
||||
76
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
76
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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');
|
||||
}
|
||||
};
|
||||
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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
Reference in New Issue
Block a user