Initial commit

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-01-11 06:54:59 +07:00
commit 37070c626e
21 changed files with 1223 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
!.gitignore
composer.phar
composer.lock
vendor

11
LICENSE Executable file
View File

@ -0,0 +1,11 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

2
README.md Executable file
View File

@ -0,0 +1,2 @@
# notchat
Free P2P chat based on asdasd

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\views\manager,
mirzaev\notchat\models\core as models,
mirzaev\notchat\models\account_model as account,
mirzaev\notchat\models\session_model as session;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Framework for PHP
use mirzaev\minimal\controller;
/**
* Core of controllers
*
* @package mirzaev\notchat\controllers
* @author mirzaev < mail >
*/
class core extends controller
{
/**
* Postfix for name of controllers files
*/
final public const POSTFIX = '';
/**
* Instance of a session
*/
protected readonly session session;
/**
* Instance of an account
*/
protected readonly ?account account;
/**
* Registry of errors
*/
protected array errors = [
'session' => [],
'account' => []
];
/**
* Constructor of an instance
*
* @param bool initialize Initialize a controller?
*
* @return void
*/
public function __construct(bool initialize = true)
{
// Blocking requests from CloudFlare (better to write this blocking into nginx config file)
if (_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return;
// For the extends system
parent::__construct(initialize);
if (initialize) {
// Initializing is requested
// Initializing of models core (connect to ArangoDB...)
new models();
// Initializing of the date until which the session will be active
expires = strtotime('+1 week');
// Initializing of default value of hash of the session
_COOKIE["session"] ??= null;
// Initializing of session
this->session = new session(_COOKIE["session"], expires, this->errors['session']);
// Handle a problems with initializing a session
if (!empty(this->errors['session'])) die;
else if (_COOKIE["session"] !== this->session->hash) {
// Hash of the session is changed (implies that the session has expired and recreated)
// Write a new hash of the session to cookies
setcookie(
'session',
this->session->hash,
[
'expires' => expires,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'strict'
]
);
}
// Initializing of preprocessor of views
this->view = new templater(this->session);
}
}
/**
* Check of initialization
*
* Checks whether a property is initialized in a document instance from ArangoDB
*
* @param string name Name of the property from ArangoDB
*
* @return bool The property is initialized?
*/
public function __isset(string name): bool
{
// Check of initialization of the property and exit (success)
return match (name) {
default => isset(this->{name})
};
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\controllers\core;
/**
* Index controller
*
* @package mirzaev\notchat\controllers
* @author mirzaev < mail >
*/
final class index extends core
{
/**
* Render the main page
*
* @param array parameters Parameters of the request (POST + GET)
*/
public function index(array parameters = []): ?string
{
// Exit (success)
if (_SERVER['REQUEST_METHOD'] === 'GET') return this->view->render(DIRECTORY_SEPARATOR . 'index.html');
else if (_SERVER['REQUEST_METHOD'] === 'POST') return main;
// Exit (fail)
return null;
}
}

View File

@ -0,0 +1,289 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Framework for PHP
use mirzaev\minimal\model;
// Framework for ArangoDB
use mirzaev\arangodb\connection as arangodb,
mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Libraries for ArangoDB
use ArangoDBClient\Document as _document,
ArangoDBClient\DocumentHandler as _document_handler;
// Built-in libraries
use exception;
/**
* Core of models
*
* @package mirzaev\notchat\controllers
* @author mirzaev < mail >
*/
class core extends model
{
/**
* Postfix for name of models files
*/
final public const POSTFIX = '';
/**
* Path to the file with settings of connecting to the ArangoDB
*/
final public const ARANGODB = '../settings/arangodb.php';
/**
* Instance of the session of ArangoDB
*/
protected static arangodb arangodb;
/**
* Name of the collection in ArangoDB
*/
public const COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST_REPLACE_IT_IN_THE_MODEL';
/**
* Constructor of an instance
*
* @param bool initialize Initialize a model?
* @param ?arangodb arangodb Instance of a session of ArangoDB
*
* @return void
*/
public function __construct(bool initialize = true, ?arangodb arangodb = null)
{
// For the extends system
parent::__construct(initialize);
if (initialize) {
// Initializing is requested
if (isset(arangodb)) {
// Recieved an instance of a session of ArangoDB
// Write an instance of a session of ArangoDB to the property
this->__set('arangodb', arangodb);
} else {
// Not recieved an instance of a session of ArangoDB
// Initializing of an instance of a session of ArangoDB
this->__get('arangodb');
}
}
}
/**
* Read from ArangoDB
*
* @param string filter Expression for filtering (AQL)
* @param string sort Expression for sorting (AQL)
* @param int amount Amount of documents for collect
* @param int page Page
* @param string return Expression describing the parameters to return (AQL)
* @param array &errors The registry on errors
*
* @return _document|array|null An array of instances of documents from ArangoDB, if they are found
*/
public static function read(
string filter = '',
string sort = 'd.created DESC, d._key DESC',
int amount = 1,
int page = 1,
string return = 'd',
array &errors = []
): _document|array|null {
try {
if (collection::init(static::arangodb->session, static::COLLECTION)) {
// Initialized the collection
// Read from ArangoDB and exit (success)
return collection::search(
static::arangodb->session,
sprintf(
<<<'AQL'
FOR d IN %s
%s
%s
LIMIT %d, %d
RETURN %s
AQL,
static::COLLECTION,
empty(filter) ? '' : "FILTER filter",
empty(sort) ? '' : "SORT sort",
--page <= 0 ? 0 : amount * page,
amount,
return
)
);
} else throw new exception('Failed to initialize the collection');
} catch (exception e) {
// Write to the registry of errors
errors[] = [
'text' => e->getMessage(),
'file' => e->getFile(),
'line' => e->getLine(),
'stack' => e->getTrace()
];
}
// Exit (fail)
return null;
}
/**
* Delete from ArangoDB
*
* @param _document instance Instance of the document from ArangoDB
* @param array &errors The registry on errors
*
* @return bool Deleted from ArangoDB without errors?
*/
public static function delete(_document instance, array &errors = []): bool
{
try {
if (collection::init(static::arangodb->session, static::COLLECTION)) {
// Initialized the collection
// Delete from ArangoDB and exit (success)
return (new _document_handler(static::arangodb->session))->remove(instance);
} else throw new exception('Failed to initialize the collection');
} catch (exception e) {
// Write to the registry of errors
errors[] = [
'text' => e->getMessage(),
'file' => e->getFile(),
'line' => e->getLine(),
'stack' => e->getTrace()
];
}
// Exit (fail)
return false;
}
/**
* Update in ArangoDB
*
* @param _document instance Instance of the document from ArangoDB
*
* @return bool Writed to ArangoDB without errors?
*/
public static function update(_document instance): bool
{
// Update in ArangoDB and exit (success)
return document::update(static::arangodb->session, instance);
}
/**
* Write
*
* @param string name Name of the property
* @param mixed value Value of the property
*
* @return void
*/
public function __set(string name, mixed value = null): void
{
match (name) {
'arangodb' => (function () use (value) {
if (this->__isset('arangodb')) {
// Is alredy initialized
// Exit (fail)
throw new exception('Forbidden to reinitialize the session of ArangoDB (this::arangodb)', 500);
} else {
// Is not already initialized
if (value instanceof arangodb) {
// Recieved an appropriate value
// Write the property and exit (success)
self::arangodb = value;
} else {
// Recieved an inappropriate value
// Exit (fail)
throw new exception('Session of ArangoDB (this::arangodb) is need to be mirzaev\arangodb\connection', 500);
}
}
})(),
default => parent::__set(name, value)
};
}
/**
* Read
*
* @param string name Name of the property
*
* @return mixed Content of the property, if they are found
*/
public function __get(string name): mixed
{
return match (name) {
'arangodb' => (function () {
try {
if (!this->__isset('arangodb')) {
// Is not initialized
// Initializing of a default value from settings
this->__set('arangodb', new arangodb(require static::ARANGODB));
}
// Exit (success)
return self::arangodb;
} catch (exception) {
// Exit (fail)
return null;
}
})(),
default => parent::__get(name)
};
}
/**
* Delete
*
* @param string name Name of the property
*
* @return void
*/
public function __unset(string name): void
{
// Deleting a property and exit (success)
parent::__unset(name);
}
/**
* Check of initialization
*
* @param string name Name of the property
*
* @return bool The property is initialized?
*/
public function __isset(string name): bool
{
// Check of initialization of the property and exit (success)
return parent::__isset(name);
}
/**
* Call a static property or method
*
* @param string name Name of the property or the method
* @param array arguments Arguments for the method
*/
public static function __callStatic(string name, array arguments): mixed
{
match (name) {
'arangodb' => (new static)->__get('arangodb'),
default => throw new exception("Not found: name", 500)
};
}
}

View File

@ -0,0 +1,276 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\ebala\models\account,
mirzaev\ebala\models\traits\status;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Library для ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception;
/**
* Model of session
*
* @package mirzaev\notchat\controllers
* @author mirzaev < mail >
*/
final class session extends core
{
/**
* Name of the collection in ArangoDB
*/
final public const COLLECTION = 'session';
/**
* An instance of the ArangoDB document from ArangoDB
*/
protected readonly _document document;
/**
* Constructor of an instance
*
* Initialize of a session and write them to the this->document property
*
* @param ?string hash Hash of the session in ArangoDB
* @param ?int expires Date of expiring of the session (used for creating a new session)
* @param array &errors Registry of errors
*
* @return static instance of the ArangoDB document of session
*/
public function __construct(?string hash = null, ?int expires = null, array &errors = [])
{
try {
if (collection::init(static::arangodb->session, self::COLLECTION)) {
// Initialized the collection
if (this->search(hash, errors)) {
// Found an instance of the ArangoDB document of session and received a session hash
} else {
// Not found an instance of the ArangoDB document of session
// Initializing a new session and write they into ArangoDB
_id = document::write(this::arangodb->session, self::COLLECTION, [
'active' => true,
'expires' => expires ?? time() + 604800,
'ip' => _SERVER['REMOTE_ADDR'],
'x-forwarded-for' => _SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
'referer' => _SERVER['HTTP_REFERER'] ?? null,
'useragent' => _SERVER['HTTP_USER_AGENT'] ?? null
]);
if (session = collection::search(this::arangodb->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d._id == '%s' && d.expires > %d && d.active == true
RETURN d
AQL,
self::COLLECTION,
_id,
time()
))) {
// Found an instance of just created new session
// Generate a hash and write into an instance of the ArangoDB document of session property
session->hash = sodium_bin2hex(sodium_crypto_generichash(_id));
if (document::update(this::arangodb->session, session)) {
// Is writed update
// Write instance of the ArangoDB document of session into property and exit (success)
this->document = session;
} else throw new exception('Could not write the session data');
} else throw new exception('Could not create or find just created session');
}
} else throw new exception('Could not initialize the collection');
} catch (exception e) {
// Write to the registry of errors
errors[] = [
'text' => e->getMessage(),
'file' => e->getFile(),
'line' => e->getLine(),
'stack' => e->getTrace()
];
}
}
/**
* Search
*
* Search for the session in ArangoDB by hash and write they into this->document property if they are found
*
* @param ?string hash Hash of the session in ArangoDB
* @param array &errors Registry of errors
*
* @return static instance of the ArangoDB document of session
*/
public function search(?string hash, array &errors = []): bool
{
try {
if (isset(hash)) {
// Recieved a hash
// Search the session data in ArangoDB
_document = session = collection::search(this::arangodb->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d.hash == '%s' && d.expires > %d && d.active == true
RETURN d
AQL,
self::COLLECTION,
hash,
time()
));
if (_document instanceof _document) {
// An instance of the ArangoDB document of session is found
// Write the session data to the property
this->document = _document;
// Exit (success)
return true;
}
}
} catch (exception e) {
// Write to the registry of errors
errors[] = [
'text' => e->getMessage(),
'file' => e->getFile(),
'line' => e->getLine(),
'stack' => e->getTrace()
];
}
// Exit (fail)
return false;
}
/**
* Write to buffer of the session
*
* @param array data Data for merging
* @param array &errors Registry of errors
*
* @return bool Is data has written into the session buffer?
*/
public function write(array data, array &errors = []): bool
{
try {
if (collection::init(this::arangodb->session, self::COLLECTION)) {
// Initialized the collection
// An instance of the ArangoDB document of session is initialized?
if (!isset(this->document)) throw new exception('An instance of the ArangoDB document of session is not initialized');
// Write data into buffwer of an instance of the ArangoDB document of session
this->document->buffer = array_replace_recursive(
this->document->buffer ?? [],
[_SERVER['INTERFACE'] => array_replace_recursive(this->document->buffer[_SERVER['INTERFACE']] ?? [], data)]
);
// Write to ArangoDB and exit (success)
return document::update(this::arangodb->session, this->document) ? true : throw new exception('Не удалось записать данные в буфер сессии');
} else throw new exception('Could not initialize the collection');
} catch (exception e) {
// Write to the registry of errors
errors[] = [
'text' => e->getMessage(),
'file' => e->getFile(),
'line' => e->getLine(),
'stack' => e->getTrace()
];
}
return false;
}
/**
* Write
*
* Write a property into an instance of the ArangoDB document
*
* @param string name Name of the property
* @param mixed value Content of the property
*
* @return void
*/
public function __set(string name, mixed value = null): void
{
// Write to the property into an instance of the ArangoDB document and exit (success)
this->document->{name} = value;
}
/**
* Read
*
* Read a property from an instance of the ArangoDB docuemnt
*
* @param string name Name of the property
*
* @return mixed Content of the property
*/
public function __get(string name): mixed
{
// Read a property from an instance of the ArangoDB document and exit (success)
return match (name) {
'arangodb' => this::arangodb,
default => this->document->{name}
};
}
/**
* Delete
*
* Deinitialize the property in an instance of the ArangoDB document
*
* @param string name Name of the property
*
* @return void
*/
public function __unset(string name): void
{
// Delete the property in an instance of the ArangoDB document and exit (success)
unset(this->document->{name});
}
/**
* Check of initialization
*
* Check of initialization of the property into an instance of the ArangoDB document
*
* @param string name Name of the property
*
* @return bool The property is initialized?
*/
public function __isset(string name): bool
{
// Check of initializatio nof the property and exit (success)
return isset(this->document->{name});
}
/**
* Execute a method
*
* Execute a method from an instance of the ArangoDB document
*
* @param string name Name of the method
* @param array arguments Arguments for the method
*
* @return mixed Result of execution of the method
*/
public function __call(string name, array arguments = []): mixed
{
// Execute the method and exit (success)
if (method_exists(this->document, name)) return this->document->{name}(arguments);
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models\traits;
// Built-in libraries
use exception;
/**
* Trait fo initialization of a status
*
* @package mirzaev\notchat\models\traits
*
* @author mirzaev < mail >
*/
trait status
{
/**
* Initialize of a status
*
* @param array &errors Registry of errors
*
* @return ?bool Status, if they are found
*/
public function status(array &errors = []): ?bool
{
try {
// Read from ArangoDB and exit (success)
return this->document->active ?? false;
} catch (exception e) {
// Write to the registry of errors
errors[] = [
'text' => e->getMessage(),
'file' => e->getFile(),
'line' => e->getLine(),
'stack' => e->getTrace()
];
}
// Exit (fail)
return null;
}
}

View File

@ -0,0 +1,47 @@
@charset "UTF-8";
* {
text-decoration: none;
outline: none;
border: none;
/* font-family: , system-ui, sans-serif; */
font-family: "dejavu";
transition: 0.1s ease-out;
}
body {
margin: 0;
min-height: 100vh;
padding: 0;
display: flex;
flex-direction: column;
background-color: var(--background, #fafafa);
}
aside {
}
header {
}
main {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
transition: 0s;
}
footer {
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat;
use mirzaev\minimal\core;
use mirzaev\minimal\router;
/* ini_set('error_reporting', E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); */
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('INDEX', __DIR__);
// Автозагрузка
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
// Инициализация маршрутизатора
router = new router;
// Запись маршрутов
router->write('/', 'index', 'index');
// Инициализация ядра
core = new core(namespace: __NAMESPACE__, router: router);
// Обработка запроса
echo core->start();

View File

@ -0,0 +1,3 @@
*
!.gitignore
!*.sample

View File

@ -0,0 +1,8 @@
<?php
return [
'endpoint' => 'unix:///var/run/arangodb3/arango.sock',
'database' => 'notchat',
'name' => 'notchat',
'password' => ''
];

View File

@ -0,0 +1,202 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\views;
// Files of the project
use mirzaev\notchat\models\session,
mirzaev\notchat\models\account;
// Framework for PHP
use mirzaev\minimal\controller;
// Templater of views
use Twig\Loader\FilesystemLoader,
Twig\Environment as twig,
Twig\Extra\Intl\IntlExtension as intl,
Twig\TwigFilter;
// Built-in libraries
use ArrayAccess;
/**
* Templater core
*
* @package mirzaev\notchat\views
* @author mirzaev < mail >
*/
final class templater extends controller implements ArrayAccess
{
/**
* Registry of global variables of view
*/
public array variables = [];
/**
* Instance of twig templater
*/
readonly public twig twig;
/**
* Constructor of an instance
*
* @param ?session session Instance of the session of ArangoDB
*
* @return void
*/
public function __construct(?session &session = null): void
{
// Initializing of an instance of twig
this->twig = new twig(new FilesystemLoader(VIEWS));
// Initializing of global variables
this->twig->addGlobal('theme', 'default');
this->twig->addGlobal('server', _SERVER);
this->twig->addGlobal('cookies', _COOKIE);
if (!empty(session->status())) {
this->twig->addGlobal('session', session);
}
// Initializing of twig extensions
this->twig->addExtension(new intl());
}
/**
* Render a HTML-document
*
* @param string file Related path to a HTML-document
* @param ?array variables Registry of variables to push into registry of global variables
*
* @return ?string HTML-документ
*/
public function render(string file, ?array variables = null): ?string
{
// Generation and exit (success)
return this->twig->render('themes' . DIRECTORY_SEPARATOR . this->twig->getGlobal('theme') . DIRECTORY_SEPARATOR . file, variables + this->variables);
}
/**
* Write
*
* Write a variable into registry of global variables
*
* @param string name Name of the variable
* @param mixed value Value of the variable
*
* @return void
*/
public function __set(string name, mixed value = null): void
{
// Write the variable and exit (success)
this->variables[name] = value;
}
/**
* Read
*
* Read a variable from registry of global variables
*
* @param string name Name of the variable
*
* @return mixed Content of the variable, if they are found
*/
public function __get(string name): mixed
{
// Read the variable and exit (success)
return this->variables[name];
}
/**
* Delete
*
* Delete a variable from the registry of global variables
*
* @param string name Name of the variable
*
* @return void
*/
public function __unset(string name): void
{
// Delete the variable and exit (success)
unset(this->variables[name]);
}
/**
* Check of initialization
*
* Check of initialization in registry of global variables
*
* @param string name Name of the variable
*
* @return bool The variable is initialized?
*/
public function __isset(string name): bool
{
// Check of initialization of the variable and exit (success)
return isset(this->variables[name]);
}
/**
* Write
*
* Write a variable into registry of global variables
*
* @param mixed name Name of an offset of the variable
* @param mixed value Value of the variable
*
* @return void
*/
public function offsetSet(mixed name, mixed value): void
{
// Write the variable and exit (success)
this->variables[name] = value;
}
/**
* Read
*
* Read a variable from registry of global variables
*
* @param mixed name Name of the variable
*
* @return mixed Content of the variable, if they are found
*/
public function offsetGet(mixed name): mixed
{
// Read the variable and exit (success)
return this->variables[name];
}
/**
* Delete
*
* Delete a variable from the registry of global variables
*
* @param mixed name Name of the variable
*
* @return void
*/
public function offsetUnset(mixed name): void
{
// Delete the variable and exit (success)
unset(this->variables[name]);
}
/**
* Check of initialization
*
* Check of initialization in registry of global variables
*
* @param mixed name Name of the variable
*
* @return bool The variable is initialized?
*/
public function offsetExists(mixed name): bool
{
// Check of initialization of the variable and exit (success)
return isset(this->variables[name]);
}
}

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="ru">
<head>
{% use 'head.html' with title as head_title, meta as head_meta, css as head_css %}
{% block title %}
{{ block('head_title') }}
{% endblock %}
{% block meta %}
{{ block('head_meta') }}
{% endblock %}
{{ block('head_css') }}
{% block css %}
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
{% include 'js.html' %}
{% block js %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,10 @@
{% block css %}
{% endblock %}
{% block body %}
<footer>
</footer>
{% endblock %}
{% block js %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% block title %}
<title>{% if head.title != empty %}{{head.title}}{% else %}notchat by mirzaev{% endif %}</title>
{% endblock %}
{% block meta %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% for meta in head.metas %}
<meta {% for name, value in meta.attributes %}{{name}}="{{value}}" {% endfor %}>
{% endfor %}
{% endblock %}
{% block css %}
<link type="text/css" rel="stylesheet" href="/css/themes/default/main.css" />
{% endblock %}

View File

@ -0,0 +1,10 @@
{% block css %}
{% endblock %}
{% block body %}
<header>
</header>
{% endblock %}
{% block js %}
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends "core.html" %}
{% use "core.html" with css as core_css, body as core, js as core_js %}
{% use "header.html" with css as header_css, body as header, js as header_js %}
{% use "footer.html" with css as footer_css, body as footer, js as footer_js %}
{% block css %}
{{ block('core_css') }}
{{ block('header_css') }}
{{ block('footer_css') }}
{% endblock %}
{% block body %}
{{ block('header') }}
<main>
{% block main %}
{{ main|raw }}
{% endblock %}
</main>
{{ block('footer') }}
{% endblock %}
{% block js %}
{{ block('footer_js') }}
{{ block('header_js') }}
{{ block('core_js') }}
{% endblock %}

View File

@ -0,0 +1,2 @@
{% block js %}
{% endblock %}

46
composer.json Executable file
View File

@ -0,0 +1,46 @@
{
"name": "mirzaev/notchat",
"description": "Free P2P chat based on asdasd",
"readme": "README.md",
"keywords": [],
"type": "site",
"homepage": "https://git.mirzaev.sexy/mirzaev/notchat",
"license": "WTFPL",
"authors": [
{
"name": "mirzaev",
"email": "mirzaev@gmail.com",
"homepage": "https://mirzaev.page",
"role": "Programmer"
}
],
"support": {
"docs": "https://git.mirzaev.sexy/mirzaev/notchat/manual",
"issues": "https://git.mirzaev.sexy/mirzaev/notchat/issues"
},
"require": {
"php": "~8.3",
"ext-sodium": "~8.3",
"mirzaev/minimal": "^2.2.0",
"mirzaev/accounts": "~1.2.x-dev",
"mirzaev/arangodb": "^1.0.0",
"triagens/arangodb": "~3.9.x-dev",
"twig/twig": "^3.4"
},
"require-dev": {
"phpunit/phpunit": "~9.5"
},
"autoload": {
"psr-4": {
"mirzaev\\notchat\\": "mirzaev/notchat/system"
}
},
"autoload-dev": {
"psr-4": {
"mirzaev\\notchat\\tests\\": "mirzaev/notchat/tests"
}
},
"scripts": {
"pre-update-cmd": "./install.sh"
}
}

10
install.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
if [ -d author/project ]; then
mv author/project author/notchat
fi
if [ -d author ]; then
mv author mirzaev
fi