This commit is contained in:
Turtle committed 2026-01-11 10:26:39 +01:00
1 parent 565c1b9911
commit 28490ce8b3
36 files changed
+587 -179

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
data/couchdb.json
-26
View File
@@ -1,26 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="./auth.js"></script>
<script src="./lib/basic.js"></script>
<script src="./help/data_xample.js"></script>
</head>
<body>
<script>
(async () => {
try {
await createTemp();
location.href = "./login.html";
} catch(err) {
alert("Ein Fehler ist aufgetreten: " + err);
}
})();
</script>
</body>
</html>
+217
View File
@@ -0,0 +1,217 @@
{
"sassy": {
"users": {
"turtle": {
"password": "1234",
"level": 3,
"xp": 2,
"avatar": null,
"abilities": [],
"enabled_abilities": [],
"role": "student",
"courses": ["weiche_ware", "bucket"]
},
"dumble-door": {
"password": "bla",
"level": 10,
"xp": 0,
"avatar": "https://static.turtle-coding-gbr.de/gmci/profile-pic.png",
"abilities": [],
"enabled_abilities": [],
"role": "professor",
"courses": ["weiche_ware", "algebra", "geometrie"]
},
"narrator": {
"password": "stanley",
"level": 10,
"xp": 0,
"avatar": "https://static.turtle-coding-gbr.de/gmci/st",
"abilities": [
"dark_mode",
"accent_color",
"pink_mode",
"profile_pic",
"simple_border",
"gold_border",
"epic_border",
"title_of_honor"
],
"enabled_abilities": [],
"role": "professor",
"courses": ["bucket", "statistik"]
}
},
"course:bucket": {
"title": "Bucket",
"description": "The end is never the end is never the end is never",
"owner": "narrator",
"questions": [
{
"title": "What is comedic timing?",
"type": "text"
},
{
"title": "How does it work?",
"type": "text"
},
{
"title": "How long should it last?",
"type": "scala"
},
{
"title": "How can it be used to effectively silence your political enemies?",
"type": "emoji"
}
],
"result": [
["YES", "YES", 5, 2],
["YES", "YES", 1, 3]
],
"result_history": [
{
"date": "2025-12-27",
"questions": [
{ "title": "Wie war die Vorlesung?", "type": "text" },
{ "title": "Wie war der Professor?", "type": "scala" }
],
"result": [
["Gut, weil ...", 5],
["Schlecht, weil ...", 1]
]
}
],
"start_date": "2025-12-23",
"expire_date": "2026-02-20"
},
"course:weiche_ware": {
"title": "Weiche Ware",
"description": "blabering",
"owner": "dumble-door",
"questions": [
{
"title": "Wie fanden Sie die Vorlesung?",
"type": "text"
},
{
"title": "Finden Sie, dass man Zauberei an lebendigen Schülern praktizieren sollte?",
"type": "scala"
}
],
"result": [
["Gut, weil ...", 5],
["Schlecht, weil ...", 1],
["Lorem ipsum dolor sit amet", 2],
["consetetur sadipscing elitr, sed diam ", 3],
[" nonumy eirmod tempor invidunt ut labore ", 4],
["et dolore magna aliquyam erat, sed diam", 5],
[" voluptua. At vero eos et accusam et justo duo", 1],
["the end", 5]
],
"result_history": [
{
"date": "2025-12-27",
"questions": [
{ "title": "1", "type": "text" },
{ "title": "2", "type": "scala" }
],
"result": [
["Gut, weil ...", 5],
["Schlecht, weil ...", 1],
["Lorem ipsum dolor sit amet", 2],
["consetetur sadipscing elitr, sed diam ", 3],
[" nonumy eirmod tempor invidunt ut labore ", 4],
["et dolore magna aliquyam erat, sed diam", 5],
[" voluptua. At vero eos et accusam et justo duo", 1],
["the end", 5]
]
}
],
"start_date": "2025-12-23",
"expire_date": "2026-02-20"
},
"course:algebra": {
"title": "Algebra",
"description": "Grundlagen der algebraischen Konzepte.",
"owner": "math-wizard",
"questions": [
{ "title": "Wie gut verstehen Sie die Grundlagen der Algebra?", "type": "text" },
{ "title": "Bewerten Sie die Schwierigkeit algebraischer Aufgaben.", "type": "scala" }
],
"result": [
["Einfach, weil ...", 5],
["Schwierig, weil ...", 1]
],
"result_history": [
{
"date": "2025-12-28",
"questions": [
{ "title": "1", "type": "text" },
{ "title": "2", "type": "scala" }
],
"result": [
["Einfach, weil ...", 5],
["Schwierig, weil ...", 1]
]
}
],
"start_date": "2025-12-26",
"expire_date": "2025-02-25"
},
"course:geometrie": {
"title": "Geometrie",
"description": "Erforschung geometrischer Figuren und Konzepte.",
"owner": "geo-genius",
"questions": [
{ "title": "Wie sicher sind Sie im Umgang mit geometrischen Formen?", "type": "text" },
{ "title": "Bewerten Sie die Komplexität geometrischer Aufgaben.", "type": "scala" }
],
"result": [
["Einfach, weil ...", 5],
["Schwierig, weil ...", 1]
],
"result_history": [
{
"date": "2025-12-29",
"questions": [
{ "title": "1", "type": "text" },
{ "title": "2", "type": "scala" }
],
"result": [
["Einfach, weil ...", 5],
["Schwierig, weil ...", 1]
]
}
],
"start_date": "2025-12-24",
"expire_date": "2025-03-15"
},
"course:statistik": {
"title": "Statistik",
"description": "Grundlagen der statistischen Analyse und Interpretation.",
"owner": "data-analyst",
"questions": [
{ "title": "Wie gut verstehen Sie statistische Methoden?", "type": "text" },
{ "title": "Bewerten Sie die Schwierigkeit statistischer Analysen.", "type": "scala" }
],
"result": [
["Einfach, weil ...", 5],
["Schwierig, weil ...", 1]
],
"result_history": [
{
"date": "2025-12-30",
"questions": [
{ "title": "1", "type": "text" },
{ "title": "2", "type": "scala" }
],
"result": [
["Einfach, weil ...", 5],
["Schwierig, weil ...", 1]
]
}
],
"start_date": "2025-12-25",
"expire_date": "2025-04-10"
}
}
}
+8
View File
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<script>
location.href = "./login.html";
</script>
</body>
</html>
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
@@ -50,12 +50,12 @@ async function main() {
const data = await couchDB.get("xample"); const data = await couchDB.get("xample");
console.log(JSON.stringify(data)); console.log(JSON.stringify(data));
//Gibt { "my_data": 1, "more_data": "bla" } wieder //Gibt { "my_data": 1, "more_data": "bla" } wieder
console.log(JSON.stringify(await couchDB.get("*")));
//Gibt ["users","course:bucket","course:weiche_ware","course:algebra","course:geometrie","course:statistik","xample"] wieder
} }
main(); main();
createTemp().then((v) => {
setUserData(couchDB);
});
</script> </script>
+3
View File
@@ -0,0 +1,3 @@
async function createTemp() {
console.warn("createTemp is deprecated");
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2 -2
View File
@@ -105,7 +105,7 @@ window.addEventListener("load", (event) => {
//http://127.0.0.1:5984/_utils/#_config/couchdb@127.0.0.1 //http://127.0.0.1:5984/_utils/#_config/couchdb@127.0.0.1
class CouchDB { class CouchDB {
static _default_url = "http://127.0.0.1:5984/"; static _default_url = "/couchdb/";
constructor() { constructor() {
this._does_auth_exists_err(); this._does_auth_exists_err();
@@ -162,7 +162,7 @@ class CouchDB {
this.request.onreadystatechange = () => { this.request.onreadystatechange = () => {
if (request.readyState != 4) return; if (request.readyState != 4) return;
try { try {
if (request.status == 200 || request.status == 201) { if (request.status == 200 || request.status == 201) {
var response = JSON.parse(request.responseText); var response = JSON.parse(request.responseText);
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-148
View File
@@ -1,148 +0,0 @@
async function createTemp() {
const couchDB = new CouchDB();
await couchDB.set("users", {
"turtle": {
"password": "1234",
"level": 3,
"xp": 2,
"avatar": null,
"abilities": [],
"enabled_abilities": [],
"role": "student",
"courses": [
"weiche_ware",
"bucket"
]
},
"dumble-door": {
"password": "bla",
"level": 10,
"xp": 0,
"avatar": "https://static.turtle-coding-gbr.de/gmci/profile-pic.png",
"abilities": [
],
"enabled_abilities": [
],
"role": "professor",
"courses": [
"weiche_ware", "algebra", "geometrie"
]
},
"narrator": {
"password": "stanley",
"level": 10,
"xp": 0,
"avatar": "https://static.turtle-coding-gbr.de/gmci/st",
"abilities": [
"dark_mode",
"accent_color",
"pink_mode",
"profile_pic",
"simple_border",
"gold_border",
"epic_border",
"title_of_honor"
],
"enabled_abilities": [
],
"role": "professor",
"courses": [
"bucket", "statistik"
]
}
});
await couchDB.set("course:bucket", {
"title": "Bucket",
"description": "The end is never the end is never the end is never",
"owner": "narrator",
"questions": [
{
"title": "What is comedic timing?",
"type": "text"
},
{
"title": "How does it work?",
"type": "text"
},
{
"title": "How long should it last?",
"type": "scala"
},
{
"title": "How can it be used to effectively silence your political enemies?",
"type": "emoji"
}
],
"result": [
["YES", "YES", 5, 2],
["YES", "YES", 1, 3]
],
"result_history": [
{
"date": "2025-12-27",
"questions": [
{ "title": "Wie war die Vorlesung?", "type": "text" },
{ "title": "Wie war der Professor?", "type": "scala" }
],
"result": [
["Gut, weil ...", 5],
["Schlecht, weil ...", 1]
]
}
],
"start_date": "2025-12-23",
"expire_date": "2026-02-20"
});
await couchDB.set("course:weiche_ware", {
"title": "Weiche Ware",
"description": "blabering",
"owner": "dumble-door",
"questions": [
{
"title": "Wie fanden Sie die Vorlesung?",
"type": "text"
},
{
"title": "Finden Sie, dass man Zauberei an lebendigen Schülern praktizieren sollte?",
"type": "scala"
}
],
"result": [
["Gut, weil ...", 5],
["Schlecht, weil ...", 1],
["Lorem ipsum dolor sit amet", 2],
["consetetur sadipscing elitr, sed diam ", 3],
[" nonumy eirmod tempor invidunt ut labore ", 4],
["et dolore magna aliquyam erat, sed diam", 5],
[" voluptua. At vero eos et accusam et justo duo", 1],
["the end", 5]
],
"result_history": [
{
"date": "2025-12-27",
"questions": [
{ "title": "1", "type": "text" },
{ "title": "2", "type": "scala" }
],
"result": [
["Gut, weil ...", 5],
["Schlecht, weil ...", 1],
["Lorem ipsum dolor sit amet", 2],
["consetetur sadipscing elitr, sed diam ", 3],
[" nonumy eirmod tempor invidunt ut labore ", 4],
["et dolore magna aliquyam erat, sed diam", 5],
[" voluptua. At vero eos et accusam et justo duo", 1],
["the end", 5]
]
}
],
"start_date": "2025-12-23",
"expire_date": "2026-02-20"
});
await couchDB.set("course:algebra", { "title": "Algebra", "description": "Grundlagen der algebraischen Konzepte.", "owner": "math-wizard", "questions": [ { "title": "Wie gut verstehen Sie die Grundlagen der Algebra?", "type": "text" }, { "title": "Bewerten Sie die Schwierigkeit algebraischer Aufgaben.", "type": "scala" } ], "result": [ ["Einfach, weil ...", 5], ["Schwierig, weil ...", 1] ], "result_history": [ { "date": "2025-12-28", "questions": [ { "title": "1", "type": "text" }, { "title": "2", "type": "scala" } ], "result": [ ["Einfach, weil ...", 5], ["Schwierig, weil ...", 1] ] } ], "start_date": "2025-12-26", "expire_date": "2025-02-25" });
await couchDB.set("course:geometrie", { "title": "Geometrie", "description": "Erforschung geometrischer Figuren und Konzepte.", "owner": "geo-genius", "questions": [ { "title": "Wie sicher sind Sie im Umgang mit geometrischen Formen?", "type": "text" }, { "title": "Bewerten Sie die Komplexität geometrischer Aufgaben.", "type": "scala" } ], "result": [ ["Einfach, weil ...", 5], ["Schwierig, weil ...", 1] ], "result_history": [ { "date": "2025-12-29", "questions": [ { "title": "1", "type": "text" }, { "title": "2", "type": "scala" } ], "result": [ ["Einfach, weil ...", 5], ["Schwierig, weil ...", 1] ] } ], "start_date": "2025-12-24", "expire_date": "2025-03-15" });
await couchDB.set("course:statistik", { "title": "Statistik", "description": "Grundlagen der statistischen Analyse und Interpretation.", "owner": "data-analyst", "questions": [ { "title": "Wie gut verstehen Sie statistische Methoden?", "type": "text" }, { "title": "Bewerten Sie die Schwierigkeit statistischer Analysen.", "type": "scala" } ], "result": [ ["Einfach, weil ...", 5], ["Schwierig, weil ...", 1] ], "result_history": [ { "date": "2025-12-30", "questions": [ { "title": "1", "type": "text" }, { "title": "2", "type": "scala" } ], "result": [ ["Einfach, weil ...", 5], ["Schwierig, weil ...", 1] ] } ], "start_date": "2025-12-25", "expire_date": "2025-04-10" });
}
+16
View File
@@ -0,0 +1,16 @@
<?php
require_once './utils/url_handler.php';
require_once './utils/couch.php';
$path_info = new TurtlePathInfo();
$path_info->content = "./files/";
$url_obj = TurtleURLObject::create_from_server(false, false);
if (str_starts_with($url_obj->url, "couchdb")) {
$couchDB = new CouchDB("./data/couchdb.json", "./data/def_data.json", $url_obj);
$couchDB->execute();
} else {
$url_obj->handle_request($path_info);
}
+139
View File
@@ -0,0 +1,139 @@
<?php
class CouchDB {
public string $db_file;
public string $def_db_file;
public TurtleURLObject $url_obj;
public function __construct(string $db_file, string $def_db_file, TurtleURLObject $url_obj) {
$this->db_file = $db_file;
$this->def_db_file = $def_db_file;
$this->url_obj = $url_obj;
}
public function getFile(): object {
if (file_exists($this->db_file)) {
$content = json_decode(file_get_contents($this->db_file));
if (json_last_error() === JSON_ERROR_NONE) {
return $content;
}
}
if (file_exists($this->def_db_file)) {
$content = json_decode(file_get_contents($this->def_db_file));
if (json_last_error() === JSON_ERROR_NONE) {
return $content;
}
}
return (object) [];
}
public function setFile(object $content): void {
file_put_contents($this->db_file, str_replace(" ", "\t", json_encode($content, JSON_PRETTY_PRINT)));
}
public function sendError(string $msg, int $code = 404) :bool {
http_response_code($code);
$this->url_obj->send_json(["ok" => false, "reason" => $msg]);
return false;
}
public function sendData(object $data = new stdClass()) :bool {
$data->ok = true;
$this->url_obj->send_json($data);
return true;
}
public function execute(): bool {
$method = strtoupper($this->url_obj->method);
$args = $this->url_obj->args;
//path
$pathParts = explode("/", trim($this->url_obj->url ?? "", "/"));
if (!empty($pathParts) && strtolower($pathParts[0]) == "couchdb") {
array_splice($pathParts, 0, 1);
}
if (empty($pathParts)) return $this->sendData();
$dbName = $pathParts[0] ?? null;
$docId = null;
if (sizeof($pathParts) > 1) $docId = $pathParts[1] ?? null;
if (!$dbName) return $this->sendError("wrong_usage");
//data
$db_obj = $this->getFile();
$jsonBody = null;
$rawBody = $this->url_obj->get_body_text();
if ($rawBody !== "") {
$jsonBody = json_decode($rawBody, true);
if (!is_array($jsonBody) && !is_object($jsonBody)) $jsonBody = null;
else $jsonBody = (object) $jsonBody;
}
//get data
$obj = null;
if (isset($db_obj->$dbName) && is_object($db_obj->$dbName)) {
if ($docId && isset($db_obj->$dbName->$docId) && is_object($db_obj->$dbName->$docId)) {
$obj = $db_obj->$dbName->$docId;
} else if ($method != "PUT" && !($method == "GET" && $docId == "*")) {
return $this->sendError("missing");
}
} else if ($method != "PUT") {
if (!$dbName || !isset($db_obj->$dbName)) {
return $this->sendError("no_db_file");
}
} else {
$db_obj->$dbName = null;
}
//execute
switch ($method) {
case "GET":
if ($docId == "*") {
$res_arr = [];
foreach ($db_obj->$dbName as $key => $value) $res_arr[] = $key;
$this->url_obj->send_json($res_arr);
return true;
}
$obj->_id = $docId;
$obj->_rev = 0;
$this->url_obj->send_json($obj);
return true;
case "PUT":
if ($db_obj->$dbName == null) {
if ($docId == null) {
$db_obj->$dbName = (object) [];
$this->setFile($db_obj);
return $this->sendData();
} else {
return $this->sendError("no_db_file");
}
}
$db_obj->$dbName->$docId ??= (object) [];
if ($jsonBody == null) return $this->sendError("missing_body");
unset($jsonBody->_id);
unset($jsonBody->_rev);
$db_obj->$dbName->$docId = $jsonBody;
$this->setFile($db_obj);
return $this->sendData((object) [
"id" => $docId,
"rev" => 0
]);
case "DELETE":
unset($db_obj->$dbName->$docId);
$this->setFile($db_obj);
return $this->sendData((object) [ "id" => $docId ]);
default:
return $this->sendError("method_not_allowed");
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
function isFileExtImage(string $fileExt) :bool {
switch ($fileExt) {
case 'png':
case 'jpg':
case 'jpeg':
case 'ico':
case 'gif':
return true;
default:
return false;
}
}
function res_mixed(bool $success, mixed $data = null, string $except = null) :object {
return $success ? res_ok($data): res_err($data ?? $except);
}
function res_ok(mixed $data = null) :object {
return (object) [
"type" => "success",
"data" => $data
];
}
function res_err(string|null $message = null) :object {
return (object) [
"type" => "error",
"message" => $message
];
}
+167
View File
@@ -0,0 +1,167 @@
<?php
require_once 'features.php';
///A collection of given paths
class TurtlePathInfo {
public string $content;
}
///Parse url and execute it
class TurtleURLObject {
public static function create_from_server(bool $lowerCase = true, bool $alternativeURL = true) :TurtleURLObject {
$urlParts = explode('?', trim($_SERVER['REQUEST_URI'], '/'));
$urlMark = _parseUrlMark($urlParts);
$request = str_replace("..", ".", $urlParts[0]);
if ($lowerCase) $request = strtolower($request);
if ($alternativeURL) $request = str_replace("-", "/", $request);
$res = new TurtleURLObject($request, $urlMark);
$res->method = $_SERVER['REQUEST_METHOD'];
$res->method_type = explode(',', $_SERVER['HTTP_ACCEPT'] ?? '');
return $res;
}
public string $url;
public object $args;
public function __construct(string $url, object $args) {
$this->url = $url;
$this->args = $args;
}
public string $method;
public array $method_type;
public function get_body_text() :string {
$bodyContent = file_get_contents("php://input");
return $bodyContent;
}
public function get_body_json() :mixed {
return json_decode($this->get_body_text());
}
public function get_html_file(string $file_path): string {
if (!file_exists($file_path)) {
return "Not found!";
} else {
return file_get_contents($file_path);
}
}
public function send_text(string $text) :void {
header('Content-Type: text/plain');
echo $text;
}
public function send_json(mixed $data) :void {
header('Content-Type: application/json');
echo json_encode($data);
}
public function send_image(string $path) :void {
$fileExt = pathinfo($path, PATHINFO_EXTENSION);
if ($fileExt === "ico") {
header("Cache-Control: no-cache, must-revalidate, max-age=0");
header("Content-Type: image/x-icon");
} else {
header("Content-Type: image/$fileExt");
}
readfile($path);
}
public function send_custom_file(string $path) :bool {
$fileExt = pathinfo($path, PATHINFO_EXTENSION);
if (isFileExtImage($fileExt)) {
$this->send_image($path);
} else if ($fileExt == "json") {
$content = file_get_contents($path);
if ($content == false) return false;
header('Content-Type: application/json');
echo $content;
} else if ($fileExt == "txt") {
$content = file_get_contents($path);
if ($content == false) return false;
$this->send_text($content);
} else if ($fileExt == "js") {
header('Content-Type: text/javascript; charset=utf-8');
echo file_get_contents($path);
return true;
} else if ($fileExt == "css") {
header("Content-Type: text/css");
echo file_get_contents($path);
} else if ($fileExt == "html") {
echo $this->get_html_file($path);
} else {
return false;
}
return true;
}
public function send_download_file(string $path) :bool {
if (!file_exists($path)) return false;
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($path).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
return true;
}
public function is_image_request() :bool {
foreach ($this->method_type as $type) {
if (!str_starts_with($type, "image/")) return false;
}
return true;
}
public function handle_request(TurtlePathInfo $path_info): void {
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$filePath = $path_info->content . $this->url;
if (strlen($this->url) == 0) {
$filePath = "{$path_info->content}START.html";
}
//-------------Image-Pages-------------------//
else if (
!empty($referer) && $this->is_image_request()
) {
if (file_exists($filePath)) {
$this->send_image($filePath);
return;
} else {
$filePath = NULL;
}
}
//-------------HTML-Pages-------------------//
if ($filePath === NULL || !file_exists($filePath)) {
$filePath = "{$path_info->content}NOT-FOUND.html";
http_response_code(404);
}
$this->send_custom_file($filePath);
}
}
function _parseUrlMark($urlParts) :object {
$res = (object) [];
if (count($urlParts) == 1) return $res;
foreach (explode('&', $urlParts[1]) as $part) {
$arr = explode('=', $part, 2);
$cnt = count($arr);
switch ($cnt) {
case 1:
$res->{$arr[0]} = 1;
break;
case 2:
$res->{$arr[0]} = $arr[1];
break;
}
}
return $res;
}