implemented database, article cards, and parsing from markdown.

This commit is contained in:
Joshua Ashton
2025-03-19 23:27:20 -06:00
parent 3b794ae87d
commit 5af0547864
16 changed files with 426 additions and 20 deletions
+2
View File
@@ -1,2 +1,4 @@
*.env *.env
composer.lock
/vendor/ /vendor/
profile_pictures
+10
View File
@@ -0,0 +1,10 @@
<?php
require_once 'functions/init.php';
require_once 'functions/functions.php';
include_once 'components/head.php';
include_once 'functions/article_functions.php';
echo article_page_from_markdown($_GET['article_id']);
include_once 'components/foot.php';
+4
View File
@@ -0,0 +1,4 @@
<foot></foot>
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<link href="css/base.css" rel="stylesheet" type="text/css">
<link href="css/text.css" rel="stylesheet" type="text/css">
<link href="css/images.css" rel="stylesheet" type="text/css">
</head>
<body>
+15
View File
@@ -0,0 +1,15 @@
{
"name": "quaxlyqueen/vintagecoding.net",
"description": "My personal blogging platform.",
"type": "project",
"require": {
"erusev/parsedown": "^1.7",
"vlucas/phpdotenv": "^5.6"
},
"authors": [
{
"name": "Josh Ashton",
"email": "me@joshashton.dev"
}
]
}
+75 -7
View File
@@ -7,16 +7,84 @@ body {
background-color: black; background-color: black;
} }
.card { .line {
margin: 20% auto; height: 1px;
width: 60%; width: 100%;
padding-top: 50px; background-color: white;
padding-bottom: 50px; border-top: 1px solid white;
margin-top: 5px;
margin-bottom: 5px;
}
border: 1px solid white; .row {
display: flex;
justify-content: space-between;
width: 100%;
margin: 15px 0px 15px 0px;
}
.center-h {
margin: 0 auto;
}
.article_card {
margin: 0 auto;
margin-top: 15%;
padding: 18px;
height: max-content;
width: 60%;
border: 5px solid white;
border-radius: 18px; border-radius: 18px;
text-align: center; display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.card_info_box {
display: flex;
}
.col-right {
margin-right: 25px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: end;
}
.card_pic_box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.button {
padding: 15px;
text-decoration: none;
border: 2px solid #00FF41;
color: white;
border-radius: 9px;
}
.tag {
margin: 15px;
padding: 10px;
border-radius: 18px;
border: 2px solid #008F11;
color: white;
text-decoration: none;
}
.tag_row {}
.tag_box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
} }
@media (max-width: 980px) { @media (max-width: 980px) {
+5
View File
@@ -0,0 +1,5 @@
.card_profile_picture {
height: 64px;
width: auto;
border-radius: 50%;
}
+28
View File
@@ -3,11 +3,39 @@ h1 {
color: white; color: white;
} }
h2 {
font-size: 5vw;
}
h3 {
font-size: 4vw;
}
h4 {
font-size: 3vw;
color: white;
}
h5 {
font-size: 2vw;
}
p { p {
font-size: 1vw; font-size: 1vw;
color: white; color: white;
} }
small {
color: white;
}
a.fill_div {
display: block;
height: 100%;
width: 100%;
text-decoration: none;
}
@media (max-width: 980px) { @media (max-width: 980px) {
h1 { h1 {
font-size: 4vw; font-size: 4vw;
+71
View File
@@ -0,0 +1,71 @@
<?php
require 'db_functions.php';
/*
* Reads the markdown file for a given article and generates the HTML for it.
*/
function article_page_from_markdown($article_id)
{
$query = 'SELECT * FROM articles WHERE article_id = ' . $article_id . ';';
$article = query_db_one_result($query);
$markdown = read_file_one_string($_ENV['ARTICLES_PATH'] . $article_id . '/' . $article['markdown_file_path']);
// Instantiate Parsedown
$parsedown = new Parsedown();
// Example usage
$html = $parsedown->text($markdown);
return $html;
}
/*
* Generates cards for a given article.
*/
function article_card($article_id)
{
$article_query = 'SELECT * FROM articles WHERE article_id = ' . $article_id . ';';
$article = query_db_one_result($article_query);
$article_tags_query = 'SELECT tag FROM article_tags INNER JOIN tags ON article_tags.tag_id = tags.tag_id WHERE article_id = ' . $article_id . ';';
$tags_results = query_db_one_or_more_results($article_tags_query);
$tags_html = '<div class="tag_row">';
while ($row = mysqli_fetch_array($tags_results))
$tags_html .= '<a href="/article.php?tag=' . $row['tag'] . '" class="tag">#' . $row['tag'] . '</a>';
$tags_html .= '</div>';
$author_query = 'SELECT username, profile_picture FROM user WHERE user_id = ' . $article['author_id'] . ';';
$author = query_db_one_result($author_query);
$card = '
<div class="article_card">
<div class="row">
<h4>' . $article['title'] . '</h4>
<div class="card_info_box">
<div class="col-right">
<small>Written by: ' . $author['username'] . '</small>
<small>Published: ' . $article['published_at'] . '</small>
</div>
<div class="card_pic_box">
<img src="/profile_pictures/' . $author['profile_picture'] . '" class="card_profile_picture">
</div>
</div>
</div>
<div class="line"></div>
<p>' . $article['excerpt'] . '</p>
<div class="row">
<a href="/article.php?article_id=' . $article['article_id'] . '" class="button">Read More</a>
<div class="tag_box">' . $tags_html . '</div>
</div>
</div>
';
return $card;
}
/*
* Displays the article creation wizard and opens a new markdown file.
*/
function create_article() {}
+53
View File
@@ -0,0 +1,53 @@
<?php
// Use environment variables for the database password and IP address.
$db_user = $_ENV['DATABASE_USER'];
$db_pass = $_ENV['DATABASE_PASSWORD'];
$ip_addr = $_ENV['IP_ADDRESS'];
// Make some constants
if ($_SERVER['HTTP_HOST'] == 'localhost') {
define('HOST', 'localhost');
define('USER', $db_user);
define('PASS', $db_pass);
define('DB', 'vintagecoding');
} else {
define('HOST', $ip_addr);
define('USER', $db_user);
define('PASS', $db_pass);
define('DB', 'vintagecoding');
}
/*
* Handles any queries that should have only one row results.
*/
function query_db_one_result($query_stmt)
{
$conn = get_connection();
$results = mysqli_query($conn, $query_stmt);
mysqli_close($conn);
if (mysqli_num_rows($results) == 0 || mysqli_num_rows($results) > 1)
return null;
else
return mysqli_fetch_assoc($results);
}
function query_db_one_or_more_results($query_stmt)
{
$conn = get_connection();
$results = mysqli_query($conn, $query_stmt);
mysqli_close($conn);
if (mysqli_num_rows($results) == 0)
return null;
else
return $results;
}
function get_connection()
{
// Connect to the DB
$conn = mysqli_connect(HOST, USER, PASS, DB);
return $conn;
}
+20
View File
@@ -0,0 +1,20 @@
<?php
/*
* Reads in file contents and stores as a single string.
*/
function read_file_one_string($file_path)
{
$fs = fopen($file_path, 'r');
$string = fread($fs, filesize($file_path));
return $string;
}
function pretty_dump($arr)
{
return '
<pre>
' . print_r($arr) . '
</pre>
';
}
+11
View File
@@ -0,0 +1,11 @@
<?php
// Initialize Composer.
require_once 'vendor/autoload.php';
// Load environment variables.
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->safeLoad();
// Set SESSION variable.
$_SESSION['initialized'] = true;
+11 -13
View File
@@ -1,16 +1,14 @@
<!DOCTYPE html> <?php
<html>
<head> // Initializes environment variables and other stuff.
<link href="css/base.css" rel="stylesheet" type="text/css"> // if (!isset($_SESSION['initialized']))
<link href="css/text.css" rel="stylesheet" type="text/css"> require_once 'functions/init.php';
</head>
<body> require_once 'functions/functions.php';
<div class="card">
<h1>Under Construction</h1>
<p>Come back 5.25.2025</p>
</div>
</body>
</html> include_once 'components/head.php';
include_once 'functions/article_functions.php';
echo article_card(1);
include_once 'components/foot.php';
+54
View File
@@ -0,0 +1,54 @@
CREATE DATABASE vintagecoding;
USE vintagecoding;
CREATE TABLE roles (
role_id INT PRIMARY KEY AUTO_INCREMENT,
role VARCHAR(16) NOT NULL
);
INSERT INTO roles (role)
VALUES ('owner'), ('admin'), ('contributor'), ('reader');
CREATE TABLE tags (
tag_id INT PRIMARY KEY AUTO_INCREMENT,
tag VARCHAR(16) NOT NULL
);
INSERT INTO tags (tag)
VALUES ('linux'), ('web-dev'), ('raspberry-pi'), ('self-hosting');
CREATE TABLE user (
user_id INT PRIMARY KEY,
username VARCHAR(32) NOT NULL UNIQUE,
email VARCHAR(64) NOT NULL UNIQUE,
password_hash VARCHAR(256) NOT NULL,
last_active TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
role_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT FALSE,
profile_picture VARCHAR(256),
FOREIGN KEY (role_id) REFERENCES roles (role_id)
);
CREATE TABLE articles (
article_id INT PRIMARY KEY AUTO_INCREMENT,
author_id INT NOT NULL,
markdown_file_path VARCHAR(256) NOT NULL UNIQUE,
title VARCHAR(64) NOT NULL,
slug VARCHAR(32),
read_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
published_at TIMESTAMP DEFAULT NULL,
published BOOLEAN DEFAULT FALSE,
excerpt VARCHAR(256),
FOREIGN KEY (author_id) REFERENCES user(user_id)
);
CREATE TABLE article_tags (
article_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (article_id, tag_id),
FOREIGN KEY (article_id) REFERENCES articles(article_id),
FOREIGN KEY (tag_id) REFERENCES tags(tag_id)
);
+54
View File
@@ -0,0 +1,54 @@
ARGS_COUNT=$#
help () {
echo "CLI Tool used to initialize the database for vintagecoding.net."
echo "With no arguments, it creates the application user in MariaDB,"
echo "and creates the following tables:"
echo "- roles (populated);"
echo "- tags (populated);"
echo "- user;"
echo "- articles;"
echo "- article_tags;"
echo ""
echo "The following flags are accepted:"
echo " init_db.sh { --help | -h }"
echo " init_db.sh { --delete | -d } { --verbose | -v }"
}
if [[ $ARGS_COUNT > 0 ]]; then
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
help
exit
fi
# Delete the existing database and start over.
if [[ "$1" == "--delete" || "$1" == "-d" ]]; then
if [[ "$2" == "--verbose" || "$2" == "-v" ]]; then
echo "Deleting the existing database vintagecoding..."
fi
echo "vintage user pw"
mariadb -u vintage -p -e "DROP DATABASE vintagecoding;"
if [[ "$2" == "--verbose" || "$2" == "-v" ]]; then
echo "Deleting the vintage user..."
fi
echo "root user pw"
mariadb -u root -p -e "DROP USER vintage@localhost;"
if [[ "$2" == "--verbose" || "$2" == "-v" ]]; then
echo "Creating the vintage user..."
fi
echo "root user pw"
mariadb -u root -p < setup.sql;
if [[ "$2" == "--verbose" || "$2" == "-v" ]]; then
echo "Creating (and populating enum/static data) tables..."
fi
echo "vintage user pw"
mariadb -u vintage -p < init.sql;
exit
fi
fi
#mariadb -u root -p < setup.sql;
mariadb -u vintage -p < init.sql;
+3
View File
@@ -0,0 +1,3 @@
--This must be ran as the root user in MariaDB
CREATE USER vintage@localhost IDENTIFIED BY 'changeme';
GRANT ALL PRIVILEGES ON vintagecoding.* TO vintage@localhost IDENTIFIED BY 'changeme';