Fixed a lot of permissioned areas, added searchable posts, post editing is better, schema, much more

This commit is contained in:
Conner Harkness 2025-06-23 15:00:42 -06:00
parent 7b20cd13b6
commit 11d4896647
13 changed files with 456 additions and 230 deletions

View File

@ -1,18 +1,17 @@
<?php
global $c;
$varNavbarLinks = [
["Home", "/"],
["Sign in", "/user/signin"],
];
$varNavbarLinks = $c->query("SELECT * from links where position like 'navbar' order by sort");
$varSidebarLinks = $c->query("SELECT * from links where position like 'sidebar' order by sort");
$varFirstNavbarLink = array_shift($varNavbarLinks);
?>
<style>
/* https://github.com/twbs/bootstrap/issues/37184 */
.dropdown-menu {
z-index: 1040 !important;
}
</style>
<script>
// Make the page's theme dark:
$("body").first().attr("data-bs-theme", "dark");
@ -55,10 +54,8 @@
<?php endforeach; ?>
</div>
<div class="navbar-nav d-inline-flex">
<div class="navbar-nav d-inline-flex align-items-center">
<div class="dropdown">
<?php
$varUser = UserAuth::getUser();
$strUserText = "User";
@ -67,7 +64,7 @@
$strUserText = $varUser["user_name"] ?? $varUser["email"] ?? "User";
?>
<a class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown">User</a>
<a class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown"><i class="fa fa-fw fa-user"></i></a>
<div class="dropdown-menu dropdown-menu-end">

104
init.php
View File

