Compare commits

...

5 commits

15 changed files with 59 additions and 645 deletions

4
.gitignore vendored
View file

@ -1,9 +1,7 @@
# Public assets #
#################
public/robots.txt
public/.well-known
assets/js/modules/npm
public/assets/js/modules/npm
# Bootstrapping #
#################

103
README.md
View file

@ -1,73 +1,66 @@
# vlw.se
This is the source code behind [vlw.se](https://vlw.se) which has been written from the ground up by me. This website is built on top of my [Vegvisir web framework](https://vegvisir.vlw.se) and my [Reflect API framework](https://reflect.vlw.se).
This is the source code behind [vlw.se](https://vlw.se) which is my personal website that I have written and designed from the ground up. The website is built on top of my own [web framework](https://vegvisir.vlw.se) and its API is also built on top of my own [API framework](https://reflect.vlw.se).
# Installation
If you for whatever reason want to get this website up and running for yourself this is how that is done.
Here's how you get my website up and running on your own machine. Note, I have only tested this on Linux and the install script we will run later is written in bash.
## This website requires the following prerequisites
- [PHP 8.0+](https://www.php.net/)
- [MariaDB 14+](https://mariadb.org/)
- [The NPM package manager](https://www.npmjs.com/)
- [The Reflect API framework](https://reflect.vlw.se)
- [The Vegvisir web framework](https://vegvisir.vlw.se)
- [The composer package manager](https://getcomposer.org/)
**Make sure you have both of these package managers installed before proceeding:**
- [Composer](https://getcomposer.org/)
- [NPM](https://www.npmjs.com/)
**Confimed supported framework versions:**
Vegvisir|Reflect
--|--
✅ [`3.1.0`](https://codeberg.org/vegvisir/vegvisir/releases/tag/3.1.0)|✅ [`2.7.2`](https://codeberg.org/reflect/reflect/releases/tag/2.7.2)
## 1. Clone this repo
Clone/download this repo to your machine. Preferably to a non-public directory - the frameworks will handle that.
## Website (Vegvisir)
1. **Download this repo**
Git clone or download this repo to any local folder
```
git clone https://codeberg.org/vlw/vlw.se
```
2. **Download and install Vegvisir**
Follow the installation instructions for [Vegvisir](https://vegvisir.vlw.se/docs/installation) and point the `root_path` variable to your local vlw.se folder.
```
git clone https://codeberg.org/vlw/vlw.se --depth 1
```
3. **Run the install script**
## 2. Install [Vegvisir](https://vegvisir.vlw.se) and [Reflect](https://reflect.vlw.se)
Follow the installation instructions for my web, and API framework. This site uses the default configuration for both frameworks so the only thing you need to do after you've installed both is to point the `root_path` and `endpoints` directory respectively to the directory where you cloned this repo.
This bash script will install dependencies and make npm modules public.
```
./install.sh
```
- [Vegvisir installation](https://vegvisir.vlw.se)
- [Reflect installation](https://reflect.vlw.se)
Et voila! You probably want to install the API-side too but the website itself should now be accessible from your configured Vegvisir host.
*Example:*
```sh
# Vegvisir
root_path = "/var/www/vlw.se"
# Reflect
endpoints = "/var/www/vlw.se"
```
## API (Reflect)
The API (and database) is where most content is stored and served from on this website.
## 3. Run the install script
Run the `install.sh` script from the root of the repo directory. [Make sure you have the required package managers installed](#installation).
1. **Download this repo**
**Example:**
```sh
# vlw@example:$
cd /var/www/vlw.se
# vlw@example:/var/www/vlw.se$
./install.sh
```
**You can skip this if you've already downloaded the repo from step 1 in the website installation.**
## 4. Import the database templates
There's are two SQL files that you can download from the releases page that has a snapshot of the MariaDB databases I use on my live website. The snapshot data for the website databse is not guaranteed to be up to date; but the database structure will be. Download and import these files into two existing databases. One for the website data, and the other has the Reflect API configurations.
Otherwise... Git clone or download this repo to any local folder
```
git clone https://codeberg.org/vlw/vlw.se
```
- [Download SQL-snapshots](https://codeberg.org/vlw/vlw.se/releases)
2. **Download and install Reflect**
Follow the installation instructions for [Reflect](https://reflect.vlw.se/docs/installation) and point the `endpoints` variable to the `/api` subdirectory in the local vlw.se folder.
## 5. Set environment variables
Make a copy of the `.env.example.ini` file called `.env.ini` from the root directory of the repo. There are a few parameters you can change here but the required ones are the following:
3. **Install dependencies**
```ini
[client_api]
base_url = ""
api_key = ""
`cd` into the api folder and install dependencies with composer.
```
composer install --optimize-autoloader
```
[server_database]
host = ""
user = ""
pass = ""
db = ""
```
4. **Create and import database**
Please refer to the comments in the ini file for more information about each field.
[Create and] import the two databases associated with vlw.se data and the Reflect API configurations from `.sql` files on the Releases page.
5. **Set environment variables**
Make a copy of `/api/.env.example.ini` and change the `[vlwdb]` variables with your MariaDB credentials.
6. **Set environment variables for website**
It's reasonable to assume if you've installed the website from this repo that you'd also want to use the API with it. Start my making a copy of `/.env.example.ini` (root directory) and change the `[api]` variables to point to your API hostname.
## Done!
That should be it. Navigate to your configured Vegvisir public host!

View file

@ -29,7 +29,7 @@
(new Rules(SearchTable::ID->value))
->type(Type::STRING)
->min(10)
->min(1)
->max(10)
->default(null),

View file

@ -1,60 +0,0 @@
<?php
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use const VLW\API\RESP_DELETE_OK;
use VLW\Database\Database;
use VLW\Database\Tables\Work\WorkTable;
require_once Path::root("src/Endpoints.php");
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work.php");
class DELETE_Work extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
(new Rules(WorkTable::ID->value))
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(WorkTable::TITLE->value))
->type(Type::STRING)
->min(3)
->max(parent::SIZE_VARCHAR),
(new Rules(WorkTable::SUMMARY->value))
->type(Type::STRING)
->min(1)
->max(parent::SIZE_TEXT),
(new Rules(WorkTable::IS_LISTED->value))
->type(Type::BOOLEAN),
(new Rules(WorkTable::DATE_MODIFIED->value))
->type(Type::NUMBER)
->min(1)
->max(parent::SIZE_UINT8),
(new Rules(WorkTable::DATE_CREATED->value))
->type(Type::NUMBER)
->min(1)
->max(parent::SIZE_UINT8)
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
public function main(): Response {
return $this->db->for(FieldsEnumsModel::NAME)->delete($_POST) === true
? new Response(RESP_DELETE_OK)
: new Response("Failed to delete work entity", 500);
}
}

View file

@ -1,109 +0,0 @@
<?php
use Reflect\Call;
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use VLW\API\Endpoints;
use VLW\Database\Database;
use VLW\Database\Tables\Work\{
WorkTable,
PermalinksTable
};
require_once Path::root("src/Endpoints.php");
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/Work.php");
require_once Path::root("src/Database/Tables/Work/WorkPermalinks.php");
class PATCH_Work extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->GET([
(new Rules(WorkTable::ID->value))
->required()
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR)
]);
$this->ruleset->POST([
(new Rules(WorkTable::TITLE->value))
->type(Type::STRING)
->min(3)
->max(parent::SIZE_VARCHAR),
(new Rules(WorkTable::SUMMARY->value))
->type(Type::STRING)
->min(1)
->max(parent::SIZE_TEXT),
(new Rules(WorkTable::IS_LISTED->value))
->type(Type::BOOLEAN),
(new Rules(WorkTable::DATE_MODIFIED->value))
->type(Type::NUMBER)
->min(1)
->max(parent::SIZE_UINT8)
->default(time()),
(new Rules(WorkTable::DATE_CREATED->value))
->type(Type::NUMBER)
->min(1)
->max(parent::SIZE_UINT8)
]);
parent::__construct();
}
// Generate a slug URL from string
private static function gen_slug(string $input): string {
return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input)));
}
// Compute and return modeled year, month, and day from Unix timestamp in request body
private static function gen_date_created(): array {
return [
WorkTable::DATE_YEAR->value => date("Y", $_POST[WorkTable::DATE_CREATED->value]),
WorkTable::DATE_MONTH ->value => date("n", $_POST[WorkTable::DATE_CREATED->value]),
WorkTable::DATE_DAY->value => date("j", $_POST[WorkTable::DATE_CREATED->value])
];
}
private function get_entity_by_id(string $id): Response {
return (new Call(Endpoints::WORK->value))->params([
WorkTable::ID->value => $id
])->get();
}
public function main(): Response {
// Use copy of request body as entity
$entity = $_POST;
// Generate a new slug id from title if changed
if ($_POST[WorkTable::TITLE->value]) {
$slug = $_POST[WorkTable::TITLE->value];
// Bail out if the slug generated from the new tite already exist
if ($this->get_entity_by_id($slug)) {
return new Response("An entity with this title already exist", 409);
}
// Add the new slug to update entity
$entity[WorkTable::ID] = $slug;
}
// Generate new work date fields from timestamp
if ($_POST[WorkTable::DATE_CREATED->value]) {
array_merge($entity, self::gen_date_created());
}
// Update entity by existing id
return $this->db->for(WorkTable::NAME)->where([WorkTable::ID->value => $_GET[WorkTable::ID->value]])->update($entity) === true
? new Response($_GET[WorkTable::ID->value])
: new Response("Failed to update entity", 500);
}
}

View file

@ -1,106 +0,0 @@
<?php
use Reflect\Call;
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use VLW\API\Endpoints;
use VLW\Database\Database;
use VLW\Database\Tables\Work\{
WorkTable,
PermalinksTable
};
require_once Path::root("src/Endpoints.php");
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/Work.php");
require_once Path::root("src/Database/Tables/Work/WorkPermalinks.php");
class POST_Work extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
(new Rules(WorkTable::TITLE->value))
->type(Type::STRING)
->min(3)
->max(parent::SIZE_VARCHAR)
->default(null),
(new Rules(WorkTable::SUMMARY->value))
->type(Type::STRING)
->min(1)
->max(parent::SIZE_TEXT)
->default(null),
(new Rules(WorkTable::IS_LISTED->value))
->type(Type::BOOLEAN)
->default(false),
(new Rules(WorkTable::DATE_CREATED->value))
->type(Type::NUMBER)
->min(1)
->max(parent::SIZE_UINT8)
->default(time())
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
// Generate a slug URL from string
private static function gen_slug(string $input): string {
return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input)));
}
// Compute and return modeled year, month, and day from a Unix timestamp
private static function gen_date_created(): array {
// Use provided timestamp in request
$date_created = $_POST[WorkTable::DATE_CREATED->value];
return [
WorkTable::DATE_YEAR->value => date("Y", $date_created),
WorkTable::DATE_MONTH ->value => date("n", $date_created),
WorkTable::DATE_DAY->value => date("j", $date_created)
];
}
private function get_entity_by_id(string $id): Response {
return (new Call(Endpoints::WORK->value))->params([
WorkTable::ID->value => $id
])->get();
}
public function main(): Response {
// Use copy of request body as entity
$entity = $_POST;
// Generate URL slug from title text or UUID if undefined
$entity[WorkTable::ID->value] = $_POST[WorkTable::TITLE->value]
? self::gen_slug($_POST[WorkTable::TITLE->value])
: parent::gen_uuid4();
// Bail out here if a work entry with id had been created already
if ($this->get_entity_by_id($entity[WorkTable::ID->value])->ok) {
return new Response("An entity with id '{$slug}' already exist", 409);
}
// Generate the necessary date fields
array_merge($entity, self::gen_date_created());
// Let's try to insert the new entity
if (!$this->db->for(WorkTable::NAME)->insert($entity)) {
return new Response("Failed to insert work entry", 500);
}
// Generate permalink for new entity
return (new Call(Endpoints::WORK_PERMALINKS->value))->post([
PermalinksTable::ID => $entity[WorkTable::ID->value],
PermalinksTable::REF_WORK_ID => $entity[WorkTable::ID->value],
PermalinksTable::DATE_CREATED => time()
]);
}
}

View file

@ -1,32 +0,0 @@
<?php
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use const VLW\API\RESP_DELETE_OK;
use VLW\Database\Database;
use VLW\Database\Tables\Work\ActionsTable;
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/WorkActions.php");
class DELETE_WorkActions extends Database {
protected Ruleset $ruleset;
public function __construct() {
parent::__construct();
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
(new Rules(ActionsTable::REF_WORK_ID->value))
->min(1)
->max(parent::SIZE_VARCHAR)
]);
}
public function main(): Response {
return $this->db->for(ActionsTable::NAME)->delete($_POST) === true
? new Response(RESP_DELETE_OK)
: new Response("Failed to delete action for work entity", 500);
}
}

View file

@ -1,72 +0,0 @@
<?php
use Reflect\Call;
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use VLW\API\Endpoints;
use VLW\Database\Database;
use VLW\Database\Tables\Work\{
WorkTable,
ActionsTable
};
require_once Path::root("src/Endpoints.php");
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/Work.php");
require_once Path::root("src/Database/Tables/Work/WorkActions.php");
class POST_WorkActions extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
(new Rules(ActionsTable::REF_WORK_ID->value))
->required()
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(ActionsTable::DISPLAY_TEXT->value))
->required()
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(ActionsTable::HREF->value))
->required()
->type(Type::STRING)
->type(Type::NULL)
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(ActionsTable::CLASS_LIST->value))
->type(Type::ARRAY)
->min(1)
->default([])
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
private static function get_entity(): Response {
return (new Call(Endpoints::WORK->value))->params([
WorkTable::ID->value => $_POST[ActionsTable::REF_WORK_ID->value]
])->get();
}
public function main(): Response {
// Bail out if work entity could not be fetched
$entity = self::get_entity();
if (!$entity->ok) {
return $entity;
}
return $this->db->for(ActionsTable::NAME)->insert($_POST) === true
? new Response($_POST[ActionsTable::REF_WORK_ID->value], 201)
: new Response("Failed to add action to work entity", 500);
}
}

