Refactor models. Handle empty ticks. Prep for CSS upload.
This commit is contained in:
		
							parent
							
								
									04e813f32c
								
							
						
					
					
						commit
						3690317206
					
				| @ -8,9 +8,9 @@ Currently very much a work in progress, but it's baically functional. | ||||
| 
 | ||||
| Deploy the `/tkr` directory to a web server that supports php. It will work either as the root of a (sub)domain (e.g. tky.mydomain.com) or if served from a subdirectory (e.g. mydomain.com/tkr). | ||||
| 
 | ||||
| If you serve it from a subdirectory, set the value of `$basePath` in `/app/Config.php` to the subdirectory name, excluding the trailing slash (e.g. `/tkr`) | ||||
| If you serve it from a subdirectory, set the value of `$basePath` in `config/init.php` to the subdirectory name, excluding the trailing slash (e.g. `/tkr`) | ||||
| 
 | ||||
| It provides an rss feed at `/rss` relative to where it's being served (e.g. `/tkr/rss` if served from `/tkr/`). Each rss entry links to an individual post (which I call "ticks"). | ||||
| It provides an rss feed at `/rss` and an atom feed at `/atom` relative to where it's being served (e.g. `/tkr/rss` if served from `/tkr/`). Each rss entry links to an individual post (which I call "ticks"). | ||||
| 
 | ||||
| ## Serving | ||||
| 
 | ||||