@ -1,10 +1,48 @@
<?php
global $c;
$intInitialize = 1;
if (!file_exists("sqlite.db"))
$intInitialize = 1;
$c = new DatabaseConnection(
"sqlite",
"sqlite.db");
if ($intInitialize == 1)
{
$c->query(
"CREATE table if not exists globals (
id integer primary key autoincrement,
global text not null,
content text not null)");
$c->query(
"CREATE table if not exists credentials (
id integer primary key autoincrement,
email text not null,
hash text not null)");
$c->query(
"CREATE table if not exists users (
id integer primary key autoincrement,
email text not null,
user_name text not null,
display_name text not null)");
$c->query(
"CREATE table if not exists sessions (
id integer primary key autoincrement,
email text not null,
token text not null,
expires timestamp null)");
$c->query(
"CREATE table if not exists permissions (
id integer primary key autoincrement,
email text not null,
permission text not null)");
$c->query(
"CREATE table if not exists links (
id integer primary key autoincrement,
@ -14,6 +52,16 @@
position text not null,
sort integer not null default 0)");
$c->query(
"CREATE table if not exists posts (
id integer primary key autoincrement,
email text not null,
path text not null,
content text not null,
created timestamp not null default current_timestamp,
updated timestamp not null default current_timestamp,
sort integer not null default 0)");
$varLinks = $c->query("SELECT * from links");
if (count($varLinks) < 1)
@ -22,12 +70,14 @@
"INSERT into links (label, url, icon, position)
values
('Home', '/', 'home', 'navbar'),
('Post', '/edit', 'edit', 'navbar'),
('Post', '/post?to=/', 'edit', 'navbar'),
('Links', '/edit/links', 'link', 'navbar'),
('Go home', '/', 'home', 'sidebar'),
('Copyright © 2025 Your Company.', '/', 'home', 'footer')");
}
}
class UserAuth
{
@ -41,38 +91,24 @@
if ($strToken !== null)
if (strlen($strToken) > 0)
{
$varTokenUsers = $c->query(
"SELECT *
from tokens as t
join user as u on u.email = t.email
$varSessions = $c->query(
"SELECT
u.*,
c.*,
s.*
from sessions as s
join credentials as c on c.email = s.email
left join users as u on u.email = s.email
where
t.token = ?
s.token = ?
and (
t.expires is null
or t.expires > current_timestamp
s.expires is null
or s.expires > current_timestamp
)",
$strToken);
$varUser = null;
if (count($varTokenUsers) == 1)
$varUser = $varTokenUsers[0];
else return null;
try
{
$varUserDetails = $c->query(
"SELECT *
from user_info as ui
where
ui.email = ?",
$varUser["email"]);
if (count($varUserDetails) == 1)
$varUser = array_merge($varUser, $varUserDetails[0]);
}
catch (Exception $x) {}
return $varUser;
if (count($varSessions) == 1)
return $varSessions[0];
}
}
catch (Exception $x) {}
@ -87,20 +123,14 @@
if ($varUser == null)
return false;
$c->query(
"CREATE table if not exists permission (
id integer primary key autoincrement,
email text not null,
name text not null)");
$varPermissions = $c->query(
"SELECT *
from permission
from permissions
where
email like ?
and (
name like ?
or name like '*'
permission like ?
or permission like '*'
)",
$varUser["email"],
$strPermission);

67
pages/edit/css.php Normal file
View File

@ -0,0 +1,67 @@
<?php
global $c;
$strFile = "site.css";
$strContent = "";
UserAuth::requirePermission("admin");
if (file_exists($strFile))
$strContent = file_get_contents($strFile);
if (Request::posts("content"))
{
$strContent = Request::getPosted("content");
file_put_contents($strFile, $strContent);
}
?>
<style>
textarea {
font-family: monospace;
}
</style>
<form method="post">
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand"><?= $strFile; ?></span>
<a class="btn btn-outline-success text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
</div>
<div class="navbar-nav d-inline-flex">
</div>
</div>
</div>
<?php /**/ ?>
<div class="container my-5">
<div class="row">
<textarea
class="form-control border-0 shadow-none"
name="content"
placeholder="Enter content here..."
oninput="fnResize(this);"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"><?= $strContent; ?></textarea>
</div>
</div>
</form>
<script>
$(function() {
fnSave = function() {
$("form").first().submit();
};
fnResize = function(x) {
x.style.height = "auto";
x.style.height = x.scrollHeight + "px";
};
fnResize($("textarea").first()[0]);
});
</script>

View File

@ -1,103 +0,0 @@
<?php
global $c;
UserAuth::requirePermission("admin");
$strId = Request::getArg(0);
$strPath = "";
$strContent = "";
if (strlen($strId) > 0)
{
$varRows = $c->query("SELECT * from post where id = ?", $strId);
if (count($varRows) !== 1)
{
BootstrapRender::message("Zero or more than one row returned", "danger");
Respond::redirect("/edit");
}
$varRow = $varRows[0];
$strPath = $varRow["path"];
$strContent = $varRow["content"];
}
if (Request::posts("path", "content"))
{
$strPath = Request::getPosted("path");
$strContent = Request::getPosted("content");
if ($strId == null || strlen($strId) < 1)
{
$c->query(
"INSERT into post (author, path, content)
values (?, ?, ?)",
"caharkness@gmail.com",
$strPath,
$strContent);
$strId = $c->query("SELECT * from post where rowid = last_insert_rowid()")[0]["id"];
}
if (strlen($strContent) < 1)
{
$c->query("DELETE from post where id = ?", $strId);
BootstrapRender::message("Post deleted successfully.", "success");
Respond::redirect("/edit");
}
$c->query(
"UPDATE post
set
path = ?,
content = ?,
updated = current_timestamp
where
id = ?",
$strPath,
$strContent,
$strId);
Respond::redirect("/edit/{$strId}");
}
?>
<style>
textarea {
font-family: monospace;
}
</style>
<div class="container my-5">
<div class="row">
<div class="col-lg-6">
<div class="mb-3">
<?php BootstrapRender::message(); ?>
</div>
<form method="post">
<?php BootstrapRender::input([
"name" => "path",
"label" => "Path",
"value" => $strPath
]); ?>
<?php BootstrapRender::input([
"name" => "content",
"label" => "Content",
"tag" => "textarea",
"value" => $strContent
]); ?>
<?php BootstrapRender::buttons([
"buttons" => [[
"label" => "Submit",
"icon" => "save"
]]
]); ?>
</div>
</div>
</div>

67
pages/edit/js.php Normal file
View File

@ -0,0 +1,67 @@
<?php
global $c;
$strFile = "site.js";
$strContent = "";
UserAuth::requirePermission("admin");
if (file_exists($strFile))
$strContent = file_get_contents($strFile);
if (Request::posts("content"))
{
$strContent = Request::getPosted("content");
file_put_contents($strFile, $strContent);
}
?>
<style>
textarea {
font-family: monospace;
}
</style>
<form method="post">
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand"><?= $strFile; ?></span>
<a class="btn btn-outline-success text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
</div>
<div class="navbar-nav d-inline-flex">
</div>
</div>
</div>
<?php /**/ ?>
<div class="container my-5">
<div class="row">
<textarea
class="form-control border-0 shadow-none"
name="content"
placeholder="Enter content here..."
oninput="fnResize(this);"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"><?= $strContent; ?></textarea>
</div>
</div>
</form>
<script>
$(function() {
fnSave = function() {
$("form").first().submit();
};
fnResize = function(x) {
x.style.height = "auto";
x.style.height = x.scrollHeight + "px";
};
fnResize($("textarea").first()[0]);
});
</script>

View File

@ -1,42 +1,109 @@
<?php
global $c;
$strPath = "/";
$strPath .= implode("/", Request::getPathParts());
global $c;
$c->query(
"CREATE table if not exists post (
id integer primary key autoincrement,
author text not null,
path text not null,
content text not null,
created timestamp not null default current_timestamp,
updated timestamp not null default current_timestamp)");
$varPosts = $c->query(
"SELECT *
from post
"SELECT
p.*,
u.user_name,
u.display_name
from posts as p
left join users as u on u.email = p.email
where
path like ?
or path like '*'",
or path like '*'
order by
created desc",
$strPath);
$strSearchQuery = Request::getParam("q");
if ($strSearchQuery)
{
$varPosts = $c->query(
"SELECT
p.*,
u.user_name,
u.display_name
from posts as p
left join users as u on u.email = p.email
where
content like concat('%', ?, '%')
order by
created desc",
$strSearchQuery);
}
$varParsedown = new Parsedown();
?>
<?php if ($strSearchQuery): ?>
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand">Search</span>
<input class="form-control me-2" type="text" name="path" placeholder="e.g. /home" value="<?= $strSearchQuery; ?>" />
<a class="btn btn-outline-primary text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-search"></i> Search</a>
</div>
<div class="navbar-nav d-inline-flex">
<?php BootstrapRender::message(); ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if (file_exists("site.css")): ?>
<link rel="stylesheet" href="site.css" />
<?php endif; ?>
<?php if (Request::getParam("edit")): ?>
<div class="container my-5">
<div class="row">
<div class="col-lg-8">
<a class="link-underline link-underline-opacity-0" href="/post?to=<?= $strPath; ?>">Write a post here</a>
</div>
</div>
</div>
<?php endif; ?>
<?php foreach ($varPosts as $p): ?>
<div class="container my-5">
<div class="row">
<div class="col-lg-12">
<div class="border border-secondary rounded p-3">
<?php
$strContent = $varParsedown->text($p["content"]);
echo $strContent;
?>
<div class="col-lg-8">
<div class="xborder xborder-secondary xrounded xp-3">
<?php echo $varParsedown->text($p["content"]); ?>
</div>
<a href="/edit/<?= $p["id"]; ?>">edit</a>
<hr />
<div class="text-muted">
<div>by <?= $p["display_name"] ?? $p["user_name"] ?? $p["email"]; ?></div>
<div>on <?= $p["created"]; ?> UTC</div>
</div>
<?php if (Request::getParam("edit")): ?>
<div>
<a href="/post/<?= $p["id"]; ?>">edit</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php if (count($varPosts) < 1): ?>
<div class="container my-5">
<div class="row">
<div class="col-lg-8">
<p>Sorry, there is nothing here to show.</p>
</div>
</div>
</div>
<?php endif; ?>
<?php if (file_exists("site.js")): ?>
<script src="site.js"></script>
<?php endif; ?>

123
pages/post/index.php Normal file
View File

@ -0,0 +1,123 @@
<?php
global $c;
UserAuth::requirePermission("admin");
$varUser = UserAuth::getUser();
$strId = Request::getArg(0);
$strPath = Request::getParam("to") ?? "";
$strContent = "";
if (strlen($strId) > 0)
{
$varRows = $c->query("SELECT * from posts where id = ?", $strId);
if (count($varRows) !== 1)
{
BootstrapRender::message("Zero or more than one row returned", "danger");
Respond::redirect("/post");
}
$varRow = $varRows[0];
$strPath = $varRow["path"];
$strContent = $varRow["content"];
}
if (Request::posts("path", "content"))
{
$strPath = Request::getPosted("path");
$strContent = Request::getPosted("content");
if ($strId == null || strlen($strId) < 1)
{
$c->query(
"INSERT into posts (email, path, content)
values (?, ?, ?)",
$varUser["email"],
$strPath,
$strContent);
$strId = $c->query("SELECT * from posts where rowid = last_insert_rowid()")[0]["id"];
}
if (strlen($strContent) < 1)
{
$c->query("DELETE from posts where id = ?", $strId);
BootstrapRender::message("Post deleted successfully.", "success");
Respond::redirect("/post");
}
$c->query(
"UPDATE posts
set
path = ?,
content = ?,
updated = current_timestamp
where
id = ?",
$strPath,
$strContent,
$strId);
Respond::redirect("/post/{$strId}");
}
?>
<style>
textarea {
font-family: monospace;
}
</style>
<form method="post">
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand">Post</span>
<input class="form-control me-2" type="text" name="path" placeholder="e.g. /home" value="<?= $strPath; ?>" />
<?php if ($strId == null || strlen($strId) < 1): ?>
<a class="btn btn-outline-primary text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-plus-circle"></i> Create</a>
<?php else: ?>
<a class="btn btn-outline-success text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
<?php endif; ?>
</div>
<div class="navbar-nav d-inline-flex">
<?php BootstrapRender::message(); ?>
</div>
</div>
</div>
<?php /**/ ?>
<div class="container my-5">
<div class="row">
<textarea
class="form-control border-0 shadow-none"
name="content"
placeholder="Enter content here..."
oninput="fnResize(this);"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"><?= $strContent; ?></textarea>
</div>
</div>
</form>
<script>
$(function() {
fnSave = function() {
$("form").first().submit();
};
fnResize = function(x) {
x.style.height = "auto";
x.style.height = x.scrollHeight + "px";
};
fnResize($("textarea").first()[0]);
});
</script>

View File

@ -5,13 +5,6 @@
if (UserAuth::getUser() == null)
Respond::redirect("/user/signin");
$c->query(
"CREATE table if not exists user_info (
id integer primary key autoincrement,
email text not null unique,
user_name text null,
display_name text null)");
$varUser = UserAuth::getUser();
$strUsername = $varUser["user_name"] ?? "";
$strDisplayName = $varUser["display_name"] ?? "";
@ -30,7 +23,7 @@
throw new Exception("Username must be alphanumeric characters only");
$c->query(
"INSERT or replace into user_info (email, user_name, display_name)
"INSERT or replace into users (email, user_name, display_name)
select
?,
?,

View File

@ -1,5 +1,5 @@
<?php
global $c;
UserAuth::requirePermission("hello_world");
TableEditor::render("user", ["email", "hash"]);
UserAuth::requirePermission("admin");
TableEditor::render("credentials", ["email", "hash"]);
?>

View File

@ -1,5 +1,5 @@
<?php
global $c;
UserAuth::requirePermission("hello_world");
TableEditor::render("permission", ["email", "name", "sort"]);
UserAuth::requirePermission("admin");
TableEditor::render("permissions", ["email", "permission"]);
?>

View File

@ -3,7 +3,7 @@
try
{
$intUserCount = $c->query("SELECT count(*) as val from user")[0]["val"];
$intUserCount = $c->query("SELECT count(*) as c from credentials")[0]["c"];
if ($intUserCount < 1)
{
@ -27,13 +27,7 @@
if (strlen($strPassword) < 6)
throw new Exception("Password must be at least 6 characters");
$c->query(
"CREATE table if not exists user (
id integer primary key autoincrement,
email text not null unique,
hash text not null)");
$varUsers = $c->query("SELECT * from user where email like ?", $strEmail);
$varUsers = $c->query("SELECT * from credentials where email like ?", $strEmail);
if (count($varUsers) > 0)
throw new Exception("E-mail address in use");
@ -41,21 +35,18 @@
$strHash = sha1($strPassword);
$c->query(
"INSERT into user (email, hash) values (?, ?)",
"INSERT into credentials (email, hash) values (?, ?)",
$strEmail,
$strHash);
$intUserCount = $c->query("SELECT count(*) as val from user")[0]["val"];
$intUserCount = $c->query("SELECT count(*) as c from credentials")[0]["c"];
if ($intUserCount == 1)
{
// Calling this ensures permission table:
UserAuth::hasPermission("dummy");
$c->query(
"INSERT into permission (email, name)
values (?, 'admin')",
$strEmail);
"INSERT into permissions (email, permission) values (?, ?)",
$strEmail,
"admin");
}
BootstrapRender::message("Registration was a success, please sign in to continue.");

View File

@ -3,7 +3,7 @@
try
{
$intUserCount = $c->query("SELECT count(*) as val from user")[0]["val"];
$intUserCount = $c->query("SELECT count(*) as c from credentials")[0]["c"];
if ($intUserCount < 1)
Respond::redirect("/user/register");
@ -15,7 +15,7 @@
$strHash = sha1($strPassword);
$varUsers = $c->query(
"SELECT *
from user
from credentials
where
email like ?
and hash = ?",
@ -27,14 +27,8 @@
$strToken = sha1(microtime());
$c->query("CREATE table if not exists tokens (
id integer primary key autoincrement,
email text not null,
token text not null,
expires timestamp null)");
$c->query(
"INSERT into tokens (email, token) values (?, ?)",
"INSERT into sessions (email, token) values (?, ?)",
$strEmail,
$strToken);

View File

@ -7,7 +7,7 @@
if (Request::getArg(0) == "all")
{
$c->query(
"UPDATE tokens
"UPDATE sessions
set
expires = current_timestamp
where email = ?",
@ -16,7 +16,7 @@
else
{
$c->query(
"UPDATE tokens
"UPDATE sessions
set
expires = current_timestamp
where token = ?",