feat(cms): implement complete SysSiteCMS content management system

Initial implementation of the HomeSysPanel CMS built with Laravel 13.0.
Includes complete admin panel, content management, user authentication,
modular architecture for products/services/chatbot, and marketing tools.

Key features:
- User authentication with email verification and password reset
- Role-based access control (Admin/User with granular permissions)
- Content management (pages, news, FAQ, downloads, galleries, banners)
- Contact management system with subjects and responses
- Marketing tools (email campaigns, visitor analytics, social sharing)
- Modular architecture (Products, Services, Chatbot modules)
- AI Chatbot integration with OpenRouter.ai and LlamaCpp support
- Visitor tracking with geolocation
- SEO optimization with meta tags and Open Graph
- Responsive design with Bootstrap 5
- Dark mode support
- Maintenance mode
- Feature request system
This commit is contained in:
marco
2026-09-07 19:06:53 -03:00
commit 38295e7e88
279 changed files with 33691 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Banner extends Model
{
protected $fillable = ['title', 'image', 'link', 'button_text', 'order', 'active'];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ContactMessage extends Model
{
protected $fillable = [
'name',
'email',
'company',
'website',
'whatsapp',
'subject_id',
'message',
'read'
];
public function subject()
{
return $this->belongsTo(ContactSubject::class, 'subject_id');
}
public function responses()
{
return $this->hasMany(ContactResponse::class, 'contact_message_id');
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ContactResponse extends Model
{
protected $fillable = ['contact_message_id', 'user_id', 'message'];
public function message()
{
return $this->belongsTo(ContactMessage::class, 'contact_message_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ContactSubject extends Model
{
protected $fillable = ['name', 'active'];
public function messages()
{
return $this->hasMany(ContactMessage::class, 'subject_id');
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Download extends Model
{
protected $fillable = ['title', 'description', 'file_path', 'file_type', 'download_count', 'active'];
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Faq extends Model
{
protected $fillable = ['question', 'answer', 'order', 'active'];
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Gallery extends Model
{
protected $fillable = ['title', 'slug', 'description', 'active', 'category_id'];
public function images()
{
return $this->hasMany(GalleryImage::class)->orderBy('order');
}
public function category()
{
return $this->belongsTo(GalleryCategory::class);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GalleryCategory extends Model
{
protected $fillable = ['name', 'slug', 'description', 'active'];
public function galleries()
{
return $this->hasMany(Gallery::class, 'category_id')->orderBy('created_at', 'desc');
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GalleryImage extends Model
{
protected $fillable = ['gallery_id', 'file_path', 'description', 'order'];
public function gallery()
{
return $this->belongsTo(Gallery::class);
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class News extends Model
{
protected $fillable = [
'title',
'slug',
'summary',
'content',
'featured_image',
'author',
'active',
'published_at',
'user_id',
'seo_title',
'seo_description',
'seo_keywords',
];
protected $casts = [
'active' => 'boolean',
'published_at' => 'datetime',
];
/**
* Get the images for the news article.
*/
public function images(): HasMany
{
return $this->hasMany(NewsImage::class)->orderBy('order');
}
/**
* Get the user who created the news article.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Check if the news article is published.
*/
public function isPublished(): bool
{
return $this->active && ($this->published_at === null || $this->published_at->isPast());
}
/**
* Scope to get only published news.
*/
public function scopePublished($query)
{
return $query->where('active', true)
->where(function ($q) {
$q->whereNull('published_at')
->orWhere('published_at', '<=', now());
});
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class NewsImage extends Model
{
protected $fillable = [
'news_id',
'file_path',
'description',
'order',
];
/**
* Get the news article this image belongs to.
*/
public function news(): BelongsTo
{
return $this->belongsTo(News::class);
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
protected $fillable = [
'slug',
'title',
'content',
'meta_description',
'meta_keywords',
'og_title',
'og_description',
'og_image',
'canonical_url',
'active'
];
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $fillable = ['key', 'value', 'group'];
public static function get($key, $default = null)
{
$setting = self::where('key', $key)->first();
return ($setting && $setting->value !== '' && $setting->value !== null) ? $setting->value : $default;
}
public static function set($key, $value, $group = 'general')
{
return self::updateOrCreate(['key' => $key], ['value' => $value, 'group' => $group]);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'email', 'avatar', 'password', 'user_type', 'company', 'website', 'whatsapp', 'preferences', 'permissions'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable implements MustVerifyEmail
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'preferences' => 'array',
'permissions' => 'array',
];
}
/**
* Check if the user is an Admin.
*/
public function isAdmin(): bool
{
$adminType = \App\Models\UserType::where('is_admin', true)->pluck('name')->toArray();
return in_array($this->user_type, $adminType);
}
/**
* Check if the user is a regular User.
*/
public function isUser(): bool
{
return !$this->isAdmin();
}
/**
* Check if the user has a specific permission.
* Admin types always have all permissions.
*/
public function hasPermission(string $permission): bool
{
if ($this->isAdmin()) {
return true;
}
return in_array($permission, $this->permissions ?? []);
}
/**
* Get all available permissions in the system.
*
* @return array<string, string>
*/
public static function getAvailablePermissions(): array
{
return [
'contacts' => 'Gerenciar Contatos',
'subjects' => 'Gerenciar Assuntos',
'services' => 'Gerenciar Serviços',
'faqs' => 'Gerenciar FAQ',
'downloads' => 'Gerenciar Downloads',
'products' => 'Gerenciar Produtos',
'galleries' => 'Gerenciar Galerias',
'news' => 'Gerenciar Notícias',
'pages' => 'Gerenciar Páginas',
'marketing' => 'Marketing',
'chatbot' => 'Gerenciar Chatbot',
'settings' => 'Dados da Empresa',
];
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UserType extends Model
{
protected $fillable = ['name', 'description', 'is_admin', 'is_protected'];
protected function casts(): array
{
return [
'is_admin' => 'boolean',
'is_protected' => 'boolean',
];
}
/**
* Get the users with this type.
*/
public function users()
{
return User::where('user_type', $this->name);
}
/**
* Get all user types as a key-value array for select fields.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getAllForSelect()
{
return self::orderBy('name')->get();
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class VisitorLog extends Model
{
protected $fillable = [
'ip_address',
'country',
'city',
'region',
'session_id',
'user_agent',
'page_url'
];
}