|  | ||||
| @ -169,13 +169,6 @@ label { | ||||
|     gap: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .upload-container { | ||||
|     background: white; | ||||
|     border-radius: 8px; | ||||
|     padding: 20px; | ||||
|     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
| 
 | ||||
| .fieldset-items { | ||||
|     margin-bottom: 14px; | ||||
|     display: grid; | ||||
| @ -293,6 +286,12 @@ label { | ||||
|         - Once the width exceeds that (e.g. desktops), it will convert to horizontal alignment | ||||
| */ | ||||
| @media (min-width: 600px) { | ||||
|     label { | ||||
|         text-align: right; | ||||
|         padding-top: 10px; /* Match input padding */ | ||||
|         margin-bottom: 0; | ||||
|     } | ||||
|              | ||||
|     .home-container { | ||||
|         grid-template-columns: 1fr 2fr; | ||||
|         grid-gap: 2em; | ||||
| @ -306,12 +305,6 @@ label { | ||||
|         align-items: start; | ||||
|     } | ||||
|             | ||||
|     label { | ||||
|         text-align: right; | ||||
|         padding-top: 10px; /* Match input padding */ | ||||
|         margin-bottom: 0; | ||||
|     } | ||||
|              | ||||
|     .file-info { | ||||
|         grid-column: 2; /* Align with input column */ | ||||
|     } | ||||
|  | ||||
| @ -42,7 +42,7 @@ loadClasses(); | ||||
| 
 | ||||
| // Everything's loaded. Now we can start ticking.
 | ||||
| Util::confirm_setup(); | ||||
| $config = Config::load(); | ||||
| $config = ConfigModel::load(); | ||||
| Session::start(); | ||||
| Session::generateCsrfToken(); | ||||
| 
 | ||||
|  | ||||
| @ -3,8 +3,8 @@ class AdminController extends Controller { | ||||
|     // GET handler
 | ||||
|     // render the admin page
 | ||||
|     public function index(){ | ||||
|         $config = Config::load(); | ||||
|         $user = User::load(); | ||||
|         $config = ConfigModel::load(); | ||||
|         $user = UserModel::load(); | ||||
| 
 | ||||
|         $vars = [ | ||||
|             'user' => $user, | ||||
| @ -17,22 +17,22 @@ class AdminController extends Controller { | ||||
|     // POST handler
 | ||||
|     // save updated settings
 | ||||
|     public function handleSave(){ | ||||
|         $config = Config::load(); | ||||
|         $config = ConfigModel::load(); | ||||
| 
 | ||||
|         if (!Config::isFirstSetup()) { | ||||
|         if (!ConfigModel::isFirstSetup()) { | ||||
|             if (!Session::isLoggedIn()){ | ||||
|                 header('Location: ' . $config->basePath . '/login'); | ||||
|                 exit; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $user = User::load(); | ||||
|         $user = UserModel::load(); | ||||
| 
 | ||||
|         // handle form submission
 | ||||
|         if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||
|             $errors = []; | ||||
|          | ||||
|             // User profile
 | ||||
|             // UserModel profile
 | ||||
|             $username        = trim($_POST['username'] ?? ''); | ||||
|             $displayName     = trim($_POST['display_name'] ?? ''); | ||||
|             $about           = trim($_POST['about'] ?? ''); | ||||
| @ -115,11 +115,15 @@ class AdminController extends Controller { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (Config::isFirstSetup()){ | ||||
|             Config::completeSetup(); | ||||
|         if (ConfigModel::isFirstSetup()){ | ||||
|             ConfigModel::completeSetup(); | ||||
|         } | ||||
| 
 | ||||
|         header('Location: ' . $config->basePath . '/admin'); | ||||
|         exit; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     private function getCustomCss(){ | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| <?php | ||||
| class AuthController extends Controller { | ||||
|     function showLogin(?string $error = null){ | ||||
|         $config = Config::load(); | ||||
|         $config = ConfigModel::load(); | ||||
|         $csrf_token = Session::getCsrfToken(); | ||||
| 
 | ||||
|         $vars = [ | ||||
| @ -14,7 +14,7 @@ class AuthController extends Controller { | ||||
|     } | ||||
| 
 | ||||
|     function handleLogin(){ | ||||
|         $config = Config::load(); | ||||
|         $config = ConfigModel::load(); | ||||
| 
 | ||||
|         $error = ''; | ||||
| 
 | ||||
| @ -48,7 +48,7 @@ class AuthController extends Controller { | ||||
| 
 | ||||
|     function handleLogout(){ | ||||
|         Session::end(); | ||||
|         $config = Config::load(); | ||||
|         $config = ConfigModel::load(); | ||||
|         header('Location: ' . $config->basePath); | ||||
|         exit; | ||||
|     } | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| <?php | ||||
| class FeedController extends Controller { | ||||
|     private Config $config; | ||||
|     private ConfigModel $config; | ||||
|     private array $ticks; | ||||
|     private array $vars; | ||||
| 
 | ||||
|     public function __construct(){ | ||||
|         $this->config = Config::load(); | ||||
|         $this->ticks = iterator_to_array(Tick::streamTicks($this->config->itemsPerPage)); | ||||
|         $this->config = ConfigModel::load(); | ||||
|         $this->ticks = iterator_to_array(TickModel::streamTicks($this->config->itemsPerPage)); | ||||
|         $this->vars = [ | ||||
|             'config' => $this->config, | ||||
|             'ticks' => $this->ticks, | ||||
|  | ||||
| @ -4,12 +4,12 @@ class HomeController extends Controller { | ||||
|     // renders the homepage view.
 | ||||
|     public function index(){ | ||||
|         $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; | ||||
|         $config = Config::load(); | ||||
|         $user = User::load(); | ||||
|         $config = ConfigModel::load(); | ||||
|         $user = UserModel::load(); | ||||
| 
 | ||||
|         $limit = $config->itemsPerPage; | ||||
|         $offset = ($page - 1) * $limit; | ||||
|         $ticks = iterator_to_array(Tick::streamTicks($limit, $offset)); | ||||
|         $ticks = iterator_to_array(TickModel::streamTicks($limit, $offset)); | ||||
| 
 | ||||
|         $view = new HomeView(); | ||||
|         $tickList = $view->renderTicksSection($config->siteDescription, $ticks, $page, $limit); | ||||
| @ -34,11 +34,13 @@ class HomeController extends Controller { | ||||
|             } | ||||
| 
 | ||||
|             // save the tick
 | ||||
|             Tick::save($_POST['tick']); | ||||
|             if (trim($_POST['tick'])){ | ||||
|                 TickModel::save($_POST['tick']); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // get the config
 | ||||
|         $config = Config::load(); | ||||
|         $config = ConfigModel::load(); | ||||
| 
 | ||||
|         // redirect to the index (will show the latest tick if one was sent)
 | ||||
|         header('Location: ' . $config->basePath); | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| <?php | ||||
|     class MoodController extends Controller { | ||||
|         public function index(){ | ||||
|             $config = Config::load(); | ||||
|             $user = User::load(); | ||||
|             $config = ConfigModel::load(); | ||||
|             $user = UserModel::load(); | ||||
|             $view = new MoodView(); | ||||
| 
 | ||||
|             $moodPicker = $view->render_mood_picker(self::get_emojis_with_labels(), $user->mood); | ||||
| @ -23,8 +23,8 @@ | ||||
|                 } | ||||
| 
 | ||||
|                 // Get the data we need
 | ||||
|                 $config = Config::load(); | ||||
|                 $user = User::load(); | ||||
|                 $config = ConfigModel::load(); | ||||
|                 $user = UserModel::load(); | ||||
|                 $mood = $_POST['mood']; | ||||
| 
 | ||||
|                 // set the mood
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| class TickController extends Controller{ | ||||
|     // every tick is identified by its timestamp
 | ||||
|     public function index(string $year, string $month, string $day, string $hour, string $minute, string $second){ | ||||
|         $model = new Tick(); | ||||
|         $model = new TickModel(); | ||||
|         $tick = $model->get($year, $month, $day, $hour, $minute, $second);        | ||||
|         $this->render('tick.php', $tick); | ||||
|     } | ||||
|  | ||||
| @ -25,7 +25,7 @@ class Session { | ||||
|     } | ||||
| 
 | ||||
|     public static function isLoggedIn(): bool { | ||||
|         //echo "User ID set: ". isset($_SESSION['user_id']). "<br/>";
 | ||||
|         //echo "UserModel ID set: ". isset($_SESSION['user_id']). "<br/>";
 | ||||
|         //exit;
 | ||||
|         return isset($_SESSION['user_id']); | ||||
|     } | ||||
|  | ||||
| @ -89,7 +89,7 @@ class Util { | ||||
|         // See if there's any data in the tables
 | ||||
|         $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); | ||||
|         $settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); | ||||
|         $config = Config::load(); | ||||
|         $config = ConfigModel::load(); | ||||
| 
 | ||||
|         // If either table has no records and we aren't on /admin
 | ||||
|         if ($user_count === 0 || $settings_count === 0){ | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| <?php | ||||
| class Config { | ||||
| class ConfigModel { | ||||
|     // properties and default values
 | ||||
|     public string $siteTitle = 'My tkr'; | ||||
|     public string $siteDescription = ''; | ||||
| @ -41,7 +41,7 @@ class Config { | ||||
|     public function save(): self { | ||||
|         $db = Util::get_db(); | ||||
| 
 | ||||
|         if (!Config::isFirstSetup()){ | ||||
|         if (!ConfigModel::isFirstSetup()){ | ||||
|             $stmt = $db->prepare("UPDATE settings SET site_title=?, site_description=?, base_url=?, base_path=?, items_per_page=? WHERE id=1"); | ||||
|         } else { | ||||
|             $stmt = $db->prepare("INSERT INTO settings (id, site_title, site_description, base_url, base_path, items_per_page) VALUES (1, ?, ?, ?, ?, ?)"); | ||||
| @ -1,5 +1,5 @@ | ||||
| <?php | ||||
| class Tick { | ||||
| class TickModel { | ||||
|     // Everything in this class just reads from and writes to the filesystem
 | ||||
|     // It doesn't maintain state, so everything's just a static function
 | ||||
|     public static function streamTicks(int $limit, int $offset = 0): Generator { | ||||
| @ -95,7 +95,7 @@ class Tick { | ||||
|                 return [ | ||||
|                     'tickTime' => $tickTime, | ||||
|                     'tick' => $tick, | ||||
|                     'config' => Config::load(), | ||||
|                     'config' => ConfigModel::load(), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| @ -1,5 +1,5 @@ | ||||
| <?php | ||||
| class User { | ||||
| class UserModel { | ||||
|     // properties
 | ||||
|     public string $username = ''; | ||||
|     public string $displayName = ''; | ||||
| @ -30,7 +30,7 @@ class User { | ||||
|    public function save(): self { | ||||
|       $db = Util::get_db(); | ||||
| 
 | ||||
|       if (!Config::isFirstSetup()){ | ||||
|       if (!ConfigModel::isFirstSetup()){ | ||||
|         $stmt = $db->prepare("UPDATE user SET username=?, display_name=?, about=?, website=?, mood=? WHERE id=1"); | ||||
|       } else { | ||||
|         $stmt = $db->prepare("INSERT INTO user (id, username, display_name, about, website, mood) VALUES (1, ?, ?, ?, ?, ?)"); | ||||
| @ -1,5 +1,5 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var User $user */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var UserModel $user */ ?>
 | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|     <head> | ||||
| @ -13,14 +13,14 @@ | ||||
|             <form method="post"> | ||||
|                 <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> | ||||
|                 <fieldset> | ||||
|                     <legend>User settings</legend> | ||||
|                     <legend>UserModel settings</legend> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <label>Username <span class=required></span></label> | ||||
|                         <label>Username <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             name="username" | ||||
|                             value="<?= $user->username ?>" | ||||
|                             required> | ||||
|                         <label>Display name <span class=required></span></label> | ||||
|                         <label>Display name <span class=required>*</span></label> | ||||
|                             <input type="text"  | ||||
|                                 name="display_name" | ||||
|                                 value="<?= $user->displayName ?>" | ||||
| @ -38,41 +38,70 @@ | ||||
|                 <fieldset> | ||||
|                     <legend>Site settings</legend> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <label>Title <span class=required></span></label> | ||||
|                         <label>Title <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             name="site_title" | ||||
|                             value="<?= $config->siteTitle ?>"  | ||||
|                             required> | ||||
|                         <label>Description <span class=required></span></label> | ||||
|                         <label>Description <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             name="site_description" | ||||
|                             value="<?= $config->siteDescription ?>"> | ||||
|                         <label>Base URL </label> | ||||
|                         <label>Base URL <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             name="base_url" | ||||
|                             value="<?= $config->baseUrl ?>" | ||||
|                             required> | ||||
|                         <label>Base path <span class=required></span></label>  | ||||
|                         <label>Base path <span class=required>*</span></label>  | ||||
|                         <input type="text" | ||||
|                             name="base_path" | ||||
|                             value="<?= $config->basePath ?>" | ||||
|                             required> | ||||
|                         <label>Items per page (max 50) <span class=required></span></label> | ||||
|                         <label>Items per page (max 50) <span class=required>*</span></label> | ||||
|                         <input type="number" | ||||
|                             name="items_per_page" | ||||
|                             value="<?= $config->itemsPerPage ?>" min="1" max="50" | ||||
|                             required> | ||||
|                     </div> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <label for="setCssFile">Set CSS File</label> | ||||
|                         <select id="setCssFile" name="css_file"> | ||||
|                             <option value="">Default</option> | ||||
|                         </select> | ||||
|                     </div> | ||||
|                 </fieldset> | ||||
|                 <fieldset> | ||||
|                     <legend>Change password</legend> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <label>New password: </label> | ||||
|                         <label>New password</label> | ||||
|                         <input type="password" name="password"> | ||||
|                         <label>Confirm new password: </label> | ||||
|                         <label>Confirm new password</label> | ||||
|                         <input type="password" name="confirm_password"> | ||||
|                     </div> | ||||
|                 </fieldset> | ||||
|                 <fieldset> | ||||
|                     <legend>CSS Upload</legend> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <form action="/upload-css" method="post" enctype="multipart/form-data"> | ||||
|                             <label for="uploadCssFile">Select File to Upload</label> | ||||
|                             <input type="file"  | ||||
|                                    id="uploadCssFile"  | ||||
|                                    name="uploadCssFile"  | ||||
|                                    accept=".css"> | ||||
|                             <div class="file-info"> | ||||
|                                 <strong>File Requirements:</strong><br> | ||||
|                                 • Must be a valid CSS file (.css extension)<br> | ||||
|                                 • Maximum size: 2MB<br> | ||||
|                                 • Will be scanned for malicious content | ||||
|                             </div> | ||||
|                             <label for="description">Description (optional)</label> | ||||
|                             <textarea id="description"  | ||||
|                                       name="description"  | ||||
|                                       placeholder="Describe this CSS file..."></textarea> | ||||
|                             <button type="submit" class="upload-btn">Upload CSS File</button> | ||||
|                         </form> | ||||
|                     </div> | ||||
|                 </fieldset> | ||||
|                 <button type="submit" class="submit-btn">Save Settings</button> | ||||
|             </form> | ||||
|         </div> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var array $ticks */ ?>
 | ||||
| <?php | ||||
| $siteTitle = htmlspecialchars($config->siteTitle); | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var array $ticks */ ?>
 | ||||
| <?php | ||||
| // Need to have a little php here because the starting xml tag
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <?php /** @var bool $isLoggedIn */ ?>
 | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var User $user */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var UserModel $user */ ?>
 | ||||
| <?php /** @var string $tickList */ ?>
 | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var string $csrf_token */ ?>
 | ||||
| <?php /** @var string $error */ ?>
 | ||||
| <!DOCTYPE html> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var string $moodPicker */ ?>
 | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
|         <title><?= $config->siteTitle ?></title>
 | ||||
|         <meta charset="UTF-8"> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
|         <div class="navbar"> | ||||
|             <a href="<?= $config->basePath ?>">home</a> | ||||
|             <a href="<?= $config->basePath ?>feed/rss">rss</a> | ||||
|  | ||||
| @ -53,7 +53,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||
| 
 | ||||
| <h1>Let’s Set Up Your tkr</h1> | ||||
| <form method="post"> | ||||
|     <h3>User settings</h3> | ||||
|     <h3>UserModel settings</h3> | ||||
|     <label>Username: <input type="text" name="username" required></label><br> | ||||
|     <label>Display name: <input type="text" name="display_name" required></label><br> | ||||
|     <label>Password: <input type="password" name="password" required></label><br> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?php /** @var Config $config */ ?>
 | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var Date $tickTime */ ?>
 | ||||
| <?php /** @var string $tick */ ?>
 | ||||
| <!DOCTYPE html> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user