Skip to content

Commit

Permalink
implemented blog
Browse files Browse the repository at this point in the history
  • Loading branch information
fprochazka authored and dg committed Apr 20, 2024
1 parent 092db9d commit 6f79b01
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 42 deletions.
15 changes: 14 additions & 1 deletion app/UI/@layout.latte
Expand Up @@ -4,14 +4,27 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">

<title>{ifset title}{include title|stripHtml} | {/ifset}Nette Web</title>
<title>{ifset title}{include title|stripHtml} | {/ifset}Nette Framework Micro-blog</title>

<link rel="stylesheet" href="{$basePath}/css/style.css">
</head>

<body>
<ul class="navig">
<li><a n:href="Home:">Home</a></li>
{if $user->isloggedIn()}
<li><a n:href="Sign:out">Sign out</a></li>
{else}
<li><a n:href="Sign:in">Sign in</a></li>
{/if}
</ul>

<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">{$flash->message}</div>

{include content}

<p class="footer">This is a <a href="http://doc.nette.org/quickstart">Nette Framework Quick Start</a>.</p>

{block scripts}
<script src="https://unpkg.com/nette-forms@3/src/assets/netteForms.js"></script>
{/block}
Expand Down
78 changes: 78 additions & 0 deletions app/UI/Edit/EditPresenter.php
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace App\UI\Edit;

use Nette;
use Nette\Application\UI\Form;


final class EditPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}


public function startup(): void
{
parent::startup();

if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}


public function renderEdit(int $postId): void
{
$post = $this->database
->table('posts')
->get($postId);

if (!$post) {
$this->error('Post not found');
}

$this->getComponent('postForm')
->setDefaults($post->toArray());
}


protected function createComponentPostForm(): Form
{
$form = new Form;
$form->addText('title', 'Title:')
->setRequired();
$form->addTextArea('content', 'Content:')
->setRequired();

$form->addSubmit('send', 'Save and publish');
$form->onSuccess[] = $this->postFormSucceeded(...);

return $form;
}


private function postFormSucceeded(array $data): void
{
$postId = $this->getParameter('postId');

if ($postId) {
$post = $this->database
->table('posts')
->get($postId);
$post->update($data);

} else {
$post = $this->database
->table('posts')
->insert($data);
}

$this->flashMessage('Post was published', 'success');
$this->redirect('Post:show', $post->id);
}
}
4 changes: 4 additions & 0 deletions app/UI/Edit/create.latte
@@ -0,0 +1,4 @@
{block content}
<h1>New post</h1>

{control postForm}
4 changes: 4 additions & 0 deletions app/UI/Edit/edit.latte
@@ -0,0 +1,4 @@
{block content}
<h1>Edit post</h1>

{control postForm}
13 changes: 13 additions & 0 deletions app/UI/Home/HomePresenter.php
Expand Up @@ -9,4 +9,17 @@

final class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}


public function renderDefault(): void
{
$this->template->posts = $this->database
->table('posts')
->order('created_at DESC')
->limit(5);
}
}
41 changes: 7 additions & 34 deletions app/UI/Home/default.latte
@@ -1,38 +1,11 @@
{* This is the welcome page, you can delete it *}

{block content}
<div id="banner">
<h1 n:block=title>Congratulations!</h1>
</div>

<div id="content">
<h2>You have successfully created your <a href="https://nette.org">Nette</a> Web project.</h2>

<p><img src="data:image/png;base64,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" alt="">
If you are exploring Nette for the first time, you should read the
<a href="https://doc.nette.org/quickstart">Quick Start</a>, <a href="https://doc.nette.org">documentation</a>,
<a href="https://blog.nette.org">blog</a> and <a href="https://forum.nette.org">forum</a>.</p>

<h2>We hope you enjoy Nette!</h2>
</div>

<style>
html { font: normal 18px/1.3 Georgia, "New York CE", utopia, serif; color: #666; -webkit-text-stroke: 1px rgba(0,0,0,0); overflow-y: scroll; }
body { background: #3484d2; color: #333; margin: 2em auto; padding: 0 .5em; max-width: 600px; min-width: 320px; }
a { color: #006aeb; padding: 3px 1px; }
a:hover, a:active, a:focus { background-color: #006aeb; text-decoration: none; color: white; }
#banner { border-radius: 12px 12px 0 0; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAB5CAMAAADPursXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGBQTFRFD1CRDkqFDTlmDkF1D06NDT1tDTNZDk2KEFWaDTZgDkiCDTtpDT5wDkZ/DTBVEFacEFOWD1KUDTRcDTFWDkV9DkR7DkN4DkByDTVeDC9TDThjDTxrDkeADkuIDTRbDC9SbsUaggAAAEdJREFUeNqkwYURgAAQA7DH3d3335LSKyxAYpf9vWCpnYbf01qcOdFVXc14w4BznNTjkQfsscAdU3b4wIh9fDVYc4zV8xZgAAYaCMI6vPgLAAAAAElFTkSuQmCC); }
h1 { font: inherit; color: white; font-size: 50px; line-height: 121px; margin: 0; padding-left: 4%; background: url(https://files.nette.org/images/logo-nette@2.png) no-repeat 95%; background-size: 130px auto; text-shadow: 1px 1px 0 rgba(0, 0, 0, .9); }
@media (max-width: 600px) {
h1 { background: none; font-size: 40px; }
}
<h1 n:block="title">My awesome blog</h1>

#content { background: white; border: 1px solid #eff4f7; border-radius: 0 0 12px 12px; padding: 10px 4%; overflow: hidden; }
<div n:foreach="$posts as $post" class="post">
<div class="date">{$post->created_at|date:'F j, Y'}</div>

h2 { font: inherit; padding: 1.2em 0; margin: 0; }
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>

img { border: none; float: right; margin: 0 0 1em 3em; }
</style>
<div>{$post->content}</div>
</div>
{/block}
61 changes: 61 additions & 0 deletions app/UI/Post/PostPresenter.php
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace App\UI\Post;

use Nette;
use Nette\Application\UI\Form;


final class PostPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}


public function renderShow(int $id): void
{
$post = $this->database->table('posts')->get($id);
if (!$post) {
$this->error('Post not found');
}

$this->template->post = $post;
$this->template->comments = $post->related('comment')->order('created_at');
}


protected function createComponentCommentForm(): Form
{
$form = new Form;
$form->addText('name', 'Your name:')
->setRequired();

$form->addEmail('email', 'Email:');

$form->addTextArea('content', 'Comment:')
->setRequired();

$form->addSubmit('send', 'Publish comment');
$form->onSuccess[] = $this->commentFormSucceeded(...);

return $form;
}


private function commentFormSucceeded(\stdClass $data): void
{
$this->database->table('comments')->insert([
'post_id' => $this->getParameter('postId'),
'name' => $data->name,
'email' => $data->email,
'content' => $data->content,
]);

$this->flashMessage('Thank you for your comment', 'success');
$this->redirect('this');
}
}
19 changes: 19 additions & 0 deletions app/UI/Post/show.latte
@@ -0,0 +1,19 @@
{block content}
<div class="date">{$post->created_at|date:'F j, Y'}</div>

<h1 n:block=title>{$post->title}</h1>

<div class="post">{$post->content}</div>

<h2>Comments</h2>

<div class="comments">
{foreach $comments as $comment}
<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">{$comment->name}</a></b> said:</p>
<div>{$comment->content}</div>
{/foreach}
</div>

<h2>Post new comment</h2>

{control commentForm}
51 changes: 51 additions & 0 deletions app/UI/Sign/SignPresenter.php
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace App\UI\Sign;

use Nette;
use Nette\Application\UI\Form;


final class SignPresenter extends Nette\Application\UI\Presenter
{
/**
* Sign-in form factory.
*/
protected function createComponentSignInForm(): Form
{
$form = new Form;
$form->addText('username', 'Username:')
->setRequired('Please enter your username.');

$form->addPassword('password', 'Password:')
->setRequired('Please enter your password.');

$form->addSubmit('send', 'Sign in');

// call method signInFormSucceeded() on success
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form;
}


private function signInFormSucceeded(Form $form, \stdClass $data): void
{
try {
$this->getUser()->login($data->username, $data->password);
$this->redirect('Home:');

} catch (Nette\Security\AuthenticationException $e) {
$form->addError('Incorrect username or password.');
}
}


public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('You have been signed out.');
$this->redirect('Home:');
}
}
4 changes: 4 additions & 0 deletions app/UI/Sign/in.latte
@@ -0,0 +1,4 @@
{block content}
<h1 n:block=title>Sign in</h1>

{control signInForm}
13 changes: 6 additions & 7 deletions config/common.neon
Expand Up @@ -9,16 +9,15 @@ application:


database:
dsn: 'sqlite::memory:'
dsn: 'sqlite:%rootDir%/data/blog.sqlite'
user:
password:


latte:
strictTypes: yes
security:
users:
admin: secret # user 'admin', password 'secret'


di:
export:
parameters: no
tags: no
latte:
strictTypes: yes
Binary file added data/blog.sqlite
Binary file not shown.
53 changes: 53 additions & 0 deletions database.sql
@@ -0,0 +1,53 @@

SET NAMES utf8;
SET foreign_key_checks = 0;

CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post_id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`content` text NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `post_id` (`post_id`),
CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- sample data

INSERT INTO posts (id, title, content, created_at) VALUES
(1, 'Discovering the Eccentric Vogons', 'Vogons are known not just for their bureaucratic obsession but also for their dreadful poetry. This post explores the peculiar culture of Vogons, their love for paperwork, and why they are considered the third worst poets in the Universe.', '2008-05-01 12:00:00'),
(2, 'The Secrets of the Pan Galactic Gargle Blaster', 'The Pan Galactic Gargle Blaster is known as the best drink in existence. This post delves into its zesty components, the effects of its consumption, and why it should be drunk with caution. Remember, the effect is similar to having your brains smashed out by a slice of lemon wrapped round a large gold brick.', '2008-04-01 12:00:00'),
(3, 'Marvin鈥檚 Guide to Coping with Existence', 'Life? Don鈥檛 talk to me about life! This post explores Marvin the Paranoid Android鈥檚 views on existence, detailing his journey through space with his depressingly funny insights and why his processors ache so much.', '2008-03-01 12:00:00'),
(4, 'Exploring the Infinite Improbability Drive', 'The Infinite Improbability Drive is a wonderful new method of crossing vast interstellar distances in a mere nothingth of a second without all that tedious mucking about in hyperspace. This post explains how this remarkable technology works and the bizarre occurrences that happen when it鈥檚 activated.', '2008-06-01 12:00:00'),
(5, 'Why Earth is Mostly Harmless', 'According to "The Hitchhiker鈥檚 Guide to the Galaxy", Earth is described as mostly harmless. This post examines what led to such a minimalistic description and what implications it might have for future intergalactic hitchhikers looking for a quick guide to the galaxy.', '2008-02-01 12:00:00');

INSERT INTO comments (post_id, name, email, content, created_at) VALUES
(1, 'Zaphod Beeblebrox', NULL, 'Never could get the hang of Vogons. Tried to party with them once, but all they wanted to do was file reports. What a snooze fest!', '2008-06-15 14:23:52'),
(1, 'Arthur Dent', NULL, 'I had a particularly dreadful encounter with a Vogon poem once. It was worse than being strapped to a mind-evaporating machine. The horror!', '2008-08-20 09:17:31'),
(1, 'Ford Prefect', NULL, 'Vogons might have their quirks, but you have to admit, they鈥檙e remarkably consistent. Consistently terrible, but consistent nonetheless.', '2009-02-11 22:30:45'),
(2, 'Trillian', NULL, 'One should be cautious with the Pan Galactic Gargle Blaster. It鈥檚 not for the faint-hearted. I prefer something less... explosive.', '2008-05-23 18:45:19'),
(2, 'Arthur Dent', NULL, 'Tried it once, thought I was a sofa for a week. Never again.', '2009-07-08 16:54:07'),
(2, 'Ford Prefect', NULL, 'If you want to understand the universe, start with a Pan Galactic Gargle Blaster. Or end with one. Either way, it鈥檚 a profound experience.', '2008-12-30 20:11:53'),
(3, 'Slartibartfast', NULL, 'Poor Marvin, I do feel for him sometimes. But then I remember it鈥檚 better him than me!', '2008-04-14 12:22:33'),
(3, 'Zaphod Beeblebrox', NULL, 'Marvin鈥檚 got the best understanding of life. Always expect the worst, and you鈥檒l never be disappointed!', '2009-05-19 14:33:21'),
(3, 'Trillian', NULL, 'It鈥檚 quite sad, really. If Marvin were a planet, he鈥檇 be the rainiest one in the solar system.', '2008-11-23 09:45:12'),
(3, 'Arthur Dent', NULL, 'Sometimes I think Marvin secretly enjoys his misery. It鈥檚 what keeps him charged.', '2009-08-17 11:37:08'),
(4, 'Arthur Dent', NULL, 'The first time we used the Infinite Improbability Drive, I turned into a penguin. It鈥檚 hard to operate a spaceship with flippers, let me tell you.', '2008-07-30 10:27:43'),
(4, 'Ford Prefect', NULL, 'I love the unpredictability of the Improbability Drive. It鈥檚 like throwing caution to the wind, but with quantum physics!', '2009-03-26 13:12:59'),
(4, 'Zaphod Beeblebrox', NULL, 'The best part about the Drive is that it鈥檚 completely safe. Well, mostly. Okay, sometimes. Alright, at least it鈥檚 not boring!', '2009-01-15 15:01:22'),
(5, 'Trillian', NULL, 'Mostly harmless? More like mostly ignored. Earth has its charms, but they鈥檙e subtle and often missed by the galactic crowd.', '2008-03-29 08:21:34'),
(5, 'Slartibartfast', NULL, 'I鈥檝e always had a soft spot for Earth. The fjords I designed are particularly delightful. It鈥檚 a shame not many appreciate the effort.', '2008-10-18 17:14:19'),
(5, 'Marvin', NULL, 'Harmless? I鈥檝e seen more action there than in most places. It鈥檚 the boredom that鈥檚 harmful, trust me.', '2009-04-25 19:58:11'),
(5, 'Ford Prefect', NULL, 'It鈥檚 the quiet ones you have to watch. Earth might seem harmless, but it鈥檚 full of surprises.', '2009-07-03 22:33:47');

0 comments on commit 6f79b01

Please sign in to comment.