diff --git a/README.md b/README.md index 5b2d40f..92347d8 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@

Site - Telegram - License + WhatsApp + License Laravel Livewire + Filament Tailwind

@@ -23,7 +24,7 @@ A **PHP-PB** é uma comunidade de desenvolvedores PHP do estado da Paraíba, Bra ### O que fazemos -- **Grupo no Telegram** - Discussões diárias sobre PHP, Laravel, dúvidas técnicas +- **Grupo no WhatsApp** - Discussões diárias sobre PHP, Laravel, dúvidas técnicas - **Meetups & Hangouts** - Encontros presenciais e online com palestras - **Blog** - Artigos técnicos escritos pela comunidade - **Vagas** - Oportunidades de emprego compartilhadas @@ -38,7 +39,9 @@ A **PHP-PB** é uma comunidade de desenvolvedores PHP do estado da Paraíba, Bra | **Tailwind CSS** | 4 | Framework CSS utility-first | | **Alpine.js** | 3 | Interatividade (incluso no Livewire) | | **MySQL** | 8 | Banco de dados | -| **FilamentPHP** | 5 | Painel administrativo | +| **FilamentPHP** | 5 | Painel administrativo e área de membro | +| **Phosphor Icons** | - | Ícones do design system | +| **Pest** | - | Testes automatizados | ## Estrutura do Projeto @@ -151,23 +154,36 @@ npm run build npm run dev ``` +## CI/CD + +- **CI** (`.github/workflows/ci.yml`): roda em todo push/PR na `master` - Pint (formatação), Larastan (análise estática) e a suíte de testes Pest. +- **Deploy** (`.github/workflows/deploy.yml`): só dispara ao criar uma tag no formato `v*.*.*`. Builda os assets, empacota a aplicação e publica via SSH/SCP na Hostinger. + +```bash +# Disparar um deploy em produção +git tag v1.0.0 +git push origin v1.0.0 +``` + ## Comunidades Parceiras Fazemos parte de uma rede de comunidades PHP do Nordeste: -| Comunidade | Estado | Link | -|------------|--------|------| -| **PHP-PB** | Paraíba | [php-pb.net](https://php-pb.net) | -| **PHP com Rapadura** | Ceará | [phpcomrapadura.org](https://phpcomrapadura.org) | -| **PHP-PE** | Pernambuco | - | -| **PHP-RN** | Rio Grande do Norte | - | +| Comunidade | Estado | +|------------|--------| +| **PHP-PB** | Paraíba | +| **PHP-PI** | Piauí | +| **PHP-MA** | Maranhão | +| **PHP com Rapadura** | Ceará | +| **PHP-PE** | Pernambuco | +| **PHP-RN** | Rio Grande do Norte | Juntos organizamos o **[PHPeste](https://phpeste.dev)** - a maior conferência PHP do Nordeste brasileiro! ## Links - [Site Oficial](https://php-pb.net) -- [Telegram](https://t.me/phppb) +- [WhatsApp](https://chat.whatsapp.com/JaWCta8t2DF9Af0zgIb8LB?mode=gi_t) - [GitHub](https://github.com/php-pb) - [Twitter/X](https://twitter.com/phppb) - [Facebook](https://www.facebook.com/groups/php.pb) diff --git a/site_novo/app/Http/Controllers/SitemapController.php b/site_novo/app/Http/Controllers/SitemapController.php new file mode 100644 index 0000000..acc1fe7 --- /dev/null +++ b/site_novo/app/Http/Controllers/SitemapController.php @@ -0,0 +1,29 @@ +orderBy('published_at', 'desc')->get(['slug', 'updated_at']); + + $staticRoutes = [ + ['route' => route('home'), 'priority' => '1.0', 'changefreq' => 'weekly'], + ['route' => route('blog.index'), 'priority' => '0.8', 'changefreq' => 'daily'], + ['route' => route('events.index'), 'priority' => '0.8', 'changefreq' => 'daily'], + ['route' => route('jobs.index'), 'priority' => '0.6', 'changefreq' => 'weekly'], + ['route' => route('speakers.index'), 'priority' => '0.5', 'changefreq' => 'monthly'], + ]; + + $xml = view('sitemap', [ + 'staticRoutes' => $staticRoutes, + 'posts' => $posts, + ])->render(); + + return response($xml, 200)->header('Content-Type', 'application/xml'); + } +} diff --git a/site_novo/app/Models/Post.php b/site_novo/app/Models/Post.php index 8f4f5a6..d21f133 100644 --- a/site_novo/app/Models/Post.php +++ b/site_novo/app/Models/Post.php @@ -81,4 +81,14 @@ public function getMainImage(): ?string return $this->main_image_url; } + + public function getExcerpt(int $length = 160): string + { + $text = collect($this->content_blocks ?? []) + ->first(fn (array $block) => ($block['type'] ?? null) === 'paragraph' && filled($block['data']['text'] ?? null)); + + $plainText = trim(html_entity_decode(strip_tags($text['data']['text'] ?? ''), ENT_QUOTES)); + + return str($plainText)->limit($length)->toString(); + } } diff --git a/site_novo/public/robots.txt b/site_novo/public/robots.txt index eb05362..82953e3 100644 --- a/site_novo/public/robots.txt +++ b/site_novo/public/robots.txt @@ -1,2 +1,5 @@ User-agent: * -Disallow: +Disallow: /admin +Disallow: /membro + +Sitemap: https://php-pb.net/sitemap.xml diff --git a/site_novo/resources/views/blog/index.blade.php b/site_novo/resources/views/blog/index.blade.php index 8880a50..778ff9d 100644 --- a/site_novo/resources/views/blog/index.blade.php +++ b/site_novo/resources/views/blog/index.blade.php @@ -1,4 +1,7 @@ - +
diff --git a/site_novo/resources/views/blog/show.blade.php b/site_novo/resources/views/blog/show.blade.php index 442d90f..40e6edf 100644 --- a/site_novo/resources/views/blog/show.blade.php +++ b/site_novo/resources/views/blog/show.blade.php @@ -1,4 +1,39 @@ - + + + + +
diff --git "a/site_novo/resources/views/components/landing/\342\232\241footer.blade.php" "b/site_novo/resources/views/components/landing/\342\232\241footer.blade.php" index 0761495..5c55995 100644 --- "a/site_novo/resources/views/components/landing/\342\232\241footer.blade.php" +++ "b/site_novo/resources/views/components/landing/\342\232\241footer.blade.php" @@ -46,9 +46,9 @@ class="text-azul-marinho hover:text-azul-principal transition-colors"> - - + diff --git a/site_novo/resources/views/components/layouts/app.blade.php b/site_novo/resources/views/components/layouts/app.blade.php index 8df4b88..beaa853 100644 --- a/site_novo/resources/views/components/layouts/app.blade.php +++ b/site_novo/resources/views/components/layouts/app.blade.php @@ -1,11 +1,44 @@ +@props([ + 'title' => null, + 'description' => 'A PHP PB é a comunidade de desenvolvedores PHP da Paraíba. Conectamos pessoas, compartilhamos conhecimento e fortalecemos o ecossistema PHP no nosso estado.', + 'image' => null, + 'type' => 'website', + 'noindex' => false, +]) + +@php + $pageTitle = $title ?? config('app.name'); + $ogImage = $image ?? asset('images/hero/farol-cena.png'); + $canonical = rtrim(config('app.url'), '/').request()->getRequestUri(); +@endphp + - + + + @if ($noindex) + + @endif + + {{ $pageTitle }} - {{ $title ?? config('app.name') }} + + + + + + + + + + + + + + @@ -18,6 +51,8 @@ @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles + + {{ $head ?? '' }} {{ $slot }} diff --git a/site_novo/resources/views/errors/403.blade.php b/site_novo/resources/views/errors/403.blade.php index 6c7a681..ba3d4cc 100644 --- a/site_novo/resources/views/errors/403.blade.php +++ b/site_novo/resources/views/errors/403.blade.php @@ -1,4 +1,4 @@ - +
diff --git a/site_novo/resources/views/errors/404.blade.php b/site_novo/resources/views/errors/404.blade.php index cb5bac5..308dc77 100644 --- a/site_novo/resources/views/errors/404.blade.php +++ b/site_novo/resources/views/errors/404.blade.php @@ -1,4 +1,4 @@ - +
diff --git a/site_novo/resources/views/events/index.blade.php b/site_novo/resources/views/events/index.blade.php index 6659836..9861aeb 100644 --- a/site_novo/resources/views/events/index.blade.php +++ b/site_novo/resources/views/events/index.blade.php @@ -1,4 +1,43 @@ - + + @if ($events->count() > 0) + + + + @endif +
diff --git a/site_novo/resources/views/jobs/index.blade.php b/site_novo/resources/views/jobs/index.blade.php index 9401038..124595f 100644 --- a/site_novo/resources/views/jobs/index.blade.php +++ b/site_novo/resources/views/jobs/index.blade.php @@ -1,4 +1,7 @@ - +
diff --git a/site_novo/resources/views/pages/home.blade.php b/site_novo/resources/views/pages/home.blade.php index 96128b9..b82950b 100644 --- a/site_novo/resources/views/pages/home.blade.php +++ b/site_novo/resources/views/pages/home.blade.php @@ -1,4 +1,27 @@ - + + + + +
diff --git a/site_novo/resources/views/sitemap.blade.php b/site_novo/resources/views/sitemap.blade.php new file mode 100644 index 0000000..97b44c2 --- /dev/null +++ b/site_novo/resources/views/sitemap.blade.php @@ -0,0 +1,18 @@ +'; ?> + +@foreach ($staticRoutes as $item) + + {{ $item['route'] }} + {{ $item['changefreq'] }} + {{ $item['priority'] }} + +@endforeach +@foreach ($posts as $post) + + {{ route('blog.show', $post->slug) }} + {{ $post->updated_at->toAtomString() }} + monthly + 0.7 + +@endforeach + diff --git a/site_novo/resources/views/speakers/index.blade.php b/site_novo/resources/views/speakers/index.blade.php index c997dc3..23c72ce 100644 --- a/site_novo/resources/views/speakers/index.blade.php +++ b/site_novo/resources/views/speakers/index.blade.php @@ -1,4 +1,7 @@ - +
diff --git a/site_novo/routes/web.php b/site_novo/routes/web.php index 60047b4..ce95c6f 100644 --- a/site_novo/routes/web.php +++ b/site_novo/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\EventController; use App\Http\Controllers\JobListingController; use App\Http\Controllers\PostController; +use App\Http\Controllers\SitemapController; use App\Http\Controllers\SpeakerController; use Illuminate\Support\Facades\Route; @@ -10,6 +11,8 @@ return view('pages.home'); })->name('home'); +Route::get('/sitemap.xml', [SitemapController::class, 'index'])->name('sitemap'); + Route::get('/blog', [PostController::class, 'index'])->name('blog.index'); Route::get('/blog/{slug}', [PostController::class, 'show'])->name('blog.show'); diff --git a/site_novo/tests/Feature/SeoTest.php b/site_novo/tests/Feature/SeoTest.php new file mode 100644 index 0000000..b028cdc --- /dev/null +++ b/site_novo/tests/Feature/SeoTest.php @@ -0,0 +1,57 @@ +get(route('home'))->assertOk(); + + $response->assertSee('name="description" content="A PHP PB é a comunidade', false); + $response->assertSee('assertSee('property="og:title"', false); + $response->assertSee('name="twitter:card" content="summary_large_image"', false); + $response->assertSee('"@type":"Organization"', false); +}); + +test('a blog post has its own meta description, og:image and blogposting jsonld', function () { + $post = Post::factory()->create([ + 'title' => 'Como testar SEO no Laravel', + ]); + + $response = $this->get(route('blog.show', $post->slug))->assertOk(); + + $response->assertSee('Como testar SEO no Laravel - PHP-PB', false); + $response->assertSee('property="og:type" content="article"', false); + $response->assertSee('"@type":"BlogPosting"', false); + $response->assertSee('"headline":"Como testar SEO no Laravel"', false); +}); + +test('the events page includes event jsonld only when there are upcoming events', function () { + $this->get(route('events.index')) + ->assertOk() + ->assertDontSee('application/ld+json', false); + + Event::factory()->create(['title' => 'Meetup PHP PB de Teste']); + + $response = $this->get(route('events.index'))->assertOk(); + + $response->assertSee('"@type":"Event"', false); + $response->assertSee('"name":"Meetup PHP PB de Teste"', false); +}); + +test('error pages are marked as noindex', function () { + $this->get('/rota-que-nao-existe') + ->assertNotFound() + ->assertSee('name="robots" content="noindex, nofollow"', false); +}); + +test('the sitemap lists static pages and published posts', function () { + $post = Post::factory()->create(); + + $response = $this->get('/sitemap.xml')->assertOk(); + + $response->assertHeader('Content-Type', 'application/xml'); + $response->assertSee('', false); + $response->assertSee(''.route('home').'', false); + $response->assertSee(''.route('blog.show', $post->slug).'', false); +});