first commit
This commit is contained in:
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
65
.env.example
Normal file
65
.env.example
Normal file
@@ -0,0 +1,65 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=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
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
10
.gitattributes
vendored
Normal file
10
.gitattributes
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
CHANGELOG.md export-ignore
|
||||
README.md export-ignore
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/.phpunit.cache
|
||||
/bootstrap/ssr
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/auth.json
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
resources/js/components/ui/*
|
||||
resources/js/ziggy.js
|
||||
resources/views/mail/*
|
||||
18
.prettierrc
Normal file
18
.prettierrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"singleAttributePerLine": false,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"printWidth": 150,
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"],
|
||||
"tailwindFunctions": ["clsx", "cn"],
|
||||
"tabWidth": 4,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.yml",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
15
Video2AI/All Renders.bru
Normal file
15
Video2AI/All Renders.bru
Normal file
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: All Renders
|
||||
type: http
|
||||
seq: 8
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://video2ai.test/api/render/all
|
||||
body: json
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{AUTH_TOKEN}}
|
||||
}
|
||||
15
Video2AI/Check Render Status.bru
Normal file
15
Video2AI/Check Render Status.bru
Normal file
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: Check Render Status
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://video2ai.test/api/render/0e6b6e60-b488-4ed2-aa14-c6b2a5b67f42
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{AUTH_TOKEN}}
|
||||
}
|
||||
20
Video2AI/Login.bru
Normal file
20
Video2AI/Login.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Login
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://video2ai.test/api/user/login
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{ "email": "test@example.com", "password": "password123" }
|
||||
}
|
||||
20
Video2AI/Logout.bru
Normal file
20
Video2AI/Logout.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Logout
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://video2ai.test/api/user/logout
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: 2|DSl9SRQI5za0QjqLkZBKhejSsfBEr3yECNcWk5mz1e10adc7
|
||||
}
|
||||
20
Video2AI/Register.bru
Normal file
20
Video2AI/Register.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Register
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://video2ai.test/api/user/register
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
body:json {
|
||||
{ "email": "test@example.com", "password": "password123", "password_confirmation": "password123" }
|
||||
}
|
||||
233
Video2AI/Start Render.bru
Normal file
233
Video2AI/Start Render.bru
Normal file
@@ -0,0 +1,233 @@
|
||||
meta {
|
||||
name: Start Render
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://video2ai.test/api/render
|
||||
body: json
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{AUTH_TOKEN}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"external_id": "12345-12334293523",
|
||||
"content_type":"moving_images",
|
||||
"width": 720,
|
||||
"height": 1280,
|
||||
"aspect_ratio": "9:16",
|
||||
"fps": 30,
|
||||
"video_bitrate":"3M",
|
||||
"audio_bitrate": "128K",
|
||||
"captions": [
|
||||
{
|
||||
"time":0,
|
||||
"duration": 5,
|
||||
"text": "Hello welcome chicken rice",
|
||||
"parameters": {
|
||||
"font": "Lilita One",
|
||||
"font_size": 15,
|
||||
"animate_rotate_words": true,
|
||||
"scale_rotate_words":true,
|
||||
"v_position_percentage": 20,
|
||||
"h_position_percentage": 50
|
||||
},
|
||||
"words": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 1.0,
|
||||
"text":"Hello"
|
||||
},
|
||||
{
|
||||
"start": 1.0,
|
||||
"end": 2.0,
|
||||
"text":"welcome"
|
||||
},
|
||||
{
|
||||
"start": 2.0,
|
||||
"end": 3.0,
|
||||
"text":"chicken"
|
||||
},
|
||||
{
|
||||
"start": 3.0,
|
||||
"end": 5.0,
|
||||
"text":"rice"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time":5,
|
||||
"duration": 7,
|
||||
"text": "Follow us now",
|
||||
"v_position_percentage": 20,
|
||||
"h_position_percentage": 50,
|
||||
"parameters": {
|
||||
"font": "Lilita One",
|
||||
"font_size": 15,
|
||||
"animate_rotate_words": true,
|
||||
"scale_rotate_words":true
|
||||
},
|
||||
"words": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 1.0,
|
||||
"text":"Follow"
|
||||
},
|
||||
{
|
||||
"start": 1.0,
|
||||
"end": 1.4,
|
||||
"text":"us"
|
||||
},
|
||||
{
|
||||
"start": 1.4,
|
||||
"end": 2.0,
|
||||
"text":"now"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"elements": [
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/system-ov/sov_1741963443481-aps-watermark-25fps.mov",
|
||||
"type":"video",
|
||||
"external_reference":"watermark_overlay",
|
||||
"time":0.0,
|
||||
"track":7,
|
||||
"duration": 5,
|
||||
"parameters": {
|
||||
"loop": true,
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421422078-cover-14763-1745421422045.png",
|
||||
"type":"image",
|
||||
"external_reference":"cover_photo",
|
||||
"time":0.0,
|
||||
"track":6,
|
||||
"duration": 0.04,
|
||||
"parameters": {
|
||||
"loop": true,
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421417131-bg-55951.png",
|
||||
"type":"image",
|
||||
"external_reference":"slideshow",
|
||||
"time":1.0,
|
||||
"track":5,
|
||||
"duration": 1.0,
|
||||
"parameters": {
|
||||
"animate":"random",
|
||||
"particle_overlay": true,
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421417296-bg-55952.png",
|
||||
"type":"image",
|
||||
"external_reference":"slideshow",
|
||||
"time":2.0,
|
||||
"track":5,
|
||||
"duration": 1.0,
|
||||
"parameters": {
|
||||
"animate":"random",
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421419185-bg-55953.png",
|
||||
"type":"image",
|
||||
"external_reference":"slideshow",
|
||||
"time":3.0,
|
||||
"track":5,
|
||||
"duration": 1.0,
|
||||
"parameters": {
|
||||
"animate":"random",
|
||||
"particle_overlay": true,
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421418777-bg-55954.png",
|
||||
"type":"image",
|
||||
"external_reference":"slideshow",
|
||||
"time":4.0,
|
||||
"track":5,
|
||||
"duration": 1.0,
|
||||
"parameters": {
|
||||
"animate":"random",
|
||||
"particle_overlay": true,
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421417131-bg-55951.png",
|
||||
"type":"image",
|
||||
"external_reference":"slideshow",
|
||||
"time":5.0,
|
||||
"track":5,
|
||||
"duration": 2,
|
||||
"parameters": {
|
||||
"animate":"random",
|
||||
"background_size":"fit|maintain_aspect_ratio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-i/gi_1745421417131-bg-55951.png",
|
||||
"type":"image",
|
||||
"external_reference":"slideshow",
|
||||
"time":5.0,
|
||||
"track":6,
|
||||
"duration": 2,
|
||||
"parameters": {
|
||||
"animate":"random",
|
||||
"v_position_percentage": 70,
|
||||
"h_position_percentage": 50,
|
||||
"scale": 0.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-tts/gtts_1745421385079-script-14763-1745421378856.mp3",
|
||||
"type": "audio",
|
||||
"external_reference":"script_audio",
|
||||
"time":0,
|
||||
"track": 4,
|
||||
"duration": 5.0,
|
||||
"parameters": {
|
||||
"volume": 0.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/generated-tts/gtts_1745422892152-post-script-14763-1745422889747.mp3",
|
||||
"type": "audio",
|
||||
"external_reference":"post_script_audio",
|
||||
"time":5,
|
||||
"track": 4,
|
||||
"duration": 2.0,
|
||||
"parameters": {
|
||||
"volume": 0.9
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"url":"https://cdn.autopilotshorts.com/system-bgm/sbgm_1723719114058-bgm-advertime.mp3",
|
||||
"type": "audio",
|
||||
"external_reference":"background_music",
|
||||
"time":0,
|
||||
"track": 3,
|
||||
"duration": 7.0,
|
||||
"parameters": {
|
||||
"volume": 0.4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
24
Video2AI/User.bru
Normal file
24
Video2AI/User.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: User
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://video2ai.test/api/user
|
||||
body: json
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{AUTH_TOKEN}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{ "email": "test@example.com", "password": "password123" }
|
||||
}
|
||||
9
Video2AI/bruno.json
Normal file
9
Video2AI/bruno.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "Video2AI",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
3
Video2AI/environments/DEV.bru
Normal file
3
Video2AI/environments/DEV.bru
Normal file
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
AUTH_TOKEN: 2|4r7GpplTJeIFllLsr4fPD0oW4IX11gvbPTQg7E7Jd47df523
|
||||
}
|
||||
28803
_ide_helper.php
Normal file
28803
_ide_helper.php
Normal file
File diff suppressed because it is too large
Load Diff
19
app/Helpers/FirstParty/Jobs/JobTrigger.php
Normal file
19
app/Helpers/FirstParty/Jobs/JobTrigger.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\RunVideoRenderPipelineJob;
|
||||
use App\Models\Video;
|
||||
|
||||
class JobTrigger
|
||||
{
|
||||
public static function RunVideoRenderPipelineJob()
|
||||
{
|
||||
$video = Video::latest()->first();
|
||||
|
||||
if ($video) {
|
||||
$job = new RunVideoRenderPipelineJob($video->id);
|
||||
$job->handle();
|
||||
} else {
|
||||
echo 'NO VIDEO';
|
||||
}
|
||||
}
|
||||
}
|
||||
349
app/Helpers/FirstParty/MediaEngine/MediaEngine.php
Normal file
349
app/Helpers/FirstParty/MediaEngine/MediaEngine.php
Normal file
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers\FirstParty\MediaEngine;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Models\MediaCollection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MediaEngine
|
||||
{
|
||||
const LOCAL_TEMP_DISK = 'local_temp';
|
||||
|
||||
const USER_UPLOADED = 'user_uploaded';
|
||||
|
||||
const USER_RENDERED = 'user_rendered';
|
||||
|
||||
const USER = 'user';
|
||||
|
||||
public static function getMediaCloudUrl($media)
|
||||
{
|
||||
return Storage::disk($media->disk)->url($media->file_path.$media->file_name);
|
||||
}
|
||||
|
||||
public static function loadMediaToLocalTemp($uuid)
|
||||
{
|
||||
$media = self::getMediaByUuid($uuid);
|
||||
|
||||
$result = [
|
||||
'media' => $media,
|
||||
'uuid' => $uuid,
|
||||
'temp_path' => null,
|
||||
'temp' => null,
|
||||
];
|
||||
|
||||
if (! $media) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (! Storage::disk($media->disk)->exists($media->file_path.$media->file_name)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$tempPath = 'temp_'.$media->file_name;
|
||||
$fileContent = Storage::disk($media->disk)->get($media->file_path.$media->file_name);
|
||||
|
||||
if (! Storage::disk(self::LOCAL_TEMP_DISK)->exists($tempPath)) {
|
||||
if (Storage::disk(self::LOCAL_TEMP_DISK)->put($tempPath, $fileContent)) {
|
||||
$result['temp_path'] = $tempPath;
|
||||
$result['temp'] = Storage::disk(self::LOCAL_TEMP_DISK)->path($tempPath);
|
||||
}
|
||||
} else {
|
||||
$result['temp_path'] = $tempPath;
|
||||
$result['temp'] = Storage::disk(self::LOCAL_TEMP_DISK)->path($tempPath);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function loadMediasToLocalTemp(array $uuids)
|
||||
{
|
||||
$medias = self::getMediaByUuids($uuids);
|
||||
$result = [];
|
||||
|
||||
foreach ($medias as $media) {
|
||||
if (! $media) {
|
||||
continue; // Skip invalid media instead of returning early
|
||||
}
|
||||
|
||||
$tempPath = 'temp_'.$media->file_name;
|
||||
$singleResult = [
|
||||
'media' => $media,
|
||||
'uuid' => $media->uuid,
|
||||
'temp_path' => null,
|
||||
'temp' => null,
|
||||
'cloud_url' => Storage::disk($media->disk)->url($media->file_path.$media->file_name),
|
||||
];
|
||||
|
||||
// Check if file exists in cloud storage
|
||||
if (! Storage::disk($media->disk)->exists($media->file_path.$media->file_name)) {
|
||||
$result[] = $singleResult; // Add to results but with null temp paths
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if already loaded in local temp
|
||||
if (Storage::disk(self::LOCAL_TEMP_DISK)->exists($tempPath)) {
|
||||
// File already exists in temp, just set the paths
|
||||
$singleResult['temp_path'] = $tempPath;
|
||||
$singleResult['temp'] = Storage::disk(self::LOCAL_TEMP_DISK)->path($tempPath);
|
||||
} else {
|
||||
// File needs to be loaded to temp
|
||||
$fileContent = Storage::disk($media->disk)->get($media->file_path.$media->file_name);
|
||||
if (Storage::disk(self::LOCAL_TEMP_DISK)->put($tempPath, $fileContent)) {
|
||||
$singleResult['temp_path'] = $tempPath;
|
||||
$singleResult['temp'] = Storage::disk(self::LOCAL_TEMP_DISK)->path($tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $singleResult;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function deleteMediaFromLocalTemp($uuid)
|
||||
{
|
||||
$result = self::loadMediaToLocalTemp($uuid);
|
||||
|
||||
if ($result['temp_path']) {
|
||||
Storage::disk(self::LOCAL_TEMP_DISK)->delete($result['temp_path']);
|
||||
}
|
||||
|
||||
return [
|
||||
'media' => $result['media'],
|
||||
'uuid' => $uuid,
|
||||
'temp_path' => null,
|
||||
'temp' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public static function deleteMediasFromLocalTemp(array $uuids)
|
||||
{
|
||||
$results = self::loadMediasToLocalTemp($uuids);
|
||||
$deletedResults = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
if ($result['temp_path']) {
|
||||
Storage::disk(self::LOCAL_TEMP_DISK)->delete($result['temp_path']);
|
||||
}
|
||||
|
||||
$deletedResults[] = [
|
||||
'media' => $result['media'],
|
||||
'uuid' => $result['uuid'],
|
||||
'temp_path' => null,
|
||||
'temp' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $deletedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new media file to the specified media collection.
|
||||
*
|
||||
* Example: $newMedia = MediaEngine::addMedia(
|
||||
* 'bgm_collection',
|
||||
* 'bgm',
|
||||
* 'user_upload',
|
||||
* 'web_app',
|
||||
* 'background_music.mp3',
|
||||
* $fileContent,
|
||||
* 'r2',
|
||||
* 'name of file',
|
||||
* $user_id,
|
||||
* );
|
||||
*/
|
||||
public static function addMedia(
|
||||
string $mediaCollectionKey,
|
||||
string $mediaType,
|
||||
string $mediaSource,
|
||||
string $mediaProvider,
|
||||
string $fileName,
|
||||
string $fileContent,
|
||||
string $disk = 'r2',
|
||||
?string $name = null,
|
||||
?int $userId = null,
|
||||
|
||||
) {
|
||||
$mediaCollection = MediaCollection::where('key', $mediaCollectionKey)->first();
|
||||
|
||||
if (! $mediaCollection) {
|
||||
throw new \InvalidArgumentException("Media collection with key '{$mediaCollectionKey}' not found.");
|
||||
}
|
||||
|
||||
$config = config("platform.media.{$mediaCollectionKey}");
|
||||
|
||||
// Adjust fileName with prefix, postfix, and ensure extension
|
||||
$adjustedFileName = $config['prefix'].epoch_now_timestamp().'-'.$fileName;
|
||||
|
||||
// Construct file path
|
||||
$filePath = $config['location'];
|
||||
|
||||
// Store the file
|
||||
$stored = Storage::disk($disk)->put($filePath.$adjustedFileName, $fileContent);
|
||||
|
||||
if (! $stored) {
|
||||
throw new \RuntimeException("Failed to store file: {$filePath}");
|
||||
}
|
||||
|
||||
// Get filetype
|
||||
$mimeType = null;
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'mime_');
|
||||
try {
|
||||
file_put_contents($tempFile, $fileContent);
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $tempFile);
|
||||
finfo_close($finfo);
|
||||
} finally {
|
||||
// This ensures the file is deleted even if an exception occurs
|
||||
if (file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
if (is_empty($mimeType)) {
|
||||
$mimeType = $config['mime'];
|
||||
}
|
||||
|
||||
$media = new Media([
|
||||
'uuid' => Str::uuid(),
|
||||
'media_collection_id' => $mediaCollection->id,
|
||||
'user_id' => $userId,
|
||||
'media_type' => $mediaType,
|
||||
'media_source' => $mediaSource,
|
||||
'media_provider' => $mediaProvider,
|
||||
'mime_type' => $mimeType,
|
||||
'file_name' => $adjustedFileName,
|
||||
'file_path' => $filePath,
|
||||
'disk' => $disk,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$media->save();
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
private static function ensureUniqueFileName($fileName, $disk, $location)
|
||||
{
|
||||
$uniqueFileName = $fileName;
|
||||
$counter = 1;
|
||||
|
||||
while (Storage::disk($disk)->exists($location.$uniqueFileName)) {
|
||||
$info = pathinfo($fileName);
|
||||
$uniqueFileName = $info['filename'].'-'.$counter.'.'.$info['extension'];
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $uniqueFileName;
|
||||
}
|
||||
|
||||
public static function getMediaByUuid($uuid)
|
||||
{
|
||||
return Media::where('uuid', $uuid)->first();
|
||||
}
|
||||
|
||||
public static function getMediaByUuids(array $uuids)
|
||||
{
|
||||
return Media::whereIn('uuid', $uuids)->get();
|
||||
}
|
||||
|
||||
public static function getMediasByCollectionKey($key)
|
||||
{
|
||||
$collection = MediaCollection::where('key', $key)->first();
|
||||
|
||||
if (! $collection) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $collection->media;
|
||||
}
|
||||
|
||||
public static function getCollectionKeyByOwnerMediaType($owner_type, $media_type)
|
||||
{
|
||||
$mediaConfig = config('platform.media');
|
||||
|
||||
foreach ($mediaConfig as $key => $item) {
|
||||
if ($item['owner_type'] == $owner_type && $item['media_type'] == $media_type) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file details from a URL including filename, extension and MIME type
|
||||
*
|
||||
* @param string $url The URL of the file
|
||||
* @return object Object containing filename, extension and MIME type
|
||||
*/
|
||||
public static function getFileDetailsbyUrl($url)
|
||||
{
|
||||
// Create an empty result object
|
||||
$result = new \stdClass;
|
||||
|
||||
// Parse the URL to extract the filename
|
||||
$pathInfo = pathinfo(parse_url($url, PHP_URL_PATH));
|
||||
|
||||
// Set the filename and extension
|
||||
$result->filename = $pathInfo['filename'] ?? '';
|
||||
$result->extension = $pathInfo['extension'] ?? '';
|
||||
|
||||
// Initialize the MIME type as unknown
|
||||
$result->mimetype = 'application/octet-stream';
|
||||
|
||||
// Try to get the real MIME type using fileinfo
|
||||
try {
|
||||
// Create a temporary file
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'file_');
|
||||
|
||||
// Get the file content from URL
|
||||
$fileContent = @file_get_contents($url);
|
||||
|
||||
if ($fileContent !== false) {
|
||||
// Write the content to the temporary file
|
||||
file_put_contents($tempFile, $fileContent);
|
||||
|
||||
// Create a finfo object
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
|
||||
// Get the MIME type
|
||||
$result->mimetype = $finfo->file($tempFile);
|
||||
|
||||
// Clean up the temporary file
|
||||
@unlink($tempFile);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// If there's an error, try to determine MIME type from extension
|
||||
$commonMimeTypes = [
|
||||
// Images
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
'svg' => 'image/svg+xml',
|
||||
|
||||
// Audio
|
||||
'mp3' => 'audio/mpeg',
|
||||
'wav' => 'audio/wav',
|
||||
'ogg' => 'audio/ogg',
|
||||
'm4a' => 'audio/mp4',
|
||||
'flac' => 'audio/flac',
|
||||
|
||||
// Video
|
||||
'mp4' => 'video/mp4',
|
||||
'webm' => 'video/webm',
|
||||
'avi' => 'video/x-msvideo',
|
||||
'mov' => 'video/quicktime',
|
||||
'mkv' => 'video/x-matroska',
|
||||
];
|
||||
|
||||
if (! empty($result->extension) && isset($commonMimeTypes[strtolower($result->extension)])) {
|
||||
$result->mimetype = $commonMimeTypes[strtolower($result->extension)];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
163
app/Helpers/FirstParty/Render/FfmpegVideoRenderer.php
Normal file
163
app/Helpers/FirstParty/Render/FfmpegVideoRenderer.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers\FirstParty\Render;
|
||||
|
||||
use App\Models\Video;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Class FfmpegVideoRenderer
|
||||
*
|
||||
* Builds and optionally executes an ffmpeg command using VideoElement models and render settings.
|
||||
*/
|
||||
class FfmpegVideoRenderer
|
||||
{
|
||||
/**
|
||||
* Render the given Video to a file via ffmpeg.
|
||||
*
|
||||
* @return object { string $name, string $path }
|
||||
*
|
||||
* @throws \InvalidArgumentException if required elements missing
|
||||
* @throws \RuntimeException on ffmpeg failure
|
||||
*/
|
||||
public static function render(Video $video): object
|
||||
{
|
||||
$elements = self::get_video_elements($video);
|
||||
$settings = self::get_render_settings($video);
|
||||
|
||||
// Gather elements by type and sort by time
|
||||
$slide_images = $elements->where('external_reference', 'slideshow')->sortBy('time')->values();
|
||||
$overlay_images = $elements->where('type', 'image')
|
||||
->where('external_reference', '!=', 'slideshow')
|
||||
->sortBy('time')
|
||||
->values();
|
||||
$video_element = $elements->firstWhere('type', 'video');
|
||||
$audio_tracks = $elements->where('type', 'audio')->sortBy('time')->values();
|
||||
|
||||
if ($slide_images->isEmpty() || ! $video_element) {
|
||||
throw new \InvalidArgumentException('At least one slideshow and one video element are required.');
|
||||
}
|
||||
|
||||
// Build ffmpeg input arguments
|
||||
$input_args = [];
|
||||
foreach ($slide_images as $slide) {
|
||||
$duration = number_format($slide->duration, 2);
|
||||
$input_args[] = "-loop 1 -t {$duration} -i \"{$slide->asset_url}\"";
|
||||
}
|
||||
foreach ($overlay_images as $overlay) {
|
||||
$duration = number_format($overlay->duration, 2);
|
||||
$input_args[] = "-loop 1 -t {$duration} -i \"{$overlay->asset_url}\"";
|
||||
}
|
||||
$input_args[] = "-i \"{$video_element->asset_url}\"";
|
||||
foreach ($audio_tracks as $audio) {
|
||||
$input_args[] = "-i \"{$audio->asset_url}\"";
|
||||
}
|
||||
|
||||
// Build filter_complex chains
|
||||
$filters = [];
|
||||
$slide_count = $slide_images->count();
|
||||
$concat_input = '';
|
||||
for ($i = 0; $i < $slide_count; $i++) {
|
||||
$concat_input .= "[{$i}:v]";
|
||||
}
|
||||
$filters[] = "{$concat_input}concat=n={$slide_count}:v=1:a=0[slideshow]";
|
||||
|
||||
$current_label = 'slideshow';
|
||||
foreach ($overlay_images as $idx => $overlay) {
|
||||
$stream_idx = $slide_count + $idx;
|
||||
$start_time = number_format($overlay->time, 2);
|
||||
$end_time = number_format($overlay->time + $overlay->duration, 2);
|
||||
$next_label = "overlay{$idx}";
|
||||
$filters[] = "[{$current_label}][{$stream_idx}:v]overlay=enable=between(t\,{$start_time}\,{$end_time})[{$next_label}]";
|
||||
$current_label = $next_label;
|
||||
}
|
||||
|
||||
$video_idx = $slide_count + $overlay_images->count();
|
||||
$v_start = number_format($video_element->time, 2);
|
||||
$v_end = number_format($video_element->time + $video_element->duration, 2);
|
||||
$filters[] = "[{$current_label}][{$video_idx}:v]overlay=enable=between(t\,{$v_start}\,{$v_end})[v_out]";
|
||||
|
||||
$audio_labels = [];
|
||||
$first_audio = $video_idx + 1;
|
||||
foreach ($audio_tracks as $idx => $audio) {
|
||||
$stream_idx = $first_audio + $idx;
|
||||
$dur = number_format($audio->duration, 2);
|
||||
$start_time = number_format($audio->time, 2);
|
||||
|
||||
$chain = "[{$stream_idx}:a]atrim=duration={$dur},asetpts=PTS-STARTPTS";
|
||||
if ($audio->time > 0) {
|
||||
$delay_ms = (int) round($audio->time * 1000);
|
||||
$chain .= ",adelay={$delay_ms}|{$delay_ms}";
|
||||
}
|
||||
|
||||
$label = "a{$idx}";
|
||||
$filters[] = "{$chain}[{$label}]";
|
||||
$audio_labels[] = $label;
|
||||
}
|
||||
|
||||
$mix_input = '';
|
||||
foreach ($audio_labels as $lbl) {
|
||||
$mix_input .= "[{$lbl}]";
|
||||
}
|
||||
$audio_count = count($audio_labels);
|
||||
$filters[] = "{$mix_input}amix=inputs={$audio_count}:duration=longest[a_out]";
|
||||
|
||||
$filter_complex = implode(';', $filters);
|
||||
|
||||
// Prepare output
|
||||
$filename = Str::random(12).'.mp4';
|
||||
$output_path = sys_get_temp_dir().DIRECTORY_SEPARATOR.$filename;
|
||||
|
||||
// FFmpeg command, including render settings
|
||||
$video_bitrate = $settings['video_bitrate'];
|
||||
$audio_bitrate = $settings['audio_bitrate'];
|
||||
$fps = $settings['fps'];
|
||||
|
||||
$command = 'ffmpeg '.implode(' ', $input_args)
|
||||
." -r {$fps}"
|
||||
." -filter_complex \"{$filter_complex}\""
|
||||
.' -map "[v_out]" -map "[a_out]"'
|
||||
." -c:v libx264 -b:v {$video_bitrate}"
|
||||
." -c:a aac -b:a {$audio_bitrate}"
|
||||
." -shortest \"{$output_path}\" -y";
|
||||
|
||||
$process = Process::fromShellCommandline($command, null, null, null, 300);
|
||||
$process->run();
|
||||
|
||||
if (! $process->isSuccessful()) {
|
||||
return (object) [
|
||||
'success' => false,
|
||||
'exception' => new \RuntimeException('FFmpeg failed: '.$process->getErrorOutput()),
|
||||
];
|
||||
throw new \RuntimeException('FFmpeg failed: '.$process->getErrorOutput());
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'success' => true,
|
||||
'name' => $filename,
|
||||
'path' => $output_path,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch video elements for rendering.
|
||||
*/
|
||||
private static function get_video_elements(Video $video): Collection
|
||||
{
|
||||
return $video->video_elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract render settings or defaults.
|
||||
*/
|
||||
private static function get_render_settings(Video $video): array
|
||||
{
|
||||
return [
|
||||
'video_bitrate' => $video->render_settings?->video_bitrate ?? '3M',
|
||||
'audio_bitrate' => $video->render_settings?->audio_bitrate ?? '128k',
|
||||
'fps' => $video->render_settings?->fps ?? '30',
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Helpers/FirstParty/Render/RenderConstants.php
Normal file
27
app/Helpers/FirstParty/Render/RenderConstants.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers\FirstParty\Render;
|
||||
|
||||
class RenderConstants
|
||||
{
|
||||
const STATUS_PLANNED = 'STATUS_PLANNED'; // the render is queued for rendering
|
||||
|
||||
const STATUS_WAITING = 'STATUS_WAITING'; // the render is waiting for a third-party service (e.g., OpenAI or ElevenLabs) to finish
|
||||
|
||||
const STATUS_TRANSCRIBING = 'STATUS_TRANSCRIBING'; // an input file is being transcribed
|
||||
|
||||
const STATUS_RENDERING = 'STATUS_RENDERING'; // the render is being processed
|
||||
|
||||
const STATUS_SUCCEEDED = 'STATUS_SUCCEEDED'; // the render has been completed successfully
|
||||
|
||||
const STATUS_FAILED = 'STATUS_FAILED'; // the render failed due to the reason specified in the error_message field
|
||||
|
||||
const STATUSES = [
|
||||
self::STATUS_PLANNED,
|
||||
self::STATUS_WAITING,
|
||||
self::STATUS_TRANSCRIBING,
|
||||
self::STATUS_RENDERING,
|
||||
self::STATUS_SUCCEEDED,
|
||||
self::STATUS_FAILED,
|
||||
];
|
||||
}
|
||||
71
app/Helpers/Global/cast_helpers.php
Normal file
71
app/Helpers/Global/cast_helpers.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
if (! function_exists('array_to_object_2025')) {
|
||||
/**
|
||||
* A magic function that turns all nested array into nested objects. Calling it with no parameter would result in a empty object.
|
||||
*
|
||||
* @param mixed $array The array to be recursively casted into an object.
|
||||
* @return \stdClass|null Returns null if the given parameter is not either an array or an object.
|
||||
*/
|
||||
function array_to_object_2025($array = []): ?stdClass
|
||||
{
|
||||
if (is_object($array)) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if (! is_array($array)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$object = new \stdClass;
|
||||
|
||||
if (! isset($array) || empty($array)) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
foreach ($array as $k => $v) {
|
||||
if (mb_strlen($k)) {
|
||||
if (is_array($v)) {
|
||||
if (array_is_assoc($v)) {
|
||||
// Convert associative arrays to objects
|
||||
$object->{$k} = array_to_object_2025($v);
|
||||
} else {
|
||||
// For indexed arrays, keep them as arrays but process their elements
|
||||
$object->{$k} = [];
|
||||
foreach ($v as $idx => $item) {
|
||||
if (is_array($item)) {
|
||||
$object->{$k}[$idx] = array_to_object_2025($item);
|
||||
} else {
|
||||
$object->{$k}[$idx] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$object->{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('array_is_assoc')) {
|
||||
/**
|
||||
* Determines whether or not an array is an associative array.
|
||||
*
|
||||
* @param array $array The array to be evaluated.
|
||||
*/
|
||||
function array_is_assoc($array): bool
|
||||
{
|
||||
if (! is_array($array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($array === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_keys($array) !== range(0, count($array) - 1);
|
||||
}
|
||||
}
|
||||
37
app/Helpers/Global/comparision_helpers.php
Normal file
37
app/Helpers/Global/comparision_helpers.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
if (! function_exists('is_empty')) {
|
||||
/**
|
||||
* A better function to check if a value is empty or null. Strings, arrays, and Objects are supported.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
function is_empty($value): bool
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
if ($value === '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
if (count($value) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
|
||||
if (count($value) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
8
app/Helpers/Global/generation_helpers.php
Normal file
8
app/Helpers/Global/generation_helpers.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
if (! function_exists('epoch_now_timestamp')) {
|
||||
function epoch_now_timestamp($multiplier = 1000)
|
||||
{
|
||||
return (int) round(microtime(true) * $multiplier);
|
||||
}
|
||||
}
|
||||
6
app/Helpers/Global/helpers.php
Normal file
6
app/Helpers/Global/helpers.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
include 'comparision_helpers.php';
|
||||
include 'cast_helpers.php';
|
||||
include 'generation_helpers.php';
|
||||
include 'user_access_helpers.php';
|
||||
58
app/Helpers/Global/user_access_helpers.php
Normal file
58
app/Helpers/Global/user_access_helpers.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
if (! function_exists('user_is_master_admin')) {
|
||||
|
||||
function user_is_master_admin(User $user)
|
||||
{
|
||||
$emails = ['autopilotshorts@gmail.com', 'team@autopilotshorts.com', 'charles@exastellar.com'];
|
||||
$user_id = 1;
|
||||
|
||||
if ($user->id == $user_id) {
|
||||
return true;
|
||||
} elseif (in_array($user->email, $emails)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('user_is_blocked_from_purchase')) {
|
||||
|
||||
function user_is_blocked_from_purchase(User $user)
|
||||
{
|
||||
$emails = ['productionlittlebird@gmail.com'];
|
||||
|
||||
if (in_array($user->email, $emails)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('user_can_generate_demo_videos')) {
|
||||
|
||||
function user_can_generate_demo_videos(User $user)
|
||||
{
|
||||
return user_is_master_admin($user);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('user_is_external_reviewer')) {
|
||||
|
||||
function user_is_external_reviewer(User $user)
|
||||
{
|
||||
$emails = [
|
||||
'shaebaelish.623259@gmail.com',
|
||||
];
|
||||
|
||||
if (in_array($user->email, $emails)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
13
app/Http/Controllers/AdminDashboardController.php
Normal file
13
app/Http/Controllers/AdminDashboardController.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AdminDashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('admin/dashboard');
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
51
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the login page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/login', [
|
||||
'canResetPassword' => Route::has('password.request'),
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password page.
|
||||
*/
|
||||
public function show(): Response
|
||||
{
|
||||
return Inertia::render('auth/confirm-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the email verification prompt page.
|
||||
*/
|
||||
public function __invoke(Request $request): Response|RedirectResponse
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: Inertia::render('auth/verify-email', ['status' => $request->session()->get('status')]);
|
||||
}
|
||||
}
|
||||
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the password reset page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/reset-password', [
|
||||
'email' => $request->email,
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status == Password::PasswordReset) {
|
||||
return to_route('login')->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [__($status)],
|
||||
]);
|
||||
}
|
||||
}
|
||||
41
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
41
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the password reset link request page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/forgot-password', [
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
return back()->with('status', __('A reset link will be sent if the account exists.'));
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
51
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the registration page.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('auth/register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return to_route('dashboard');
|
||||
}
|
||||
}
|
||||
137
app/Http/Controllers/Auth/SanctumAuthController.php
Normal file
137
app/Http/Controllers/Auth/SanctumAuthController.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class SanctumAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Register a new user and return a token
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||
'password' => ['required', 'confirmed', Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
$token = $user->createToken('auth_token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => [
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'user' => $user,
|
||||
],
|
||||
'message' => 'Registration completed successfully.',
|
||||
],
|
||||
], 201);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'data' => $e->errors(),
|
||||
'message' => 'Please review your inputs before submitting again.',
|
||||
],
|
||||
], 422);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'data' => [],
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login a user and return a token
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
if (! Auth::attempt($request->only('email', 'password'))) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'data' => [],
|
||||
'message' => 'Invalid credentials provided.',
|
||||
],
|
||||
], 401);
|
||||
}
|
||||
|
||||
$user = User::where('email', $request->email)->firstOrFail();
|
||||
$token = $user->createToken('auth_token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => [
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'user' => $user,
|
||||
],
|
||||
'message' => 'Authentication successful.',
|
||||
],
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'data' => $e->errors(),
|
||||
'message' => 'Please review your inputs before submitting again.',
|
||||
],
|
||||
], 422);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'data' => [],
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the user (revoke the token)
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => [
|
||||
'data' => [],
|
||||
'message' => 'Successfully signed out.',
|
||||
],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'data' => [],
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
30
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
/** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */
|
||||
$user = $request->user();
|
||||
|
||||
event(new Verified($user));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
18
app/Http/Controllers/FrontHomeController.php
Normal file
18
app/Http/Controllers/FrontHomeController.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class FrontHomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
if (App::environment('local')) {
|
||||
return Inertia::render('welcome');
|
||||
}
|
||||
|
||||
return Inertia::render('comingsoon');
|
||||
}
|
||||
}
|
||||
377
app/Http/Controllers/RenderController.php
Normal file
377
app/Http/Controllers/RenderController.php
Normal file
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\FirstParty\Render\RenderConstants;
|
||||
use App\Models\User;
|
||||
use App\Models\Video;
|
||||
use App\Models\VideoCaption;
|
||||
use App\Models\VideoElement;
|
||||
use App\Models\VideoRender;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use stdClass;
|
||||
use Str;
|
||||
|
||||
class RenderController extends Controller
|
||||
{
|
||||
public function getVideoElements(Request $request, string $uuid)
|
||||
{
|
||||
if (! Str::isUuid($uuid)) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => 'Invalid UUID format.',
|
||||
],
|
||||
], 400);
|
||||
}
|
||||
|
||||
$video = Video::with('video_elements')->where('uuid', $uuid)->first();
|
||||
|
||||
if (! $video) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => 'Video not found.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json((object) [
|
||||
'success' => [
|
||||
'data' => [
|
||||
'video_elements' => $video->video_elements,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
if (! $video_render) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => 'Video render not found.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$video = Video::where('id', $video_render->video_id)->first();
|
||||
|
||||
if (! $video) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => 'Video not found.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function startRender(Request $request)
|
||||
{
|
||||
|
||||
$video_render_request = array_to_object_2025($request->all());
|
||||
|
||||
$video_render_action = $this->saveUserVideoRenderRequest(Auth::user(), $video_render_request);
|
||||
|
||||
if (! $video_render_action->success) {
|
||||
$error_message = $video_render_action?->message ? $video_render_action->message : 'Unable to render, possibly because the video is already being rendered. Check external ID.';
|
||||
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => $error_message,
|
||||
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
// Create a video
|
||||
return response()->json((object) [
|
||||
'success' => [
|
||||
'data' => [
|
||||
'uuid' => $video_render_action->model->uuid,
|
||||
'status' => $video_render_action->model->status,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function renderStatus(Request $request, string $uuid)
|
||||
{
|
||||
if (! Str::isUuid($uuid)) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => 'Invalid UUID.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
$video_render = VideoRender::where('uuid', $uuid)->first();
|
||||
|
||||
if (! $video_render) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'message' => 'Video render not found.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json((object) [
|
||||
'success' => [
|
||||
'data' => [
|
||||
'uuid' => $video_render->uuid,
|
||||
'status' => $video_render->status,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function allRenders(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$video_renders = VideoRender::where('user_id', $user->id)
|
||||
->orderBy('id', 'desc')->get();
|
||||
|
||||
return response()->json((object) [
|
||||
'success' => [
|
||||
'data' => [
|
||||
'video_renders' => $video_renders,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function saveUserVideoRenderRequest(User $user, stdClass $video_render_request)
|
||||
{
|
||||
// check if there is an existing video render request with the same external id
|
||||
|
||||
$video_render_is_busy = VideoRender::where('user_id', $user->id)
|
||||
->where('external_id', $video_render_request->external_id)
|
||||
->whereIn('status', [
|
||||
RenderConstants::STATUS_PLANNED,
|
||||
RenderConstants::STATUS_WAITING,
|
||||
RenderConstants::STATUS_TRANSCRIBING,
|
||||
RenderConstants::STATUS_RENDERING,
|
||||
])->first();
|
||||
|
||||
if ($video_render_is_busy) {
|
||||
return (object) [
|
||||
'success' => false,
|
||||
'message' => 'Video is already in queue or rendering. Status: '.$video_render_is_busy->status,
|
||||
];
|
||||
}
|
||||
// dd($video_render_request);
|
||||
|
||||
$video = $this->getUserVideoByExternalId($user, $video_render_request->external_id);
|
||||
|
||||
$video = $this->updateVideoWithRenderRequest($video, $video_render_request);
|
||||
|
||||
$video = $this->saveUserVideo($video);
|
||||
|
||||
$this->saveVideoCaptions($video);
|
||||
|
||||
try {
|
||||
$this->saveVideoElements($video);
|
||||
} catch (\Exception $e) {
|
||||
return (object) [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
$new_video_render = VideoRender::create([
|
||||
'user_id' => $user->id,
|
||||
'video_id' => $video->id,
|
||||
'external_id' => $video_render_request->external_id,
|
||||
'payload' => $video_render_request,
|
||||
'status' => RenderConstants::STATUS_PLANNED,
|
||||
]);
|
||||
|
||||
return (object) [
|
||||
'success' => true,
|
||||
'model' => $new_video_render,
|
||||
];
|
||||
}
|
||||
|
||||
private function saveVideoCaptions(Video $video)
|
||||
{
|
||||
VideoCaption::where('video_id', $video->id)->delete();
|
||||
|
||||
if (isset($video->payload->captions)) {
|
||||
foreach ($video->payload->captions as $caption) {
|
||||
$video_caption = new VideoCaption;
|
||||
$video_caption->video_id = $video->id;
|
||||
$video_caption->time = $caption->time;
|
||||
$video_caption->duration = $caption->duration;
|
||||
$video_caption->text = $caption->text;
|
||||
|
||||
if (isset($caption->parameters)) {
|
||||
$video_caption->parameters = $caption->parameters;
|
||||
}
|
||||
|
||||
$video_caption->words = $caption->words;
|
||||
$video_caption->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function saveVideoElements(Video $video)
|
||||
{
|
||||
if (isset($video->payload->elements)) {
|
||||
$existing_video_elements = VideoElement::where('video_id', $video->id)->get();
|
||||
|
||||
// Create a lookup array of existing elements by asset hash, but keep ALL matching elements
|
||||
$existing_elements_by_hash = [];
|
||||
foreach ($existing_video_elements as $existing_element) {
|
||||
if (! isset($existing_elements_by_hash[$existing_element->asset_hash])) {
|
||||
$existing_elements_by_hash[$existing_element->asset_hash] = [];
|
||||
}
|
||||
$existing_elements_by_hash[$existing_element->asset_hash][] = $existing_element;
|
||||
}
|
||||
|
||||
// Track which elements we're keeping
|
||||
$kept_element_ids = [];
|
||||
// Track which hashes we've already processed to handle duplicates
|
||||
$processed_hashes = [];
|
||||
|
||||
// Validate element URL if exist
|
||||
foreach ($video->payload->elements as $element) {
|
||||
if (isset($element->url)) {
|
||||
if (! $this->validateElementUrl($element->url)) {
|
||||
throw new \Exception('Invalid URL: '.$element->url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save
|
||||
foreach ($video->payload->elements as $element) {
|
||||
$asset_hash = $this->getAssetHash($video, $element->url);
|
||||
|
||||
if (isset($existing_elements_by_hash[$asset_hash]) && count($existing_elements_by_hash[$asset_hash]) > 0) {
|
||||
// Get the next unused element with this hash
|
||||
$unused_elements = array_filter($existing_elements_by_hash[$asset_hash], function ($elem) use ($kept_element_ids) {
|
||||
return ! in_array($elem->id, $kept_element_ids);
|
||||
});
|
||||
|
||||
if (count($unused_elements) > 0) {
|
||||
// Use the first unused element
|
||||
$video_element = reset($unused_elements);
|
||||
$kept_element_ids[] = $video_element->id;
|
||||
} else {
|
||||
// All elements with this hash are already used, create a new one
|
||||
$video_element = new VideoElement;
|
||||
$video_element->video_id = $video->id;
|
||||
$video_element->asset_hash = $asset_hash;
|
||||
$video_element->original_asset_url = $element->url;
|
||||
}
|
||||
} else {
|
||||
// No elements with this hash, create a new one
|
||||
$video_element = new VideoElement;
|
||||
$video_element->video_id = $video->id;
|
||||
$video_element->asset_hash = $asset_hash;
|
||||
$video_element->original_asset_url = $element->url;
|
||||
}
|
||||
|
||||
if (isset($element->external_reference) && ! is_empty($element->external_reference)) {
|
||||
$video_element->external_reference = $element->external_reference;
|
||||
}
|
||||
$video_element->type = $element->type;
|
||||
$video_element->time = $element->time;
|
||||
$video_element->track = $element->track;
|
||||
$video_element->duration = $element->duration;
|
||||
|
||||
if (isset($element->parameters)) {
|
||||
$video_element->parameters = $element->parameters;
|
||||
}
|
||||
|
||||
$video_element->save();
|
||||
|
||||
// Add newly created ID to kept list if needed
|
||||
if (! in_array($video_element->id, $kept_element_ids)) {
|
||||
$kept_element_ids[] = $video_element->id;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete elements that weren't in the payload
|
||||
if (count($existing_video_elements) > 0) {
|
||||
VideoElement::where('video_id', $video->id)
|
||||
->whereNotIn('id', $kept_element_ids)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAssetHash(Video $video, $url)
|
||||
{
|
||||
return hash('sha256', $video->id.'-'.$url);
|
||||
}
|
||||
|
||||
private function validateElementUrl(string $url)
|
||||
{
|
||||
// First check if it's a valid URL format
|
||||
$validator = Validator::make(['url' => $url], [
|
||||
'url' => 'required|url',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate url by making a http head request, return true | false boolean
|
||||
try {
|
||||
// Using Laravel's HTTP client to make a HEAD request
|
||||
$response = Http::withOptions([
|
||||
'timeout' => 10,
|
||||
'connect_timeout' => 5,
|
||||
'verify' => true,
|
||||
'http_errors' => false, // Don't throw exceptions for 4xx/5xx responses
|
||||
])->head($url);
|
||||
|
||||
// Check if the response is successful (2xx status code) or a redirect (3xx)
|
||||
return $response->successful();
|
||||
} catch (\Exception $e) {
|
||||
// Catch any exceptions (connection issues, invalid URLs, etc.)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function saveUserVideo(Video $video)
|
||||
{
|
||||
if ($video->isDirty()) {
|
||||
$video->save();
|
||||
}
|
||||
|
||||
return $video;
|
||||
}
|
||||
|
||||
private function updateVideoWithRenderRequest(Video $video, stdClass $video_render_request)
|
||||
{
|
||||
// dd($video_render_request);
|
||||
|
||||
$video->content_type = $video_render_request->content_type;
|
||||
$video->width = $video_render_request->width;
|
||||
$video->height = $video_render_request->height;
|
||||
$video->aspect_ratio = $video_render_request->aspect_ratio;
|
||||
$video->payload = $video_render_request;
|
||||
$video->render_settings = (object) [
|
||||
'video_bitrate' => $video_render_request->video_bitrate,
|
||||
'audio_bitrate' => $video_render_request->audio_bitrate,
|
||||
'fps' => $video_render_request->fps,
|
||||
];
|
||||
|
||||
return $video;
|
||||
}
|
||||
|
||||
private function getUserVideoByExternalId(User $user, string $external_id)
|
||||
{
|
||||
$video = Video::where('user_id', $user->id)
|
||||
->where('external_id', $external_id)
|
||||
->first();
|
||||
|
||||
if (! $video) {
|
||||
|
||||
$video = new Video;
|
||||
$video->user_id = $user->id;
|
||||
$video->external_id = $external_id;
|
||||
}
|
||||
|
||||
return $video;
|
||||
}
|
||||
}
|
||||
39
app/Http/Controllers/Settings/PasswordController.php
Normal file
39
app/Http/Controllers/Settings/PasswordController.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's password settings page.
|
||||
*/
|
||||
public function edit(): Response
|
||||
{
|
||||
return Inertia::render('settings/password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
63
app/Http/Controllers/Settings/ProfileController.php
Normal file
63
app/Http/Controllers/Settings/ProfileController.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's profile settings page.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('settings/profile', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile settings.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return to_route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
27
app/Http/Controllers/TestController.php
Normal file
27
app/Http/Controllers/TestController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\RunVideoRenderPipelineJob;
|
||||
use App\Models\Video;
|
||||
|
||||
class TestController extends Controller
|
||||
{
|
||||
public function RunVideoRenderPipelineJob()
|
||||
{
|
||||
$dispatch_job = true;
|
||||
$video = Video::latest()->first();
|
||||
|
||||
if ($video) {
|
||||
|
||||
if ($dispatch_job) {
|
||||
RunVideoRenderPipelineJob::dispatch($video->id)->onQueue('render');
|
||||
} else {
|
||||
$job = new RunVideoRenderPipelineJob($video->id);
|
||||
$job->handle();
|
||||
}
|
||||
} else {
|
||||
echo 'NO VIDEO';
|
||||
}
|
||||
}
|
||||
}
|
||||
13
app/Http/Controllers/UserDashboardController.php
Normal file
13
app/Http/Controllers/UserDashboardController.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Inertia\Inertia;
|
||||
|
||||
class UserDashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('dashboard');
|
||||
}
|
||||
}
|
||||
23
app/Http/Middleware/AdminMiddleware.php
Normal file
23
app/Http/Middleware/AdminMiddleware.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AdminMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (user_is_master_admin($request->user())) {
|
||||
return $next($request);
|
||||
}
|
||||
abort(403, 'You are not authorized to perform this action.');
|
||||
}
|
||||
}
|
||||
23
app/Http/Middleware/HandleAppearance.php
Normal file
23
app/Http/Middleware/HandleAppearance.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HandleAppearance
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
View::share('appearance', $request->cookie('appearance') ?? 'system');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
57
app/Http/Middleware/HandleInertiaRequests.php
Normal file
57
app/Http/Middleware/HandleInertiaRequests.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Middleware;
|
||||
use Tighten\Ziggy\Ziggy;
|
||||
|
||||
class HandleInertiaRequests extends Middleware
|
||||
{
|
||||
/**
|
||||
* The root template that's loaded on the first page visit.
|
||||
*
|
||||
* @see https://inertiajs.com/server-side-setup#root-template
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rootView = 'app';
|
||||
|
||||
/**
|
||||
* Determines the current asset version.
|
||||
*
|
||||
* @see https://inertiajs.com/asset-versioning
|
||||
*/
|
||||
public function version(Request $request): ?string
|
||||
{
|
||||
return parent::version($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the props that are shared by default.
|
||||
*
|
||||
* @see https://inertiajs.com/shared-data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function share(Request $request): array
|
||||
{
|
||||
[$message, $author] = str(Inspiring::quotes()->random())->explode('-');
|
||||
|
||||
return [
|
||||
...parent::share($request),
|
||||
'name' => config('app.name'),
|
||||
'quote' => ['message' => trim($message), 'author' => trim($author)],
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
'user_is_admin' => user_is_master_admin($request->user()),
|
||||
],
|
||||
'ziggy' => fn (): array => [
|
||||
...(new Ziggy)->toArray(),
|
||||
'location' => $request->url(),
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
];
|
||||
}
|
||||
}
|
||||
85
app/Http/Requests/Auth/LoginRequest.php
Normal file
85
app/Http/Requests/Auth/LoginRequest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the request's credentials.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => __('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($this->throttleKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the login request is not rate limited.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function ensureIsNotRateLimited(): void
|
||||
{
|
||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event(new Lockout($this));
|
||||
|
||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => __('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiting throttle key for the request.
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
}
|
||||
}
|
||||
32
app/Http/Requests/Settings/ProfileUpdateRequest.php
Normal file
32
app/Http/Requests/Settings/ProfileUpdateRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'lowercase',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class)->ignore($this->user()->id),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
79
app/Jobs/RunVideoRenderPipelineJob.php
Normal file
79
app/Jobs/RunVideoRenderPipelineJob.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
||||
use App\Helpers\FirstParty\Render\FfmpegVideoRenderer;
|
||||
use App\Helpers\FirstParty\Render\RenderConstants;
|
||||
use App\Models\Video;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class RunVideoRenderPipelineJob implements ShouldQueue
|
||||
{
|
||||
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $video_id;
|
||||
|
||||
public $timeout = 300;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(int $video_id)
|
||||
{
|
||||
$this->onQueue('general_video');
|
||||
$this->video_id = $video_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->batch()?->cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$video = Video::with('video_elements', 'latest_render')->find($this->video_id);
|
||||
|
||||
if (! $video) {
|
||||
return;
|
||||
}
|
||||
|
||||
$video_render = $video->latest_render;
|
||||
$video_render->status = RenderConstants::STATUS_RENDERING;
|
||||
$video_render->processing_started_at = now();
|
||||
$video_render->save();
|
||||
|
||||
$output = FfmpegVideoRenderer::render($video);
|
||||
|
||||
if ($output->success) {
|
||||
$video_render->status = RenderConstants::STATUS_SUCCEEDED;
|
||||
|
||||
$saved_media = MediaEngine::addMedia(
|
||||
MediaEngine::getCollectionKeyByOwnerMediaType('user', 'video'),
|
||||
'video',
|
||||
MediaEngine::USER_RENDERED,
|
||||
MediaEngine::USER,
|
||||
$output->name,
|
||||
file_get_contents($output->path),
|
||||
);
|
||||
|
||||
$video_render->completed_video_uuid = $saved_media->uuid;
|
||||
$video_render->completed_video_full_url = MediaEngine::getMediaCloudUrl($saved_media);
|
||||
$video_render->processing_finished_at = now();
|
||||
$video_render->save();
|
||||
} else {
|
||||
$video_render->processing_finished_at = now();
|
||||
$video_render->status = RenderConstants::STATUS_FAILED;
|
||||
$video_render->save();
|
||||
|
||||
throw $output->exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
app/Jobs/SaveVideoElementsBatchJob.php
Normal file
78
app/Jobs/SaveVideoElementsBatchJob.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
||||
use App\Models\Media;
|
||||
use App\Models\Video;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SaveVideoElementsBatchJob implements ShouldQueue
|
||||
{
|
||||
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $video_id;
|
||||
|
||||
public $timeout = 300;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(int $video_id)
|
||||
{
|
||||
$this->onQueue('general_video');
|
||||
$this->video_id = $video_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->batch()?->cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$video = Video::with('video_elements')->find($this->video_id);
|
||||
|
||||
if (! $video) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($video->video_elements as $video_element) {
|
||||
|
||||
if (! is_empty($video_element->asset_uuid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// dump($video_element);
|
||||
|
||||
// Media Details: Filename, extension and mimetype
|
||||
$media_details = MediaEngine::getFileDetailsbyUrl($video_element->original_asset_url);
|
||||
|
||||
// Media Content: Blob
|
||||
$media_content = file_get_contents($video_element->original_asset_url);
|
||||
|
||||
// Media Filename: generate a new filename
|
||||
$media_filename = $video_element->type.'_'.epoch_now_timestamp().'.'.$media_details->extension;
|
||||
|
||||
$saved_media = MediaEngine::addMedia(
|
||||
MediaEngine::getCollectionKeyByOwnerMediaType('user', $video_element->type),
|
||||
$video_element->type,
|
||||
MediaEngine::USER_UPLOADED,
|
||||
MediaEngine::USER,
|
||||
$media_filename,
|
||||
$media_content,
|
||||
);
|
||||
|
||||
$video_element->asset_uuid = $saved_media->uuid;
|
||||
$video_element->asset_url = MediaEngine::getMediaCloudUrl($saved_media);
|
||||
$video_element->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
app/Models/Media.php
Normal file
75
app/Models/Media.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class Media
|
||||
*
|
||||
* @property int $id
|
||||
* @property uuid|null $uuid
|
||||
* @property int $media_collection_id
|
||||
* @property int|null $user_id
|
||||
* @property string $media_type
|
||||
* @property string $media_source
|
||||
* @property string $media_provider
|
||||
* @property string $name
|
||||
* @property string $mime_type
|
||||
* @property string $file_name
|
||||
* @property string $file_path
|
||||
* @property string $disk
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property MediaCollection $media_collection
|
||||
*/
|
||||
class Media extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'medias';
|
||||
|
||||
protected $casts = [
|
||||
'media_collection_id' => 'int',
|
||||
'user_id' => 'int',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'media_collection_id',
|
||||
'user_id',
|
||||
'media_type',
|
||||
'media_source',
|
||||
'media_provider',
|
||||
'mime_type',
|
||||
'file_name',
|
||||
'file_path',
|
||||
'disk',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'media_url',
|
||||
];
|
||||
|
||||
public function media_collection()
|
||||
{
|
||||
return $this->belongsTo(MediaCollection::class);
|
||||
}
|
||||
|
||||
protected function mediaUrl(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function ($value, $attributes) {
|
||||
return Storage::disk($attributes['disk'])->url($attributes['file_path'].$attributes['file_name']);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
44
app/Models/MediaCollection.php
Normal file
44
app/Models/MediaCollection.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class MediaCollection
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $key
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property bool $is_system
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Collection|Media[] $media
|
||||
*/
|
||||
class MediaCollection extends Model
|
||||
{
|
||||
protected $table = 'media_collections';
|
||||
|
||||
protected $casts = [
|
||||
'is_system' => 'bool',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'name',
|
||||
'description',
|
||||
'is_system',
|
||||
];
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->hasMany(Media::class);
|
||||
}
|
||||
}
|
||||
62
app/Models/User.php
Normal file
62
app/Models/User.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Str;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'email',
|
||||
'password',
|
||||
'uuid',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The "booted" method of the model.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
$model->uuid = $model->uuid ?? (string) Str::uuid();
|
||||
});
|
||||
}
|
||||
}
|
||||
80
app/Models/Video.php
Normal file
80
app/Models/Video.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Str;
|
||||
|
||||
/**
|
||||
* Class Video
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $external_id
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Collection|VideoRender[] $video_renders
|
||||
*/
|
||||
class Video extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'videos';
|
||||
|
||||
protected $casts = [
|
||||
'width' => 'int',
|
||||
'height' => 'int',
|
||||
'payload' => 'object',
|
||||
'render_settings' => 'object',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'external_id',
|
||||
'content_type',
|
||||
'width',
|
||||
'height',
|
||||
'aspect_ratio',
|
||||
'payload',
|
||||
'render_settings',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'id',
|
||||
];
|
||||
|
||||
public function video_renders()
|
||||
{
|
||||
return $this->hasMany(VideoRender::class)->orderBy('id', 'DESC');
|
||||
}
|
||||
|
||||
public function video_captions()
|
||||
{
|
||||
return $this->hasMany(VideoCaption::class)->orderBy('time', 'ASC');
|
||||
}
|
||||
|
||||
public function video_elements()
|
||||
{
|
||||
return $this->hasMany(VideoElement::class)->orderBy('time', 'ASC');
|
||||
}
|
||||
|
||||
public function latest_render()
|
||||
{
|
||||
return $this->hasOne(VideoRender::class)->latest();
|
||||
}
|
||||
|
||||
/**
|
||||
* The "booted" method of the model.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
$model->uuid = $model->uuid ?? (string) Str::uuid();
|
||||
});
|
||||
}
|
||||
}
|
||||
56
app/Models/VideoCaption.php
Normal file
56
app/Models/VideoCaption.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class VideoCaption
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $video_id
|
||||
* @property float $time
|
||||
* @property float $duration
|
||||
* @property string $text
|
||||
* @property string $parameters
|
||||
* @property string $payload
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string|null $deleted_at
|
||||
* @property Video $video
|
||||
*/
|
||||
class VideoCaption extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'video_captions';
|
||||
|
||||
protected $casts = [
|
||||
'video_id' => 'int',
|
||||
'time' => 'float',
|
||||
'duration' => 'float',
|
||||
'parameters' => 'object',
|
||||
'words' => 'object',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'video_id',
|
||||
'time',
|
||||
'duration',
|
||||
|
||||
'text',
|
||||
'parameters',
|
||||
'words',
|
||||
];
|
||||
|
||||
public function video()
|
||||
{
|
||||
return $this->belongsTo(Video::class);
|
||||
}
|
||||
}
|
||||
54
app/Models/VideoElement.php
Normal file
54
app/Models/VideoElement.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class VideoElement
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $original_asset_url
|
||||
* @property uuid|null $asset_uuid
|
||||
* @property string|null $asset_url
|
||||
* @property string $type
|
||||
* @property float $time
|
||||
* @property int $track
|
||||
* @property float $duration
|
||||
* @property string $parameters
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*/
|
||||
class VideoElement extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'video_elements';
|
||||
|
||||
protected $casts = [
|
||||
'time' => 'float',
|
||||
'track' => 'int',
|
||||
'duration' => 'float',
|
||||
'parameters' => 'object',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'parent_element_id',
|
||||
'external_reference',
|
||||
'asset_hash',
|
||||
'original_asset_url',
|
||||
'asset_uuid',
|
||||
'asset_url',
|
||||
'type',
|
||||
'time',
|
||||
'track',
|
||||
'duration',
|
||||
'parameters',
|
||||
];
|
||||
}
|
||||
83
app/Models/VideoRender.php
Normal file
83
app/Models/VideoRender.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Str;
|
||||
|
||||
/**
|
||||
* Class VideoRender
|
||||
*
|
||||
* @property int $id
|
||||
* @property uuid $uuid
|
||||
* @property string|null $external_id
|
||||
* @property int|null $video_id
|
||||
* @property int|null $user_id
|
||||
* @property string $payload
|
||||
* @property string $status
|
||||
* @property Carbon|null $processing_started_at
|
||||
* @property Carbon|null $processing_finished_at
|
||||
* @property uuid|null $completed_video_uuid
|
||||
* @property string|null $completed_video_full_url
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Video|null $video
|
||||
* @property User|null $user
|
||||
*/
|
||||
class VideoRender extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'video_renders';
|
||||
|
||||
protected $casts = [
|
||||
'video_id' => 'int',
|
||||
'user_id' => 'int',
|
||||
'processing_started_at' => 'datetime',
|
||||
'processing_finished_at' => 'datetime',
|
||||
'payload' => 'object',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'external_id',
|
||||
'video_id',
|
||||
'user_id',
|
||||
'payload',
|
||||
'status',
|
||||
'processing_started_at',
|
||||
'processing_finished_at',
|
||||
'completed_video_uuid',
|
||||
'completed_video_full_url',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'id',
|
||||
];
|
||||
|
||||
public function video()
|
||||
{
|
||||
return $this->belongsTo(Video::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The "booted" method of the model.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
$model->uuid = $model->uuid ?? (string) Str::uuid();
|
||||
});
|
||||
}
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
36
app/Providers/HorizonServiceProvider.php
Normal file
36
app/Providers/HorizonServiceProvider.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Horizon\Horizon;
|
||||
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
||||
|
||||
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Horizon::routeSmsNotificationsTo('15556667777');
|
||||
// Horizon::routeMailNotificationsTo('example@example.com');
|
||||
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Horizon gate.
|
||||
*
|
||||
* This gate determines who can access Horizon in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewHorizon', function ($user) {
|
||||
return in_array($user->email, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
18
artisan
Executable file
18
artisan
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
$status = $app->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
37
bootstrap/app.php
Normal file
37
bootstrap/app.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\HandleAppearance;
|
||||
use App\Http\Middleware\HandleInertiaRequests;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
then: function () {
|
||||
if (config('platform.general.enable_test_routes')) {
|
||||
Route::prefix('tests')
|
||||
->middleware('web')
|
||||
->group(base_path('routes/test.php'));
|
||||
}
|
||||
}
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->encryptCookies(except: ['appearance', 'sidebar_state']);
|
||||
|
||||
$middleware->web(append: [
|
||||
HandleAppearance::class,
|
||||
HandleInertiaRequests::class,
|
||||
AddLinkHeadersForPreloadedAssets::class,
|
||||
]);
|
||||
|
||||
$middleware->statefulApi();
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
//
|
||||
})->create();
|
||||
2
bootstrap/cache/.gitignore
vendored
Normal file
2
bootstrap/cache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
7
bootstrap/providers.php
Normal file
7
bootstrap/providers.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
Artesaos\SEOTools\Providers\SEOToolsServiceProvider::class,
|
||||
];
|
||||
21
components.json
Normal file
21
components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "resources/css/app.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
95
composer.json
Normal file
95
composer.json
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"$schema": "https://getcomposer.org/schema.json",
|
||||
"name": "laravel/react-starter-kit",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"framework"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"artesaos/seotools": "^1.3",
|
||||
"inertiajs/inertia-laravel": "^2.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/horizon": "^5.31",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"tightenco/ziggy": "^2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.5",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.18",
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^3.8",
|
||||
"pestphp/pest-plugin-laravel": "^3.1",
|
||||
"reliese/laravel": "^1.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
},
|
||||
"files": [
|
||||
"app/Helpers/Global/helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi",
|
||||
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||
"@php artisan migrate --graceful --ansi"
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
|
||||
],
|
||||
"dev:ssr": [
|
||||
"npm run build:ssr",
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"php artisan inertia:start-ssr\" --names=server,queue,logs,ssr"
|
||||
],
|
||||
"test": [
|
||||
"@php artisan config:clear --ansi",
|
||||
"@php artisan test"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
10159
composer.lock
generated
Normal file
10159
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
config/app.php
Normal file
126
config/app.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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
|
||||
| other UI elements where an application name needs to be displayed.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. The timezone
|
||||
| is set to "UTC" by default as it is suitable for most use cases.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is utilized by Laravel's encryption services and should be set
|
||||
| to a random, 32 character string to ensure that all encrypted values
|
||||
| are secure. You should do this prior to deploying the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
];
|
||||
115
config/auth.php
Normal file
115
config/auth.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication 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' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| which utilizes session storage plus the Eloquent user provider.
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| providers to represent the model / table. These providers may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options specify the behavior of Laravel's password
|
||||
| reset functionality, including the table utilized for token storage
|
||||
| and the user provider that is invoked to actually retrieve users.
|
||||
|
|
||||
| The expiry time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| window expires and users are asked to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
108
config/cache.php
Normal file
108
config/cache.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache store that will be used by the
|
||||
| framework. This connection is utilized if another isn't explicitly
|
||||
| specified when running a cache operation inside the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| 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: "array", "database", "file", "memcached",
|
||||
| "redis", "dynamodb", "octane", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_CACHE_CONNECTION'),
|
||||
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
'lock_path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 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.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||
|
||||
];
|
||||
174
config/database.php
Normal file
174
config/database.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for database operations. This is
|
||||
| the connection which will be utilized unless another connection
|
||||
| is explicitly specified when you execute a query / statement.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below are all of the database connections defined for your application.
|
||||
| An example configuration is provided for each database system which
|
||||
| is supported by Laravel. You're free to add / remove connections.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DB_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'busy_timeout' => null,
|
||||
'journal_mode' => null,
|
||||
'synchronous' => null,
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'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,
|
||||
'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,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run on the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as Memcached. You may define your connection settings here.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||
'persistent' => env('REDIS_PERSISTENT', false),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
93
config/filesystems.php
Normal file
93
config/filesystems.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application for file storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below you may configure as many filesystem disks as necessary, and you
|
||||
| may even configure multiple disks for the same driver. Examples for
|
||||
| most supported storage drivers are configured here for reference.
|
||||
|
|
||||
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/private'),
|
||||
'serve' => true,
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
'report' => 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,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'r2' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('CLOUDFLARE_R2_ACCESS_KEY_ID'),
|
||||
'secret' => env('CLOUDFLARE_R2_SECRET_ACCESS_KEY'),
|
||||
'region' => env('CLOUDFLARE_R2_REGION'),
|
||||
'bucket' => env('CLOUDFLARE_R2_BUCKET'),
|
||||
'url' => env('CLOUDFLARE_R2_URL'),
|
||||
'visibility' => 'public',
|
||||
'endpoint' => env('CLOUDFLARE_R2_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('CLOUDFLARE_R2_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
289
config/horizon.php
Normal file
289
config/horizon.php
Normal file
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the subdomain where Horizon will be accessible from. If this
|
||||
| setting is null, Horizon will reside under the same domain as the
|
||||
| application. Otherwise, this value will serve as the subdomain.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('HORIZON_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the URI path where Horizon will be accessible from. Feel free
|
||||
| to change this path to anything you like. Note that the URI will not
|
||||
| affect the paths of its internal API that aren't exposed to users.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('HORIZON_PATH', 'horizon'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Redis Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the name of the Redis connection where Horizon will store the
|
||||
| meta information required for it to function. It includes the list
|
||||
| of supervisors, failed jobs, job metrics, and other information.
|
||||
|
|
||||
*/
|
||||
|
||||
'use' => 'default',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Redis Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This prefix will be used when storing all Horizon data in Redis. You
|
||||
| may modify the prefix when you are running multiple installations
|
||||
| of Horizon on the same server so that they don't have problems.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env(
|
||||
'HORIZON_PREFIX',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Route Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These middleware will get attached onto each Horizon route, giving you
|
||||
| the chance to add your own middleware to this list or change any of
|
||||
| the existing middleware. Or, you can simply stick with this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Wait Time Thresholds
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to configure when the LongWaitDetected event
|
||||
| will be fired. Every connection / queue combination may have its
|
||||
| own, unique threshold (in seconds) before this event is fired.
|
||||
|
|
||||
*/
|
||||
|
||||
'waits' => [
|
||||
'redis:default' => 60,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Trimming Times
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you can configure for how long (in minutes) you desire Horizon to
|
||||
| persist the recent and failed jobs. Typically, recent jobs are kept
|
||||
| for one hour while all failed jobs are stored for an entire week.
|
||||
|
|
||||
*/
|
||||
|
||||
'trim' => [
|
||||
'recent' => 60,
|
||||
'pending' => 60,
|
||||
'completed' => 60,
|
||||
'recent_failed' => 10080,
|
||||
'failed' => 10080,
|
||||
'monitored' => 10080,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Silenced Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Silencing a job will instruct Horizon to not place the job in the list
|
||||
| of completed jobs within the Horizon dashboard. This setting may be
|
||||
| used to fully remove any noisy jobs from the completed jobs list.
|
||||
|
|
||||
*/
|
||||
|
||||
'silenced' => [
|
||||
// App\Jobs\ExampleJob::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Metrics
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you can configure how many snapshots should be kept to display in
|
||||
| the metrics graph. This will get used in combination with Horizon's
|
||||
| `horizon:snapshot` schedule to define how long to retain metrics.
|
||||
|
|
||||
*/
|
||||
|
||||
'metrics' => [
|
||||
'trim_snapshots' => [
|
||||
'job' => 24,
|
||||
'queue' => 24,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fast Termination
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When this option is enabled, Horizon's "terminate" command will not
|
||||
| wait on all of the workers to terminate unless the --wait option
|
||||
| is provided. Fast termination can shorten deployment delay by
|
||||
| allowing a new instance of Horizon to start while the last
|
||||
| instance will continue to terminate each of its workers.
|
||||
|
|
||||
*/
|
||||
|
||||
'fast_termination' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Memory Limit (MB)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value describes the maximum amount of memory the Horizon master
|
||||
| supervisor may consume before it is terminated and restarted. For
|
||||
| configuring these limits on your workers, see the next section.
|
||||
|
|
||||
*/
|
||||
|
||||
'memory_limit' => 64,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Worker Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the queue worker settings used by your application
|
||||
| in all environments. These supervisors and settings handle all your
|
||||
| queued jobs and will be provisioned by Horizon during deployment.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'supervisor-1' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['default'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 128,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
],
|
||||
],
|
||||
|
||||
'environments' => [
|
||||
'production' => [
|
||||
'supervisor-render' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['render'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 1024,
|
||||
'tries' => 1,
|
||||
'timeout' => 240,
|
||||
'nice' => 0,
|
||||
'rest' => 2,
|
||||
],
|
||||
'supervisor-media' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['media'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 1024,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
'rest' => 0,
|
||||
],
|
||||
'supervisor-default' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['default'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 1024,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
'rest' => 0,
|
||||
],
|
||||
],
|
||||
|
||||
'local' => [
|
||||
'supervisor-render' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['render'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 1024,
|
||||
'tries' => 1,
|
||||
'timeout' => 240,
|
||||
'nice' => 0,
|
||||
'rest' => 2,
|
||||
],
|
||||
'supervisor-media' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['media'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 1024,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
'rest' => 0,
|
||||
],
|
||||
'supervisor-default' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['default'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 1024,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
'rest' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
55
config/inertia.php
Normal file
55
config/inertia.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Server Side Rendering
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configures if and how Inertia uses Server Side Rendering
|
||||
| to pre-render each initial request made to your application's pages
|
||||
| so that server rendered HTML is delivered for the user's browser.
|
||||
|
|
||||
| See: https://inertiajs.com/server-side-rendering
|
||||
|
|
||||
*/
|
||||
|
||||
'ssr' => [
|
||||
'enabled' => true,
|
||||
'url' => 'http://127.0.0.1:13714',
|
||||
// 'bundle' => base_path('bootstrap/ssr/ssr.mjs'),
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Testing
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The values described here are used to locate Inertia components on the
|
||||
| filesystem. For instance, when using `assertInertia`, the assertion
|
||||
| attempts to locate the component as a file relative to the paths.
|
||||
|
|
||||
*/
|
||||
|
||||
'testing' => [
|
||||
|
||||
'ensure_pages_exist' => true,
|
||||
|
||||
'page_paths' => [
|
||||
resource_path('js/pages'),
|
||||
],
|
||||
|
||||
'page_extensions' => [
|
||||
'js',
|
||||
'jsx',
|
||||
'svelte',
|
||||
'ts',
|
||||
'tsx',
|
||||
'vue',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
132
config/logging.php
Normal file
132
config/logging.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
use Monolog\Processor\PsrLogMessageProcessor;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that is utilized to write
|
||||
| messages to your logs. The value provided here should match one of
|
||||
| the channels present in the list of "channels" configured below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Laravel
|
||||
| utilizes the Monolog PHP logging library, which includes a variety
|
||||
| of powerful log handlers and formatters that you're free to use.
|
||||
|
|
||||
| Available drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => explode(',', env('LOG_STACK', 'single')),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => StreamHandler::class,
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
116
config/mail.php
Normal file
116
config/mail.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send all email
|
||||
| messages unless another mailer is explicitly specified when sending
|
||||
| the message. All additional mailers can be configured within the
|
||||
| "mailers" array. Examples of each type of mailer are provided.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'log'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers that can be used
|
||||
| when delivering an email. You may specify which one you're using for
|
||||
| your mailers below. You may also add additional mailers if needed.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "resend", "log", "array",
|
||||
| "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => env('MAIL_SCHEME'),
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||
'port' => env('MAIL_PORT', 2525),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'transport' => 'resend',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
|
||||
'roundrobin' => [
|
||||
'transport' => 'roundrobin',
|
||||
'mailers' => [
|
||||
'ses',
|
||||
'postmark',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all emails sent by your application to be sent from
|
||||
| the same address. Here you may specify a name and address that is
|
||||
| used globally for all emails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
];
|
||||
534
config/models.php
Normal file
534
config/models.php
Normal file
@@ -0,0 +1,534 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| In this section you may define the default configuration for each model
|
||||
| that will be generated from any database.
|
||||
|
|
||||
*/
|
||||
|
||||
'*' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model Files Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| We need a location to store your new generated files. All files will be
|
||||
| placed within this directory. When you turn on base files, they will
|
||||
| be placed within a Base directory inside this location.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => app_path('Models'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model Namespace
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Every generated model will belong to this namespace. It is suggested
|
||||
| that this namespace should follow PSR-4 convention and be very
|
||||
| similar to the path of your models defined above.
|
||||
|
|
||||
*/
|
||||
|
||||
'namespace' => 'App\Models',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Parent Class
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All Eloquent models should inherit from Eloquent Model class. However,
|
||||
| you can define a custom Eloquent model that suits your needs.
|
||||
| As an example one custom model has been added for you which
|
||||
| will allow you to create custom database castings.
|
||||
|
|
||||
*/
|
||||
|
||||
'parent' => Illuminate\Database\Eloquent\Model::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Traits
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sometimes you may want to append certain traits to all your models.
|
||||
| If that is what you need, you may list them bellow.
|
||||
| As an example we have a BitBooleans trait which will treat MySQL bit
|
||||
| data type as booleans. You might probably not need it, but it is
|
||||
| an example of how you can customize your models.
|
||||
|
|
||||
*/
|
||||
|
||||
'use' => [
|
||||
// Reliese\Database\Eloquent\BitBooleans::class,
|
||||
// Reliese\Database\Eloquent\BlamableBehavior::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you wish your models had appended the connection from which they
|
||||
| were generated, you should set this value to true and your
|
||||
| models will have the connection property filled.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Timestamps
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If your tables have CREATED_AT and UPDATED_AT timestamps you may
|
||||
| enable them and your models will fill their values as needed.
|
||||
| You can also specify which fields should be treated as timestamps
|
||||
| in case you don't follow the naming convention Eloquent uses.
|
||||
| If your table doesn't have these fields, timestamps will be
|
||||
| disabled for your model.
|
||||
|
|
||||
*/
|
||||
|
||||
'timestamps' => true,
|
||||
|
||||
// 'timestamps' => [
|
||||
// 'enabled' => true,
|
||||
// 'fields' => [
|
||||
// 'CREATED_AT' => 'created_at',
|
||||
// 'UPDATED_AT' => 'updated_at',
|
||||
// ]
|
||||
// ],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Soft Deletes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If your tables support soft deletes with a DELETED_AT attribute,
|
||||
| you can enable them here. You can also specify which field
|
||||
| should be treated as a soft delete attribute in case you
|
||||
| don't follow the naming convention Eloquent uses.
|
||||
| If your table doesn't have this field, soft deletes will be
|
||||
| disabled for your model.
|
||||
|
|
||||
*/
|
||||
|
||||
'soft_deletes' => true,
|
||||
|
||||
// 'soft_deletes' => [
|
||||
// 'enabled' => true,
|
||||
// 'field' => 'deleted_at',
|
||||
// ],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Date Format
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define your models' date format. The following format
|
||||
| is the default format Eloquent uses. You won't see it in your
|
||||
| models unless you change it to a more convenient value.
|
||||
|
|
||||
*/
|
||||
|
||||
'date_format' => 'Y-m-d H:i:s',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define how many models Eloquent should display when
|
||||
| paginating them. The default number is 15, so you might not
|
||||
| see this number in your models unless you change it.
|
||||
|
|
||||
*/
|
||||
|
||||
'per_page' => 15,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Base Files
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By default, your models will be generated in your models path, but
|
||||
| when you generate them again they will be replaced by new ones.
|
||||
| You may want to customize your models and, at the same time, be
|
||||
| able to generate them as your tables change. For that, you
|
||||
| can enable base files. These files will be replaced whenever
|
||||
| you generate them, but your customized files will not be touched.
|
||||
|
|
||||
*/
|
||||
|
||||
'base_files' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Snake Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Eloquent treats your model attributes as snake cased attributes, but
|
||||
| if you have camel-cased fields in your database you can disable
|
||||
| that behaviour and use camel case attributes in your models.
|
||||
|
|
||||
*/
|
||||
|
||||
'snake_attributes' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Indent options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| As default indention is done with tabs, but you can change it by setting
|
||||
| this to the amount of spaces you that you want to use for indentation.
|
||||
| Usually you will use 4 spaces instead of tabs.
|
||||
|
|
||||
*/
|
||||
|
||||
'indent_with_space' => 0,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Qualified Table Names
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If some of your tables have cross-database relationships (probably in
|
||||
| MySQL), you can make sure your models take into account their
|
||||
| respective database schema.
|
||||
|
|
||||
| Can Either be NULL, FALSE or TRUE
|
||||
| TRUE: Schema name will be prepended on the table
|
||||
| FALSE:Table name will be set without schema name.
|
||||
| NULL: Table name will follow laravel pattern,
|
||||
| i.e. if class name(plural) matches table name, then table name will not be added
|
||||
*/
|
||||
|
||||
'qualified_tables' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Hidden Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When casting your models into arrays or json, the need to hide some
|
||||
| attributes sometimes arise. If your tables have some fields you
|
||||
| want to hide, you can define them bellow.
|
||||
| Some fields were defined for you.
|
||||
|
|
||||
*/
|
||||
|
||||
'hidden' => [
|
||||
'*secret*', '*password', '*token',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mass Assignment Guarded Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may want to protect some fields from mass assignment. You can
|
||||
| define them bellow. Some fields were defined for you.
|
||||
| Your fillable attributes will be those which are not in the list
|
||||
| excluding your models' primary keys.
|
||||
|
|
||||
*/
|
||||
|
||||
'guarded' => [
|
||||
// 'created_by', 'updated_by'
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Casts
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may want to specify which of your table fields should be cast as
|
||||
| something other than a string. For instance, you may want a
|
||||
| text field be cast as an array or and object.
|
||||
|
|
||||
| You may define column patterns which will be cast using the value
|
||||
| assigned. We have defined some fields for you. Feel free to
|
||||
| modify them to fit your needs.
|
||||
|
|
||||
*/
|
||||
|
||||
'casts' => [
|
||||
'*_json' => 'json',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Excluded Tables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When performing the generation of models you may want to skip some of
|
||||
| them, because you don't want a model for them or any other reason.
|
||||
| You can define those tables bellow. The migrations table was
|
||||
| filled for you, since you may not want a model for it.
|
||||
|
|
||||
*/
|
||||
|
||||
'except' => [
|
||||
'migrations',
|
||||
'failed_jobs',
|
||||
'password_resets',
|
||||
'personal_access_tokens',
|
||||
'password_reset_tokens',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Specified Tables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You can specify specific tables. This will generate the models only
|
||||
| for selected tables, ignoring the rest.
|
||||
|
|
||||
*/
|
||||
|
||||
'only' => [
|
||||
// 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Table Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you have a prefix on your table names but don't want it in the model
|
||||
| and relation names, specify it here.
|
||||
|
|
||||
*/
|
||||
|
||||
'table_prefix' => '',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Lower table name before doing studly
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If tables names are capitalised using studly produces incorrect name
|
||||
| this can help fix it ie TABLE_NAME now becomes TableName
|
||||
|
|
||||
*/
|
||||
|
||||
'lower_table_name_first' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model Names
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By default the generator will create models with names that match your tables.
|
||||
| However, if you wish to manually override the naming, you can specify a mapping
|
||||
| here between table and model names.
|
||||
|
|
||||
| Example:
|
||||
| A table called 'billing_invoices' will generate a model called `BillingInvoice`,
|
||||
| but you'd prefer it to generate a model called 'Invoice'. Therefore, you'd add
|
||||
| the following array key and value:
|
||||
| 'billing_invoices' => 'Invoice',
|
||||
*/
|
||||
|
||||
'model_names' => [
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Relation Name Strategy
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| How the relations should be named in your models.
|
||||
|
|
||||
| 'related' Use the related table as the relation name.
|
||||
| (post.author --> user.id)
|
||||
generates Post::user() and User::posts()
|
||||
|
|
||||
| 'foreign_key' Use the foreign key as the relation name.
|
||||
| This can help to provide more meaningful relationship names, and avoids naming conflicts
|
||||
| if you have more than one relationship between two tables.
|
||||
| (post.author_id --> user.id)
|
||||
| generates Post::author() and User::posts_where_author()
|
||||
| (post.editor_id --> user.id)
|
||||
| generates Post::editor() and User::posts_where_editor()
|
||||
| ID suffixes can be omitted from foreign keys.
|
||||
| (post.author --> user.id)
|
||||
| (post.editor --> user.id)
|
||||
| generates the same as above.
|
||||
| Where the foreign key matches the related table name, it behaves as per the 'related' strategy.
|
||||
| (post.user_id --> user.id)
|
||||
| generates Post::user() and User::posts()
|
||||
*/
|
||||
|
||||
'relation_name_strategy' => 'related',
|
||||
// 'relation_name_strategy' => 'foreign_key',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Determines need or not to generate constants with properties names like
|
||||
|
|
||||
| ...
|
||||
| const AGE = 'age';
|
||||
| const USER_NAME = 'user_name';
|
||||
| ...
|
||||
|
|
||||
| that later can be used in QueryBuilder like
|
||||
|
|
||||
| ...
|
||||
| $builder->select([User::USER_NAME])->where(User::AGE, '<=', 18);
|
||||
| ...
|
||||
|
|
||||
| that helps to avoid typos in strings when typing field names and allows to use
|
||||
| code competition with available model's field names.
|
||||
*/
|
||||
'with_property_constants' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Optionally includes a full list of columns in the base generated models,
|
||||
| which can be used to avoid making calls like
|
||||
|
|
||||
| ...
|
||||
| \Illuminate\Support\Facades\Schema::getColumnListing
|
||||
| ...
|
||||
|
|
||||
| which can be slow, especially for large tables.
|
||||
*/
|
||||
'with_column_list' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Disable Pluralization Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You can disable pluralization tables and relations
|
||||
|
|
||||
*/
|
||||
'pluralize' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Disable Pluralization Except For Certain Tables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable pluralization for certain tables
|
||||
|
|
||||
*/
|
||||
'override_pluralize_for' => [
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Move $hidden property to base files
|
||||
|--------------------------------------------------------------------------
|
||||
| When base_files is true you can set hidden_in_base_files to true
|
||||
| if you want the $hidden to be generated in base files
|
||||
|
|
||||
*/
|
||||
'hidden_in_base_files' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Move $fillable property to base files
|
||||
|--------------------------------------------------------------------------
|
||||
| When base_files is true you can set fillable_in_base_files to true
|
||||
| if you want the $fillable to be generated in base files
|
||||
|
|
||||
*/
|
||||
'fillable_in_base_files' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Generate return types for relation methods.
|
||||
|--------------------------------------------------------------------------
|
||||
| When enable_return_types is set to true, return type declarations are added
|
||||
| to all generated relation methods for your models.
|
||||
|
|
||||
| NOTE: This requires PHP 7.0 or later.
|
||||
|
|
||||
*/
|
||||
'enable_return_types' => false,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Specifics
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| In this section you may define the default configuration for each model
|
||||
| that will be generated from a specific database. You can also nest
|
||||
| table specific configurations.
|
||||
| These values will override those defined in the section above.
|
||||
|
|
||||
*/
|
||||
|
||||
// 'shop' => [
|
||||
// 'path' => app_path(),
|
||||
// 'namespace' => 'App',
|
||||
// 'snake_attributes' => false,
|
||||
// 'qualified_tables' => true,
|
||||
// 'use' => [
|
||||
// Reliese\Database\Eloquent\BitBooleans::class,
|
||||
// ],
|
||||
// 'except' => ['migrations'],
|
||||
// 'only' => ['users'],
|
||||
// // Table Specifics Bellow:
|
||||
// 'user' => [
|
||||
// // Don't use any default trait
|
||||
// 'use' => [],
|
||||
// ]
|
||||
// ],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Connection Specifics
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| In this section you may define the default configuration for each model
|
||||
| that will be generated from a specific connection. You can also nest
|
||||
| database and table specific configurations.
|
||||
|
|
||||
| You may wish to use connection specific config for setting a parent
|
||||
| model with a read only setup, or enforcing a different set of rules
|
||||
| for a connection, e.g. using snake_case naming over CamelCase naming.
|
||||
|
|
||||
| This supports nesting with the following key configuration values, in
|
||||
| reverse precedence order (i.e. the last one found becomes the value).
|
||||
|
|
||||
| connections.{connection_name}.property
|
||||
| connections.{connection_name}.{database_name}.property
|
||||
| connections.{connection_name}.{table_name}.property
|
||||
| connections.{connection_name}.{database_name}.{table_name}.property
|
||||
|
|
||||
| These values will override those defined in the section above.
|
||||
|
|
||||
*/
|
||||
|
||||
// 'connections' => [
|
||||
// 'read_only_external' => [
|
||||
// 'parent' => \App\Models\ReadOnlyModel::class,
|
||||
// 'connection' => true,
|
||||
// 'users' => [
|
||||
// 'connection' => false,
|
||||
// ],
|
||||
// 'my_other_database' => [
|
||||
// 'password_resets' => [
|
||||
// 'connection' => false,
|
||||
// ]
|
||||
// ]
|
||||
// ],
|
||||
// ],
|
||||
];
|
||||
5
config/platform/general.php
Normal file
5
config/platform/general.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'enable_test_routes' => env('ENABLE_TEST_ROUTES', false),
|
||||
];
|
||||
52
config/platform/media.php
Normal file
52
config/platform/media.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'temps' => [
|
||||
'owner_type' => 'system',
|
||||
'location' => '/temps/',
|
||||
'prefix' => 'temps_',
|
||||
'postfix' => '',
|
||||
'extension' => '',
|
||||
'mime' => '',
|
||||
'media_type' => '*',
|
||||
'name' => 'Temp Files',
|
||||
'description' => 'Temp Files',
|
||||
'is_system' => true,
|
||||
],
|
||||
'user-i' => [
|
||||
'owner_type' => 'user',
|
||||
'location' => '/user-i/',
|
||||
'prefix' => 'ui_',
|
||||
'postfix' => '',
|
||||
'extension' => 'png',
|
||||
'mime' => 'image/png',
|
||||
'media_type' => 'image',
|
||||
'name' => 'User Images',
|
||||
'description' => 'Images uploaded by user.',
|
||||
'is_system' => false,
|
||||
],
|
||||
'user-v' => [
|
||||
'owner_type' => 'user',
|
||||
'location' => '/user-v/',
|
||||
'prefix' => 'uv_',
|
||||
'postfix' => '',
|
||||
'extension' => 'mp4',
|
||||
'mime' => 'video/mp4',
|
||||
'media_type' => 'video',
|
||||
'name' => 'User videos',
|
||||
'description' => 'Videos uploaded by user.',
|
||||
'is_system' => false,
|
||||
],
|
||||
'user-a' => [
|
||||
'owner_type' => 'user',
|
||||
'location' => '/user-a/',
|
||||
'prefix' => 'ua_',
|
||||
'postfix' => '',
|
||||
'extension' => 'mp3',
|
||||
'mime' => 'audio/mp3',
|
||||
'media_type' => 'audio',
|
||||
'name' => 'User audio',
|
||||
'description' => 'Audios uploaded by user.',
|
||||
'is_system' => false,
|
||||
],
|
||||
];
|
||||
112
config/queue.php
Normal file
112
config/queue.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue supports a variety of backends via a single, unified
|
||||
| API, giving you convenient access to each backend using identical
|
||||
| syntax for each. The default queue connection is defined below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection options for every queue backend
|
||||
| used by your application. An example configuration is provided for
|
||||
| each backend supported by Laravel. You're also free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_QUEUE_CONNECTION'),
|
||||
'table' => env('DB_QUEUE_TABLE', 'jobs'),
|
||||
'queue' => env('DB_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Batching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following options configure the database and table that store job
|
||||
| batching information. These options can be updated to any database
|
||||
| connection and table which has been defined by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'batching' => [
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'job_batches',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control how and where failed jobs are stored. Laravel ships with
|
||||
| support for storing failed jobs in a simple file or in a database.
|
||||
|
|
||||
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||
83
config/sanctum.php
Normal file
83
config/sanctum.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort()
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. This will override any values set in the token's
|
||||
| "expires_at" attribute, but first-party sessions are not affected.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Token Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||
| security scanning initiatives maintained by open source platforms
|
||||
| that notify developers if they commit tokens into repositories.
|
||||
|
|
||||
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||
|
|
||||
*/
|
||||
|
||||
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
||||
70
config/seotools.php
Normal file
70
config/seotools.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @see https://github.com/artesaos/seotools
|
||||
*/
|
||||
|
||||
return [
|
||||
'inertia' => env('SEO_TOOLS_INERTIA', true),
|
||||
'meta' => [
|
||||
/*
|
||||
* The default configurations to be used by the meta generator.
|
||||
*/
|
||||
'defaults' => [
|
||||
'title' => 'Video² AI',
|
||||
'titleBefore' => false, // Put defaults.title before page title, like 'It's Over 9000! - Dashboard'
|
||||
'description' => 'A new era of AI-powered video creation',
|
||||
'separator' => ' - ',
|
||||
'keywords' => [],
|
||||
'canonical' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
|
||||
'robots' => false, // Set to 'all', 'none' or any combination of index/noindex and follow/nofollow
|
||||
],
|
||||
/*
|
||||
* Webmaster tags are always added.
|
||||
*/
|
||||
'webmaster_tags' => [
|
||||
'google' => null,
|
||||
'bing' => null,
|
||||
'alexa' => null,
|
||||
'pinterest' => null,
|
||||
'yandex' => null,
|
||||
'norton' => null,
|
||||
],
|
||||
|
||||
'add_notranslate_class' => false,
|
||||
],
|
||||
'opengraph' => [
|
||||
/*
|
||||
* The default configurations to be used by the opengraph generator.
|
||||
*/
|
||||
'defaults' => [
|
||||
'title' => 'Video² AI',
|
||||
'description' => 'A new era of AI-powered video creation',
|
||||
'url' => false, // Set null for using Url::current(), set false to total remove
|
||||
'type' => false,
|
||||
'site_name' => false,
|
||||
'images' => [],
|
||||
],
|
||||
],
|
||||
'twitter' => [
|
||||
/*
|
||||
* The default values to be used by the twitter cards generator.
|
||||
*/
|
||||
'defaults' => [
|
||||
// 'card' => 'summary',
|
||||
// 'site' => '@LuizVinicius73',
|
||||
],
|
||||
],
|
||||
'json-ld' => [
|
||||
/*
|
||||
* The default configurations to be used by the json-ld generator.
|
||||
*/
|
||||
'defaults' => [
|
||||
'title' => 'Video² AI',
|
||||
'description' => 'A new era of AI-powered video creation',
|
||||
'url' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
|
||||
'type' => 'WebPage',
|
||||
'images' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
38
config/services.php
Normal file
38
config/services.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'key' => env('RESEND_KEY'),
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'notifications' => [
|
||||
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
|
||||
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
217
config/session.php
Normal file
217
config/session.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines the default session driver that is utilized for
|
||||
| incoming requests. Laravel supports a variety of storage options to
|
||||
| persist session data. Database storage is a great default choice.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "apc",
|
||||
| "memcached", "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to expire immediately when the browser is closed then you may
|
||||
| indicate that via the expire_on_close configuration option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => (int) env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it's stored. All encryption is performed
|
||||
| automatically by Laravel and you may use the session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the "file" session driver, the session files are placed
|
||||
| on disk. The default storage location is defined here; however, you
|
||||
| are free to provide another location where they should be stored.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table to
|
||||
| be used to store sessions. Of course, a sensible default is defined
|
||||
| for you; however, you're welcome to change this to another table.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => env('SESSION_TABLE', 'sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using one of the framework's cache driven session backends, you may
|
||||
| define the cache store which should be used to store the session data
|
||||
| between requests. This must match one of your defined cache stores.
|
||||
|
|
||||
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the session cookie that is created by
|
||||
| the framework. Typically, you should not need to change this value
|
||||
| since doing so does not grant a meaningful security improvement.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application, but you're free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('SESSION_PATH', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the domain and subdomains the session cookie is
|
||||
| available to. By default, the cookie will be available to the root
|
||||
| domain and all subdomains. Typically, this shouldn't be changed.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. It's unlikely you should disable this option.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" to permit secure cross-site requests.
|
||||
|
|
||||
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => env('SESSION_SAME_SITE', 'lax'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Partitioned Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will tie the cookie to the top-level site for
|
||||
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||
|
|
||||
*/
|
||||
|
||||
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
|
||||
|
||||
];
|
||||
1
database/.gitignore
vendored
Normal file
1
database/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sqlite*
|
||||
44
database/factories/UserFactory.php
Normal file
44
database/factories/UserFactory.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
50
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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('users', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
$table->id();
|
||||
$table->uuid('uuid');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
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('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');
|
||||
}
|
||||
};
|
||||
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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('media_collections', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('key');
|
||||
$table->string('name');
|
||||
$table->text('description');
|
||||
$table->boolean('is_system');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique('key');
|
||||
$table->index('is_system');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('media_collections');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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('medias', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->softDeletes();
|
||||
$table->uuid()->nullable()->unique();
|
||||
$table->foreignId('media_collection_id');
|
||||
$table->foreignId('user_id')->nullable();
|
||||
$table->enum('media_type', ['video', 'image', 'audio', 'any']);
|
||||
$table->enum('media_source', ['ai_generated', 'user_uploaded', 'system_uploaded', 'user_rendered', 'system_rendered']);
|
||||
$table->string('name')->nullable();
|
||||
$table->string('media_provider');
|
||||
$table->string('mime_type');
|
||||
$table->string('file_name');
|
||||
$table->string('file_path');
|
||||
$table->string('disk');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('media_collection_id')->references('id')->on('media_collections');
|
||||
$table->index('media_source');
|
||||
$table->index('media_type');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('medias');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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('videos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->softDeletes();
|
||||
$table->uuid('uuid');
|
||||
$table->foreignId('user_id')->nullable();
|
||||
$table->string('external_id')->nullable();
|
||||
|
||||
$table->string('content_type')->default('blank')->index();
|
||||
$table->integer('width');
|
||||
$table->integer('height');
|
||||
$table->enum('aspect_ratio', ['16:9', '9:16', '1:1', '4:3', '3:4'])->default('9:16');
|
||||
$table->json('payload');
|
||||
$table->json('render_settings');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('videos');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use App\Helpers\FirstParty\Render\RenderConstants;
|
||||
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('video_renders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->uuid('uuid');
|
||||
$table->string('external_id')->nullable();
|
||||
$table->foreignId('video_id')->nullable();
|
||||
$table->foreignId('user_id')->nullable();
|
||||
$table->json('payload');
|
||||
$table->enum('status', RenderConstants::STATUSES)->default(RenderConstants::STATUS_PLANNED);
|
||||
|
||||
$table->datetime('processing_started_at')->nullable();
|
||||
$table->datetime('processing_finished_at')->nullable();
|
||||
|
||||
$table->uuid('completed_video_uuid')->nullable();
|
||||
$table->string('completed_video_full_url')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_id')->references('id')->on('videos');
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('video_renders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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('video_captions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->foreignId('video_id');
|
||||
$table->double('time');
|
||||
$table->double('duration');
|
||||
$table->text('text');
|
||||
$table->json('parameters');
|
||||
$table->json('words');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_id')->references('id')->on('videos');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('video_captions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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('video_elements', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->foreignId('video_id');
|
||||
|
||||
$table->string('external_reference')->nullable();
|
||||
|
||||
$table->string('asset_hash', 64);
|
||||
|
||||
$table->string('original_asset_url')->nullable();
|
||||
$table->uuid('asset_uuid')->nullable();
|
||||
$table->string('asset_url')->nullable();
|
||||
|
||||
$table->enum('type', ['video', 'image', 'audio']);
|
||||
$table->double('time');
|
||||
$table->integer('track');
|
||||
$table->double('duration');
|
||||
$table->json('parameters');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_id')->references('id')->on('videos');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('video_elements');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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::table('video_elements', function (Blueprint $table) {
|
||||
$table->foreignId('parent_element_id')->nullable();
|
||||
|
||||
$table->foreign('parent_element_id')->references('id')->on('video_elements')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('video_elements', function (Blueprint $table) {
|
||||
$table->dropForeign('video_elements_parent_element_id_foreign');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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
|
||||
{
|
||||
// Video Template: Reusable videos that can be duplicated and edited to create new videos
|
||||
Schema::create('video_templates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable(); // who owns it
|
||||
$table->boolean('is_public')->default(false); // can be viewed by anyone?
|
||||
$table->string('name', 255); // name of template
|
||||
$table->json('parameters'); // template configurations
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('video_templates');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use App\Helpers\FirstParty\Render\RenderConstants;
|
||||
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
|
||||
{
|
||||
// Video: A single video that can be edited and saved
|
||||
Schema::create('videos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid'); // uuid
|
||||
$table->foreignId('video_template_id')->nullable(); // is a template?
|
||||
$table->foreignId('user_id')->nullable(); // who owns it
|
||||
$table->string('title', 255); // title of video
|
||||
$table->string('type', 50); // moving_images, single_video, split_video, split_video_background_image
|
||||
$table->json('parameters'); // parameters for video (width, height, aspect ratio, frame rate)
|
||||
$table->enum('status', [RenderConstants::STATUS_COMPLETE, RenderConstants::STATUS_PROCESSING, RenderConstants::STATUS_ERROR]); // status of video
|
||||
$table->integer('processing_percentage')->default(0); // progress of processing
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_template_id')->references('id')->on('video_templates')->onDelete('set null');
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('videos');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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
|
||||
{
|
||||
// Video Data: some video assets have additional data associated with them
|
||||
Schema::create('video_datas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('video_id'); // video this data belongs to
|
||||
$table->enum('media_type', ['text', 'image', 'video', 'audio', 'video_subtitle', 'text_overlay']); // what kind of data is this?
|
||||
$table->enum('source', ['generated', 'uploaded', 'other']); // where did this data come from?
|
||||
$table->string('source_name')->nullable(); // name of source
|
||||
$table->string('key', 255); // identifier for data
|
||||
$table->string('value')->nullable(); // if data is a id or value or some form of identifier
|
||||
$table->jsonb('payload')->nullable(); // if data contains additional information like api response, etc.
|
||||
$table->string('url')->nullable(); // if data contains a url
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_id')->references('id')->on('videos')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('video_datas');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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
|
||||
{
|
||||
// Video Asset: medias that may or maynot have video_data associated
|
||||
Schema::create('video_assets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid'); // UUID that can be publicly shared
|
||||
$table->foreignId('video_id'); // video this asset belongs to
|
||||
$table->foreignId('video_data_id')->nullable(); // video data this asset is associated with
|
||||
$table->enum('media_type', ['image', 'video', 'audio']); // what kind of data is this?
|
||||
$table->string('mime_type'); // mime type of asset
|
||||
$table->string('name')->nullable(); // name of asset that can be pubicly shared
|
||||
$table->string('cloud_filename')->nullable(); // filename on cloud storage
|
||||
$table->integer('width')->nullable(); // for images and video: width of image
|
||||
$table->integer('height')->nullable(); // for images and video: height of image
|
||||
$table->float('aspect_ratio')->nullable(); // for images and video: aspect ratio of image
|
||||
$table->double('duration')->nullable(); // for audio and video assets: duration of asset
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_id')->references('id')->on('videos')->onDelete('cascade');
|
||||
$table->foreign('video_data_id')->references('id')->on('video_datas')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('video_assets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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
|
||||
{
|
||||
// Text Overlay: text that can be overlaid on a video
|
||||
Schema::create('text_overlays', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('video_id'); // video this text overlay belongs to
|
||||
$table->text('text_content'); // text content
|
||||
$table->jsonb('style_payload')->nullable(); // font family, font size, font color, font weight, text align
|
||||
$table->string('cloud_filename')->nullable(); // filename on cloud storage
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('video_id')->references('id')->on('videos')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('text_overlays');
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user