Compare commits

...

6 commits

Author SHA1 Message Date
e8a81b789b chore: bump Vegvisir to 3.2.8 (#52)
Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/52
2025-09-11 12:31:53 +02:00
5e9317def5 fix: enable strict parameter checks for /coffee POST endpoint (#51)
This will prevent typos from slipping through, which is nice.

Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/51
2025-08-30 08:27:36 +02:00
8209ea5ecc fix: /coffee DELETE endpoint incorrectly configured and missing table reference (#50)
Fix class config and missing `from()` method for the `/coffee` DELETE endpoint

Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/50
2025-08-30 08:27:12 +02:00
85e8e00091 feat(api): create DateTimeImmutable from timestamp for coffee POST endpoint (#49)
This PR allows creation of coffee entities at a specific date from a Unix timestamp by passing an integer to `date_created` when making a POST request to the `/coffee` endpoint.

Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/49
2025-08-24 03:40:58 +02:00
vlw
d4d73e9278 feat: add install script (#48)
This PR adds an install script which will configure vlw.se along with Reflect and Vegvisir. The only step which is manual (maybe for now) is pointing the web and REST API server. This is mentioned at the end of the installation.

Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/48
Co-authored-by: vlw <victor@vlw.se>
Co-committed-by: vlw <victor@vlw.se>
2025-08-07 13:00:10 +02:00
fafa8c5852 fix: search minimum length offset and merge oversight (#47)
Fixed merge derp from #46 and minimum search query length offset

Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/47
2025-08-02 14:58:36 +02:00
8 changed files with 282 additions and 119 deletions

View file

@ -1,67 +1,27 @@
# 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).
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 [Vegvisir web framework](https://vegvisir.vlw.se) and its optional REST API is built on top of my [Reflect API framework](https://reflect.vlw.se).
# Installation
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.
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 requires Bash with `coreutils` installed.
**Make sure you have both of these package managers installed before proceeding:**
- [Composer](https://getcomposer.org/)
- [NPM](https://www.npmjs.com/)
## Prerequisites
- A web server
- A MariaDB/MySQL server
- PHP 8.4 or newer with the following extensions enabled:
- - `php8.4-mysql`
- - `php8.4-mbstring`
- The composer package manager
- Bash with `coreutils` installed (for the install script)
## 1. Clone this repo
Clone/download this repo to your machine. Preferably to a non-public directory - the frameworks will handle that.
Clone this repository with its submodules. Preferably to a non-public directory - the frameworks will handle that.
```
git clone https://codeberg.org/vlw/vlw.se --depth 1
git clone https://codeberg.org/vlw/vlw.se --recurse-submodules --depth 1
```
## 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.
- [Vegvisir installation](https://vegvisir.vlw.se)
- [Reflect installation](https://reflect.vlw.se)
*Example:*
```sh
# Vegvisir
root_path = "/var/www/vlw.se"
# Reflect
endpoints = "/var/www/vlw.se"
## 2. Run the install script
Run the `install.sh` script from the root directory of this repository.
```
## 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).
**Example:**
```sh
# vlw@example:$
cd /var/www/vlw.se
# vlw@example:/var/www/vlw.se$
./install.sh
```
## 4. Import the database templates
There's are two SQL files in [`/src/Database/Seeds/`](https://codeberg.org/vlw/vlw.se/src/branch/master/src/Database/Seeds) that you can use to initialize the two databases required for this website.
- `vlw` - This database has the website data and should be added to the `db` variable under `server_database` in `/.env.ini`
- `vlw_reflect` - This is the Reflect database that has all the endpoints pre-configured. You'll have to add your own ACL rules.
## 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:
```ini
[client_api]
base_url = ""
api_key = ""
[server_database]
host = ""
user = ""
pass = ""
db = ""
```
Please refer to the comments in the ini file for more information about each field.
## Done!
That should be it. Navigate to your configured Vegvisir public host!
This script will install and configure Vegvisir, Reflect, and the website through a few propmpted steps.

View file

@ -8,12 +8,11 @@
use VLW\Database\Models\Coffee\Coffee;
use VLW\Database\Tables\Coffee\Coffee as CoffeeTable;
require_once Path::root("src/UUID.php");
require_once Path::root("src/API/API.php");
require_once Path::root("src/Database/Models/Coffee/Coffee.php");
require_once Path::root("src/Database/Tables/Coffee/Coffee.php");
final class POST_Coffee extends API {
final class DELETE_Coffee extends API {
public function __construct() {
parent::__construct(new Ruleset()->GET([
new Rules(CoffeeTable::ID->value)

View file

@ -13,9 +13,10 @@
final class POST_Coffee extends API {
public function __construct() {
parent::__construct(new Ruleset()->POST([
parent::__construct(new Ruleset(strict: true)->POST([
new Rules(CoffeeTable::DATE_CREATED->value)
->type(Type::STRING)
->type(Type::NUMBER)
->default(null)
]));
}
@ -26,7 +27,10 @@
// Parse DateTime from POST string
if ($_POST[CoffeeTable::DATE_CREATED->value]) {
try {
$datetime = new DateTimeImmutable($_POST[CoffeeTable::DATE_CREATED->value]);
// Create DateTimeImmutable from Unix timestamp or datetime string
$datetime = gettype($_POST[CoffeeTable::DATE_CREATED->value]) === "integer"
? DateTimeImmutable::createFromTimestamp($_POST[CoffeeTable::DATE_CREATED->value])
: new DateTimeImmutable($_POST[CoffeeTable::DATE_CREATED->value]);
} catch (DateMalformedStringException $error) {
return new Response($error->getMessage(), 400);
}

251
install.sh Normal file
View file

@ -0,0 +1,251 @@
#!/bin/bash
# Define constants
DB_VLW="vlw"
DB_API="vlw_api"
# Initialize variables
cwd=""
database_app_host=""
database_app_user=""
database_seed_host=""
database_seed_user=""
database_app_password=""
database_seed_password=""
echo_err() {
echo "!! -> $1"
}
# Make sure we have all the system packages we need to proceed with the installation
check_sys_depend() {
local valid=true
local packages=("git" "composer")
for package in "${packages[@]}" ; do
if ! dpkg -l | grep -qw "ii ${package}" ; then
echo_err "Package '${package}' is not installed."
valid=false
fi
done
# Bail out if any required package is missing
if [ "$valid" = false ] ; then
exit 1
fi
}
install_vegvisir() {
echo
echo "Installing Vegvisir into '$cwd/vegvisir'"
curl -fsSL https://codeberg.org/vegvisir/install/raw/branch/master/install.sh | bash -s -- --install=n --overwrite=y --example=n --dir="$cwd"
}
install_reflect() {
echo
echo "Installing Reflect into '$cwd/reflect'"
curl -fsSL https://codeberg.org/reflect/install/raw/branch/master/install.sh | bash -s -- --install=n --overwrite=y --seed=n --dir="$cwd" --endpoints="api" --host="$database_app_host" --user="$database_app_user" --password="$database_app_password" --db="$DB_API"
}
install_vlw() {
composer install --classmap-authoritative
}
seed_databases() {
echo
echo "-- Database seeding --"
echo "We're now going to seed the databases '$DB_VLW' and '$DB_API' with default data"
echo "- Make sure that both databases exist and are empty"
echo "- Your credentials for this user '$(whoami)' might be different from your app credentials (php-mysql)"
echo
# Database seed hostname
echo "Enter the full hostname of your MySQL/MariaDB server to use in this script for seeding."
read -p "Press enter to use the same host as the app credentials [$database_app_host]: " database_seed_host </dev/tty
# Use the same database host as the app configuration
if [[ "$database_seed_host" == "" ]] ; then
database_seed_host=$database_app_host
fi
# Database seed user
echo
echo "Enter the username for your MySQL/MariaDB server to use in this script for seeding."
read -p "Press enter to use the same host as the app credentials [$database_app_user]: " database_seed_user </dev/tty
# Use the same database user as the app configuration
if [[ "$database_seed_user" == "" ]] ; then
database_seed_user=$database_app_user
fi
# Database seed password
echo
echo "Enter the password for your MySQL/MariaDB server to use in this script for seeding."
echo "Enter 'null' to disable password authentication"
read -p "Press enter to use the same password as the app credentials [<database_password>]: " database_seed_password </dev/tty
# Use the same database password as the app configuration
if [[ "$database_seed_password" == "" ]] ; then
database_seed_password=$database_app_password
fi
# Seed the main database
mysql -h "$database_seed_host" -u "$database_seed_user" --password="$database_seed_password" $DB_VLW < src/Database/Seeds/vlw.sql
if [ $? -ne 0 ]; then
echo_err "Installation aborted: MySQL error"
exit 1
fi
# Seed the API database
mysql -h "$database_seed_host" -u "$database_seed_user" --password="$database_seed_password" $DB_API < src/Database/Seeds/api.sql
if [ $? -ne 0 ]; then
echo_err "Installation aborted: MySQL error"
exit 1
fi
}
configure_vlw() {
local config_password=""
local config_available=""
local config_available_to=""
local config_available_from=""
local config_available_average=""
local config_available_timezone=""
local config_forgejo=""
local config_forgejo_url=""
local config_forgejo_profiles=""
# A configuration file already exists
if [[ -f ".env.ini" ]] ; then
echo
echo "A configuration file already exists at: ${cwd}/.env.ini"
read -p "Do you want to overwrite this file? (y/n): " overwrite </dev/tty
# Remove existing configuration file or abort
if [[ "$overwrite" == "y" || "$overwrite" == "Y" ]] ; then
echo "Removing existing configuration and proceeding with the installation in ${cwd}..."
rm .env.ini
else
echo_err "Installation aborted: Configuration file already exists"
exit 1
fi
fi
echo
echo "-- Database configuration --"
read -p "Enter the full hostname of your MySQL/MariaDB server (php-mysql): " database_app_host </dev/tty
read -p "Enter the app username for your MySQL/MariaDB server (php-mysql): " database_app_user </dev/tty
read -p "Enter the app password for your MySQL/MariaDB server (php-mysql): " config_password </dev/tty
# Coerce empty input as null keyword for later configurations
if [[ "$config_password" == "" ]] ; then
database_app_password="null"
fi
echo
read -p "(Optional) Would you like to configure time available settings? (y/n): " config_available </dev/tty
# Check the user's response
if [[ "$config_available" == "n" || "$config_available" == "N" ]]; then
config_available_to=0
config_available_from=0
config_available_average=0
config_available_timezone="Europe/Stockholm"
fi
if ! [[ -n "$config_available_timezone" ]]; then
read -p "Enter your timezone in IANA Time Zone Database Format ('Europe/Stockholm' for example): " config_available_timezone </dev/tty
fi
if ! [[ -n "$config_available_from" ]]; then
read -p "Enter time available from hour (24-hour format): " config_available_from </dev/tty
fi
if ! [[ -n "$config_available_to" ]]; then
read -p "Enter time available to hour (24-hour format): " config_available_to </dev/tty
fi
if ! [[ -n "$config_available_average" ]]; then
read -p "Enter average reply time in hours: " config_available_average </dev/tty
fi
echo
read -p "(Optional) Would you like to configure Forgejo language updates? (y/n): " config_forgejo </dev/tty
# Check the user's response
if [[ "$config_forgejo" == "n" || "$config_forgejo" == "N" ]]; then
config_forgejo_url="https://git.vlw.se"
config_forgejo_profiles="vlw,vegvisir,reflect"
fi
if ! [[ -n "$config_forgejo_url" ]]; then
read -p "Enter a hostname to a Forgejo instance ('https://git.vlw.se' for example): " config_forgejo_url </dev/tty
fi
if ! [[ -n "$config_forgejo_profiles" ]]; then
read -p "Enter a comma seperated list of Forgejo profiles to scan ('vlw,vegvisir,reflect' for example): " config_forgejo_profiles </dev/tty
fi
local config=(
"; This config file was generated automatically by ./install.sh"
"; Refer to '.env.example.ini' for more information"
"[mariadb]"
"host = '$database_app_host'"
"user = '$database_app_user'"
"pass = '$config_password'"
"db = '$DB_VLW'"
"[config_time_available]"
"time_zone = '$config_available_timezone'"
"available_to_hour = '$config_available_to'"
"reply_average_hours = '$config_available_average'"
"available_from_hour = '$config_available_from'"
"[service_forgejo]"
"url = '$config_forgejo_url'"
"profiles = '$config_forgejo_profiles'"
)
for line in "${config[@]}" ; do
echo "${line}" >> .env.ini
done
}
main() {
# Get the current working directory
cwd=$(pwd)
check_sys_depend
configure_vlw
seed_databases
install_vlw
install_vegvisir
install_reflect
echo "-- Success --"
echo "vlw.se has been installed! :)"
echo "- Point all traffic to your web server to '${cwd}/vegvisir/public/index.php'"
echo "- Point all traffic to your REST API server to '${cwd}/reflect/public/index.php'"
echo "-------------"
echo
}
# Prompt the user for confirmation
echo
echo "-- Installing vlw.se --"
echo "You are currently in: $(pwd)"
read -p "Do you want to proceed with the installation in this directory? (y/n): " choice </dev/tty
# Check the user's response
if [[ "$choice" == "y" || "$choice" == "Y" ]] ; then
echo "Proceeding with the installation in $(pwd)..."
main
else
echo "Installation aborted."
fi

View file

@ -1,68 +1,13 @@
const DEBOUNCE_TIMEOUT_MS = 100;
const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive";
<<<<<<< HEAD
// Set global Vegvisir naviation delay for page transition effect
VV.delay = 100;
// Handle search box open/close buttons
{
// Open search box
new Elevent("click", document.querySelector(".searchbox-open"), () => {
document.querySelector("header").classList.add(CLASSNAME_SEARCHBOX_ACTIVE);
// Select searchbox inner input element
document.querySelector("searchbox input").focus();
});
// Close searchbox
new Elevent("click", document.querySelector(".searchbox-close"), () => {
// Disable search button interaction while animation is running
// This is required to prevent conflicts with the :hover "peak" transformation
const searchButtonElement = document.querySelector("header button.search");
const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
searchButtonElement.style.setProperty("pointer-events", "none");
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
// Wait for the transform animation to finish
setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration);
});
}
// Root shell navigation event handlers
{
// On all top shell navigations
new Elevent(VV.EVENT.START, VV.shell, () => {
// Close searchbox on top shell navigations
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
});
}
// Handle search logic
{
const searchResultsElement = document.querySelector("search-results");
document.querySelector("header input[type='search']").addEventListener("input", (event) => {
// Debounce user input
clearTimeout(event.target._throttle);
event.target._throttle = setTimeout(() => {
// Navigate search-results element on user input
new VV(searchResultsElement).navigate(`/search?query=${event.target.value}`);
}, 100);
});
}
=======
// Navigate to the start page if the logo in the header is clicked
document.querySelector("header .logo").addEventListener("click", () => new VV().navigate("/"));
>>>>>>> chore/v3.2
// Navigate to the start page if the logo in the header is clicked
document.querySelector("header .logo").addEventListener("click", () => new VV().navigate("/"));
// Scroll page to top on navigation
<<<<<<< HEAD
VV.shell.addEventListener(VV.EVENT.FINISH, () => window.scrollTo({ top: 0 }));
=======
VV.shell.addEventListener(VV.EVENT.FINISH, () => window.scrollTo({ top: 0 }));
// Open search box
@ -98,4 +43,3 @@ document.querySelector("header input[type='search']").addEventListener("input",
new VV(document.querySelector("search-results")).navigate(`/search?q=${event.target.value}`);
}, DEBOUNCE_TIMEOUT_MS);
});
>>>>>>> chore/v3.2

View file

@ -17,7 +17,7 @@
public function __construct() {
$this->query = $_GET[GET_KEY_QUERY] ?? "";
$this->results = strlen($this->query) > MIN_QUERY_LENGTH ? parent::query($this->query, limit: LIMIT_RESULTS) : [];
$this->results = strlen($this->query) >= MIN_QUERY_LENGTH ? parent::query($this->query, limit: LIMIT_RESULTS) : [];
}
}
@ -88,7 +88,7 @@
</section>
<?php break; ?>
<?php case 1: ?>
<?php case (MIN_QUERY_LENGTH - 1): ?>
<section class="center">
<?= VV::embed("public/assets/media/icons/search.svg") ?>
<p>Almost there, type at least two letters to search</p>

View file

@ -3,6 +3,7 @@
namespace VLW\Database\Models\Coffee;
use \VV;
use \Exception;
use \vlw\MySQL\Order;
use \DateTimeImmutable;
@ -61,7 +62,11 @@
}
public function delete(): bool {
return $this->db->delete([CoffeeTable::ID->value => $this->id]);
try {
return $this->db->from(CoffeeTable::TABLE)->delete([CoffeeTable::ID->value => $this->id]);
} catch (Exception $error) {
return false;
}
}
final public DateTimeImmutable $date_created {

@ -1 +1 @@
Subproject commit 461b2cc82b268ca09919a3506625957a868a9d27
Subproject commit 016b88068212243ce33894fbba9ffa91009146f0