View file

@ -1,48 +0,0 @@
<?php
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use VLW\Database\Database;
use VLW\Database\Tables\Work\PermalinksTable;
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/WorkPermalinks.php");
class GET_WorkPermalinks extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->GET([
(new Rules(PermalinksTable::ID->value))
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(PermalinksTable::REF_WORK_ID->value))
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR)
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
public function main(): Response {
$response = $this->db->for(PermalinksTable::NAME)
->where($_GET)
->select([
PermalinksTable::ID->value,
PermalinksTable::REF_WORK_ID->value,
PermalinksTable::DATE_CREATED->value
]);
return $response->num_rows > 0
? new Response($response->fetch_all(MYSQLI_ASSOC))
: new Response([], 404);
}
}

View file

@ -1,61 +0,0 @@
<?php
use Reflect\Call;
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use VLW\Database\Database;
use VLW\Database\Tables\Work\PermalinksTable;
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/WorkPermalinks.php");
class POST_WorkPermalinks extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
(new Rules(PermalinksTable::ID->value))
->required()
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(PermalinksTable::REF_WORK_ID->value))
->required()
->type(Type::STRING)
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(PermalinksTable::DATE_CREATED->value))
->type(Type::NUMBER)
->min(1)
->max(parent::SIZE_UINT8)
->default(time())
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
private static function get_entity(): Response {
return (new Call(Endpoints::WORK->value))->params([
WorkTable::ID->value => $_POST[TagsTable::REF_WORK_ID->value]
])->get();
}
public function main(): Response {
// Bail out if work entity could not be fetched
$entity = self::get_entity();
if (!$entity->ok) {
return $entity;
}
return $this->db->for(PermalinksTable::NAME)->insert($_POST) === true
? new Response($_POST[PermalinksTable::ID->value], 201)
: new Response("Failed to add permalink to work entity", 500);
}
}

View file

@ -1,38 +0,0 @@
<?php
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use const VLW\API\RESP_DELETE_OK;
use VLW\Database\Database;
use VLW\Database\Tables\Work\TagsTable;
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/WorkTags.php");
class DELETE_WorkTags extends Database {
private Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->GET([
(new Rules(TagsTable::REF_WORK_ID->value))
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(TagsTable::NAME->value))
->type(Type::ENUM, TagsNameEnum::names())
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
public function main(): Response {
return $this->db->for(TagsTable::NAME)->delete($_POST) === true
? new Response(RESP_DELETE_OK)
: new Response("Failed to delete value from document", 500);
}
}

View file

@ -1,59 +0,0 @@
<?php
use Reflect\Call;
use Reflect\{Response, Path};
use ReflectRules\{Ruleset, Rules, Type};
use VLW\API\Endpoints;
use VLW\Database\Database;
use VLW\Database\Tables\Work\{
WorkTable,
TagsTable,
TagsNameEnum
};
require_once Path::root("src/Endpoints.php");
require_once Path::root("src/Database/Database.php");
require_once Path::root("src/Database/Tables/Work/Work.php");
require_once Path::root("src/Database/Tables/Work/WorkTags.php");
class POST_WorkTags extends Database {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
(new Rules(TagsTable::REF_WORK_ID->value))
->required()
->min(1)
->max(parent::SIZE_VARCHAR),
(new Rules(TagsTable::NAME->value))
->required()
->type(Type::ENUM, TagsNameEnum::names())
]);
$this->ruleset->validate_or_exit();
parent::__construct();
}
private static function get_entity(): Response {
return (new Call(Endpoints::WORK->value))->params([
WorkTable::ID->value => $_POST[TagsTable::REF_WORK_ID->value]
])->get();
}
public function main(): Response {
// Bail out if work entity could not be fetched
$entity = self::get_entity();
if (!$entity->ok) {
return $entity;
}
return $this->db->for(TagsTable::NAME)->insert($_POST) === true
? new Response($_POST[TagsTable::REF_WORK_ID->value], 201)
: new Response("Failed to add tag to work entity", 500);
}
}

View file

@ -121,7 +121,10 @@ section.timeline .items .item img {
}
section.timeline .items .item .actions {
display: flex;
align-items: baseline;
margin-top: 7px;
gap: var(--padding);
}
/* # Size queries */
@ -166,6 +169,10 @@ section.timeline .items .item .actions {
border-top-color: rgba(var(--primer-color-accent), .2);
}
section.timeline .items .item .actions {
flex-direction: column;
}
section.timeline .year:first-of-type .month:first-of-type .day:first-of-type .items .item:first-of-type {
margin-top: var(--padding);
}

View file

@ -1 +0,0 @@
../../../../../node_modules/elevent/src/Elevent.mjs

2
public/robots.txt Normal file
View file

@ -0,0 +1,2 @@
User-agent: *
Disallow: