mirror of
https://github.com/kevachat/webapp.git
synced 2025-01-22 04:24:18 +00:00
implement comission charge for publications
This commit is contained in:
parent
4cae437965
commit
ab546df14d
29
.env
29
.env
@ -19,7 +19,7 @@ APP_ENV=dev
|
||||
APP_SECRET=EDIT_ME
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
APP_VERSION=1.8.4
|
||||
APP_VERSION=1.9.0
|
||||
|
||||
APP_NAME=KevaChat
|
||||
|
||||
@ -101,4 +101,29 @@ APP_ADD_POST_REMOTE_IP_DENIED=
|
||||
APP_ADD_POST_KEY_REGEX=/^([\d]+)@([A-z0-9\.\:\[\]]+)$/
|
||||
|
||||
# Post content rules (for kevacoin value, max length is 3072)
|
||||
APP_ADD_POST_VALUE_REGEX=/.*/ui
|
||||
APP_ADD_POST_VALUE_REGEX=/.*/ui
|
||||
|
||||
# Post cost (set 0 for free publications)
|
||||
APP_ADD_POST_COST_KVA=1
|
||||
|
||||
# Room cost (set 0 for free publications)
|
||||
APP_ADD_ROOM_COST_KVA=100
|
||||
|
||||
# User cost (set 0 for free registration)
|
||||
APP_ADD_USER_COST_KVA=100
|
||||
|
||||
# Quantity of payment confirmations to send message to blockchain
|
||||
APP_POOL_CONFIRMATIONS=1
|
||||
|
||||
# Cleanup abandoned messages without payment after seconds timeout
|
||||
APP_POOL_TIMEOUT=3600
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||
#
|
||||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
20
README.md
20
README.md
@ -12,7 +12,7 @@ Instance require connection to the [Kevacoin](https://github.com/kevacoin-projec
|
||||
|
||||
KevaChat following open wallet model, where community boost shared ballance for talks.
|
||||
|
||||
* In another way, node administrators able to provide unique payment addresses to each message sent and charge commission for instance monetization. Currently, this model not implemented because of about zero conversation cost in KevaCoin network.
|
||||
* In another way, node administrators able to provide unique payment addresses to each message sent and charge commission for instance monetization.
|
||||
|
||||
Administrators have flexible settings of access levels explained in the `.env` file: read-only rooms, connection and post limits, etc.
|
||||
|
||||
@ -30,15 +30,19 @@ All messages related to their room `namespaces`.
|
||||
|
||||
## Install
|
||||
|
||||
### Production
|
||||
* `git clone https://github.com/kevachat/webapp.git`
|
||||
* `cd webapp`
|
||||
* `composer update`
|
||||
* `php bin/console doctrine:schema:update --force`
|
||||
* `* * * * * php crontab/pool`
|
||||
|
||||
`composer create-project kevachat/webapp KevaChat`
|
||||
## Update
|
||||
|
||||
### Development
|
||||
|
||||
* `git clone https://github.com/kevachat/webapp.git KevaChat`
|
||||
* `cd KevaChat`
|
||||
* `composer install`
|
||||
`cd webapp`
|
||||
`git pull`
|
||||
`composer update`
|
||||
`php bin/console doctrine:migrations:migrate`
|
||||
`APP_ENV=prod APP_DEBUG=0 php bin/console cache:clear`
|
||||
|
||||
## Setup
|
||||
|
||||
|
8
compose.override.yaml
Normal file
8
compose.override.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
21
compose.yaml
Normal file
21
compose.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: postgres:${POSTGRES_VERSION:-16}-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||
# You should definitely change the password in production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
volumes:
|
||||
- database_data:/var/lib/postgresql/data:rw
|
||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
volumes:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database_data:
|
||||
###< doctrine/doctrine-bundle ###
|
@ -10,8 +10,11 @@
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"clitor-is-protocol/kevacoin": "^1.0",
|
||||
"doctrine/doctrine-bundle": "^2.11",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||
"doctrine/orm": "^3.0",
|
||||
"jdenticon/jdenticon": "^1.0",
|
||||
"kevachat/kevacoin": "^1.0",
|
||||
"kevachat/kevacoin": "^1.7",
|
||||
"league/commonmark": "^2.4",
|
||||
"symfony/console": "7.0.*",
|
||||
"symfony/dotenv": "7.0.*",
|
||||
@ -23,6 +26,7 @@
|
||||
"symfony/twig-bundle": "7.0.*",
|
||||
"symfony/yaml": "7.0.*",
|
||||
"twig/extra-bundle": "^3.8",
|
||||
"twig/intl-extra": "^3.8",
|
||||
"twig/markdown-extra": "^3.8"
|
||||
},
|
||||
"config": {
|
||||
@ -73,5 +77,8 @@
|
||||
"allow-contrib": false,
|
||||
"require": "7.0.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/maker-bundle": "^1.54"
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,7 @@ return [
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
];
|
||||
|
50
config/packages/doctrine.yaml
Normal file
50
config/packages/doctrine.yaml
Normal file
@ -0,0 +1,50 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
use_savepoints: true
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
6
config/packages/doctrine_migrations.yaml
Normal file
6
config/packages/doctrine_migrations.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
@ -36,6 +36,11 @@ parameters:
|
||||
app.add.post.remote.ip.denied: '%env(APP_ADD_POST_REMOTE_IP_DENIED)%'
|
||||
app.add.post.key.regex: '%env(APP_ADD_POST_KEY_REGEX)%'
|
||||
app.add.post.value.regex: '%env(APP_ADD_POST_VALUE_REGEX)%'
|
||||
app.add.post.cost.kva: '%env(APP_ADD_POST_COST_KVA)%'
|
||||
app.add.room.cost.kva: '%env(APP_ADD_ROOM_COST_KVA)%'
|
||||
app.add.user.cost.kva: '%env(APP_ADD_USER_COST_KVA)%'
|
||||
app.pool.confirmations: '%env(APP_POOL_CONFIRMATIONS)%'
|
||||
app.pool.timeout: '%env(APP_POOL_TIMEOUT)%'
|
||||
app.moderator.remote.ip: '%env(APP_MODERATOR_REMOTE_IP)%'
|
||||
|
||||
services:
|
||||
|
0
migrations/.gitignore
vendored
Normal file
0
migrations/.gitignore
vendored
Normal file
@ -44,6 +44,13 @@ body
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
form > span
|
||||
{
|
||||
float: right;
|
||||
line-height: 24px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* header */
|
||||
|
||||
header
|
||||
|
141
src/Controller/CrontabController.php
Normal file
141
src/Controller/CrontabController.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\Entity\Pool;
|
||||
|
||||
class CrontabController extends AbstractController
|
||||
{
|
||||
#[Route(
|
||||
'/crontab/pool',
|
||||
name: 'crontab_pool',
|
||||
methods:
|
||||
[
|
||||
'GET'
|
||||
]
|
||||
)]
|
||||
public function pool(
|
||||
Request $request,
|
||||
EntityManagerInterface $entity
|
||||
): Response
|
||||
{
|
||||
// Connect kevacoin
|
||||
$client = new \Kevachat\Kevacoin\Client(
|
||||
$this->getParameter('app.kevacoin.protocol'),
|
||||
$this->getParameter('app.kevacoin.host'),
|
||||
$this->getParameter('app.kevacoin.port'),
|
||||
$this->getParameter('app.kevacoin.username'),
|
||||
$this->getParameter('app.kevacoin.password')
|
||||
);
|
||||
|
||||
// Get room list
|
||||
$rooms = [];
|
||||
|
||||
foreach ((array) $client->kevaListNamespaces() as $value)
|
||||
{
|
||||
$rooms[$value['namespaceId']] = mb_strtolower($value['displayName']);
|
||||
}
|
||||
|
||||
// Skip room lock events
|
||||
if (empty($rooms))
|
||||
{
|
||||
return new Response(); // @TODO
|
||||
}
|
||||
|
||||
// Get pending from payment pool
|
||||
foreach ($entity->getRepository(Pool::class)->findBy(
|
||||
[
|
||||
'sent' => 0,
|
||||
'expired' => 0
|
||||
]
|
||||
) as $pool)
|
||||
{
|
||||
// Payment received, send to blockchain
|
||||
if ($client->getReceivedByAddress($pool->getAddress(), $this->getParameter('app.pool.confirmations')) >= $pool->getCost())
|
||||
{
|
||||
// Check physical wallet balance
|
||||
if ($client->getBalance() <= $pool->getCost())
|
||||
{
|
||||
break; // @TODO exception
|
||||
}
|
||||
|
||||
// Is room request
|
||||
else if ('_KEVA_NS_' == $pool->getKey())
|
||||
{
|
||||
// Check room name not taken
|
||||
if (in_array(mb_strtolower($pool->getValue()), $rooms))
|
||||
{
|
||||
continue; // @TODO exception
|
||||
}
|
||||
|
||||
// Create new room record
|
||||
if ($client->kevaNamespace($pool->getValue()))
|
||||
{
|
||||
// Update status
|
||||
$pool->setSent(
|
||||
time()
|
||||
);
|
||||
|
||||
$entity->persist(
|
||||
$pool
|
||||
);
|
||||
|
||||
$entity->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Is regular key/value request
|
||||
else
|
||||
{
|
||||
// Check namespace is valid
|
||||
if (!isset($rooms[$pool->getNamespace()]))
|
||||
{
|
||||
continue; // @TODO exception
|
||||
}
|
||||
|
||||
if ($client->kevaPut($pool->getNamespace(), $pool->getKey(), $pool->getValue()))
|
||||
{
|
||||
// Update status
|
||||
$pool->setSent(
|
||||
time()
|
||||
);
|
||||
|
||||
$entity->persist(
|
||||
$pool
|
||||
);
|
||||
|
||||
$entity->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record expired
|
||||
else
|
||||
{
|
||||
if ($pool->getTime() + $this->getParameter('app.pool.timeout') >= time())
|
||||
{
|
||||
// Update status
|
||||
$pool->setExpired(
|
||||
time()
|
||||
);
|
||||
|
||||
$entity->persist(
|
||||
$pool
|
||||
);
|
||||
|
||||
$entity->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(); // @TODO
|
||||
}
|
||||
}
|
@ -204,6 +204,7 @@ class ModuleController extends AbstractController
|
||||
'sign' => $sign,
|
||||
'message' => $message,
|
||||
'username' => $username,
|
||||
'cost' => $this->getParameter('app.add.post.cost.kva'),
|
||||
'enabled' =>
|
||||
(
|
||||
!in_array(
|
||||
@ -224,7 +225,8 @@ class ModuleController extends AbstractController
|
||||
return $this->render(
|
||||
'default/module/room.html.twig',
|
||||
[
|
||||
'request' => $request
|
||||
'request' => $request,
|
||||
'cost' => $this->getParameter('app.add.room.cost.kva')
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\Entity\Pool;
|
||||
|
||||
class RoomController extends AbstractController
|
||||
{
|
||||
#[Route(
|
||||
@ -160,7 +164,8 @@ class RoomController extends AbstractController
|
||||
]
|
||||
)]
|
||||
public function room(
|
||||
Request $request
|
||||
Request $request,
|
||||
EntityManagerInterface $entity
|
||||
): Response
|
||||
{
|
||||
// Connect kevacoin
|
||||
@ -175,6 +180,54 @@ class RoomController extends AbstractController
|
||||
// Get room feed
|
||||
$feed = [];
|
||||
|
||||
// Get pending from payment pool
|
||||
foreach ($entity->getRepository(Pool::class)->findBy(
|
||||
[
|
||||
'namespace' => $request->get('namespace'),
|
||||
'sent' => 0,
|
||||
'expired' => 0
|
||||
]
|
||||
) as $pending)
|
||||
{
|
||||
// Require valid kevachat meta
|
||||
if ($data = $this->_post(
|
||||
[
|
||||
'key' => $pending->getKey(),
|
||||
'value' => $pending->getValue(),
|
||||
'txid' => hash( // @TODO tmp solution as required for tree building
|
||||
'sha256',
|
||||
rand()
|
||||
)
|
||||
]
|
||||
))
|
||||
{
|
||||
// Detect parent post
|
||||
preg_match('/^@([A-z0-9]{64})\s/i', $data->message, $mention);
|
||||
$feed[$data->id] =
|
||||
[
|
||||
'id' => $data->id,
|
||||
'user' => $data->user,
|
||||
'icon' => $data->icon,
|
||||
'time' => $data->time,
|
||||
'parent' => isset($mention[1]) ? $mention[1] : null,
|
||||
'message' => trim(
|
||||
preg_replace( // remove mention from folded message
|
||||
'/^@([A-z0-9]{64})\s/i',
|
||||
'',
|
||||
$data->message
|
||||
)
|
||||
),
|
||||
'pending' => true,
|
||||
'pool' =>
|
||||
[
|
||||
'cost' => $pending->getCost(),
|
||||
'address' => $pending->getAddress(),
|
||||
'expires' => $pending->getTime() + $this->getParameter('app.pool.timeout')
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Get pending paradise
|
||||
foreach ((array) $client->kevaPending() as $pending) // @TODO relate to this room
|
||||
{
|
||||
@ -318,7 +371,8 @@ class RoomController extends AbstractController
|
||||
)]
|
||||
public function post(
|
||||
Request $request,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
EntityManagerInterface $entity
|
||||
): Response
|
||||
{
|
||||
// Check maintenance mode disabled
|
||||
@ -550,19 +604,55 @@ class RoomController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
// Send message to DHT
|
||||
if (
|
||||
$client->kevaPut(
|
||||
$request->get('namespace'),
|
||||
// Post has commission cost, send message to pending payment pool
|
||||
if ($this->getParameter('app.add.post.cost.kva') > 0)
|
||||
{
|
||||
$time = time();
|
||||
|
||||
$pool = new Pool();
|
||||
|
||||
$pool->setTime(
|
||||
$time
|
||||
);
|
||||
|
||||
$pool->setSent(
|
||||
0
|
||||
);
|
||||
|
||||
$pool->setExpired(
|
||||
0
|
||||
);
|
||||
|
||||
$pool->setCost(
|
||||
$this->getParameter('app.add.post.cost.kva')
|
||||
);
|
||||
|
||||
$pool->setAddress(
|
||||
$client->getNewAddress()
|
||||
);
|
||||
|
||||
$pool->setNamespace(
|
||||
$request->get('namespace')
|
||||
);
|
||||
|
||||
$pool->setKey(
|
||||
sprintf(
|
||||
'%s@%s',
|
||||
time(), // @TODO save timestamp as part of key to keep timing actual for the chat feature
|
||||
$time,
|
||||
$username
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$pool->setValue(
|
||||
$request->get('message')
|
||||
)
|
||||
)
|
||||
{
|
||||
);
|
||||
|
||||
$entity->persist(
|
||||
$pool
|
||||
);
|
||||
|
||||
$entity->flush();
|
||||
|
||||
// Register event time
|
||||
$memcached->set(
|
||||
$memory,
|
||||
@ -584,6 +674,43 @@ class RoomController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
// Post has zero cost, send message to DHT
|
||||
else
|
||||
{
|
||||
if (
|
||||
$client->kevaPut(
|
||||
$request->get('namespace'),
|
||||
sprintf(
|
||||
'%s@%s',
|
||||
time(), // @TODO save timestamp as part of key to keep timing actual for the chat feature
|
||||
$username
|
||||
),
|
||||
$request->get('message')
|
||||
)
|
||||
)
|
||||
{
|
||||
// Register event time
|
||||
$memcached->set(
|
||||
$memory,
|
||||
time(),
|
||||
(int) $this->getParameter('app.add.post.remote.ip.delay') // auto remove on cache expire
|
||||
);
|
||||
|
||||
// Redirect back to room
|
||||
return $this->redirectToRoute(
|
||||
'room_namespace',
|
||||
[
|
||||
'mode' => $request->get('mode'),
|
||||
'namespace' => $request->get('namespace'),
|
||||
'sign' => $request->get('sign'),
|
||||
'error' => null,
|
||||
'message' => null,
|
||||
'_fragment' => 'latest'
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Something went wrong, return error message
|
||||
return $this->redirectToRoute(
|
||||
'room_namespace',
|
||||
@ -608,7 +735,8 @@ class RoomController extends AbstractController
|
||||
)]
|
||||
public function add(
|
||||
Request $request,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
EntityManagerInterface $entity
|
||||
): Response
|
||||
{
|
||||
// Check maintenance mode disabled
|
||||
@ -782,6 +910,80 @@ class RoomController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
// Room registration has commission cost, send to pending payment pool
|
||||
if ($this->getParameter('app.add.room.cost.kva') > 0)
|
||||
{
|
||||
if ($address = $client->getNewAddress())
|
||||
{
|
||||
$time = time();
|
||||
|
||||
$pool = new Pool();
|
||||
|
||||
$pool->setTime(
|
||||
$time
|
||||
);
|
||||
|
||||
$pool->setSent(
|
||||
0
|
||||
);
|
||||
|
||||
$pool->setExpired(
|
||||
0
|
||||
);
|
||||
|
||||
$pool->setCost(
|
||||
$this->getParameter('app.add.room.cost.kva')
|
||||
);
|
||||
|
||||
$pool->setAddress(
|
||||
$address
|
||||
);
|
||||
|
||||
$pool->setNamespace(
|
||||
''
|
||||
);
|
||||
|
||||
$pool->setKey(
|
||||
'_KEVA_NS_'
|
||||
);
|
||||
|
||||
$pool->setValue(
|
||||
$name
|
||||
);
|
||||
|
||||
$entity->persist(
|
||||
$pool
|
||||
);
|
||||
|
||||
$entity->flush();
|
||||
|
||||
// Redirect back to room
|
||||
return $this->redirectToRoute(
|
||||
'room_list',
|
||||
[
|
||||
'mode' => $request->get('mode'),
|
||||
'name' => $name,
|
||||
'warning' => sprintf(
|
||||
$translator->trans('To complete, send %s KVA to %s'),
|
||||
$this->getParameter('app.add.room.cost.kva'),
|
||||
$address
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return $this->redirectToRoute(
|
||||
'room_list',
|
||||
[
|
||||
'username' => $request->get('username'),
|
||||
'error' => $translator->trans('Could not init registration address!')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to DHT
|
||||
if ($namespace = $client->kevaNamespace($name))
|
||||
{
|
||||
|
@ -9,6 +9,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\Entity\Pool;
|
||||
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
private $_algorithm = PASSWORD_BCRYPT;
|
||||
@ -167,7 +171,8 @@ class UserController extends AbstractController
|
||||
return $this->render(
|
||||
'default/user/join.html.twig',
|
||||
[
|
||||
'request' => $request
|
||||
'request' => $request,
|
||||
'cost' => $this->getParameter('app.add.user.cost.kva')
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -247,7 +252,8 @@ class UserController extends AbstractController
|
||||
)]
|
||||
public function add(
|
||||
Request $request,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
EntityManagerInterface $entity
|
||||
): Response
|
||||
{
|
||||
// Check maintenance mode disabled
|
||||
@ -442,6 +448,79 @@ class UserController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
// User registration has commission cost, send message to pending payment pool
|
||||
if ($this->getParameter('app.add.user.cost.kva') > 0)
|
||||
{
|
||||
if ($address = $client->getNewAddress())
|
||||
{
|
||||
$time = time();
|
||||
|
||||
$pool = new Pool();
|
||||
|
||||
$pool->setTime(
|
||||
$time
|
||||
);
|
||||
|
||||
$pool->setSent(
|
||||
0
|
||||
);
|
||||
|
||||
$pool->setExpired(
|
||||
0
|
||||
);
|
||||
|
||||
$pool->setCost(
|
||||
$this->getParameter('app.add.user.cost.kva')
|
||||
);
|
||||
|
||||
$pool->setAddress(
|
||||
$address
|
||||
);
|
||||
|
||||
$pool->setNamespace(
|
||||
$namespace
|
||||
);
|
||||
|
||||
$pool->setKey(
|
||||
$username
|
||||
);
|
||||
|
||||
$pool->setValue(
|
||||
$hash
|
||||
);
|
||||
|
||||
$entity->persist(
|
||||
$pool
|
||||
);
|
||||
|
||||
$entity->flush();
|
||||
|
||||
// Redirect back to room
|
||||
return $this->redirectToRoute(
|
||||
'user_add',
|
||||
[
|
||||
'username' => $request->get('username'),
|
||||
'warning' => sprintf(
|
||||
$translator->trans('To complete registration, send %s KVA to %s'),
|
||||
$this->getParameter('app.add.user.cost.kva'),
|
||||
$address
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return $this->redirectToRoute(
|
||||
'user_add',
|
||||
[
|
||||
'username' => $request->get('username'),
|
||||
'error' => $translator->trans('Could not init registration address!')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Auth success, add user to DB
|
||||
if (!$this->_add($client, $namespace, $username, $hash))
|
||||
{
|
||||
|
0
src/Entity/.gitignore
vendored
Normal file
0
src/Entity/.gitignore
vendored
Normal file
141
src/Entity/Pool.php
Normal file
141
src/Entity/Pool.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\PoolRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PoolRepository::class)]
|
||||
class Pool
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $time = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $sent = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $expired = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $cost = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $address = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $namespace = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $key = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $value = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTime(): ?int
|
||||
{
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
public function setTime(int $time): static
|
||||
{
|
||||
$this->time = $time;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSent(): ?int
|
||||
{
|
||||
return $this->sent;
|
||||
}
|
||||
|
||||
public function setSent(int $sent): static
|
||||
{
|
||||
$this->sent = $sent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExpired(): ?int
|
||||
{
|
||||
return $this->expired;
|
||||
}
|
||||
|
||||
public function setExpired(int $expired): static
|
||||
{
|
||||
$this->expired = $expired;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCost(): ?float
|
||||
{
|
||||
return $this->cost;
|
||||
}
|
||||
|
||||
public function setCost(float $cost): static
|
||||
{
|
||||
$this->cost = $cost;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddress(): ?string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function setAddress(string $address): static
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNamespace(): ?string
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function setNamespace(string $namespace): static
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKey(): ?string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setKey(string $key): static
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
0
src/Repository/.gitignore
vendored
Normal file
0
src/Repository/.gitignore
vendored
Normal file
23
src/Repository/PoolRepository.php
Normal file
23
src/Repository/PoolRepository.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Pool;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Pool>
|
||||
*
|
||||
* @method Pool|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Pool|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Pool[] findAll()
|
||||
* @method Pool[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class PoolRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Pool::class);
|
||||
}
|
||||
}
|
@ -31,6 +31,13 @@ class AppExtension extends AbstractExtension
|
||||
'formatAgo'
|
||||
]
|
||||
),
|
||||
new TwigFilter(
|
||||
'format_expire',
|
||||
[
|
||||
$this,
|
||||
'formatExpire'
|
||||
]
|
||||
),
|
||||
new TwigFilter(
|
||||
'format_bytes',
|
||||
[
|
||||
@ -147,6 +154,77 @@ class AppExtension extends AbstractExtension
|
||||
}
|
||||
}
|
||||
|
||||
public function formatExpire(
|
||||
int $time,
|
||||
): string
|
||||
{
|
||||
$diff = $time - time();
|
||||
|
||||
if ($diff < 1)
|
||||
{
|
||||
return $this->translator->trans('expired');
|
||||
}
|
||||
|
||||
$values =
|
||||
[
|
||||
365 * 24 * 60 * 60 =>
|
||||
[
|
||||
$this->translator->trans('year to expire'),
|
||||
$this->translator->trans('years to expire'),
|
||||
$this->translator->trans(' years to expire')
|
||||
],
|
||||
30 * 24 * 60 * 60 =>
|
||||
[
|
||||
$this->translator->trans('month to expire'),
|
||||
$this->translator->trans('months to expire'),
|
||||
$this->translator->trans(' months to expire')
|
||||
],
|
||||
24 * 60 * 60 =>
|
||||
[
|
||||
$this->translator->trans('day to expire'),
|
||||
$this->translator->trans('days to expire'),
|
||||
$this->translator->trans(' days to expire')
|
||||
],
|
||||
60 * 60 =>
|
||||
[
|
||||
$this->translator->trans('hour to expire'),
|
||||
$this->translator->trans('hours to expire'),
|
||||
$this->translator->trans(' hours to expire')
|
||||
],
|
||||
60 =>
|
||||
[
|
||||
$this->translator->trans('minute to expire'),
|
||||
$this->translator->trans('minutes to expire'),
|
||||
$this->translator->trans(' minutes to expire')
|
||||
],
|
||||
1 =>
|
||||
[
|
||||
$this->translator->trans('second to expire'),
|
||||
$this->translator->trans('seconds to expire'),
|
||||
$this->translator->trans(' seconds to expire')
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($values as $key => $value)
|
||||
{
|
||||
$result = $diff / $key;
|
||||
|
||||
if ($result >= 1)
|
||||
{
|
||||
$round = round($result);
|
||||
|
||||
return sprintf(
|
||||
'%s %s',
|
||||
$round,
|
||||
$this->_plural(
|
||||
$round,
|
||||
$value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function formatBytes(
|
||||
int $bytes,
|
||||
int $precision = 2
|
||||
|
36
symfony.lock
36
symfony.lock
@ -1,4 +1,31 @@
|
||||
{
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.11",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.10",
|
||||
"ref": "c170ded8fc587d6bd670550c43dafcf093762245"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine.yaml",
|
||||
"src/Entity/.gitignore",
|
||||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-migrations-bundle": {
|
||||
"version": "3.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.1",
|
||||
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine_migrations.yaml",
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
@ -42,6 +69,15 @@
|
||||
"src/Kernel.php"
|
||||
]
|
||||
},
|
||||
"symfony/maker-bundle": {
|
||||
"version": "1.54",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/monolog-bundle": {
|
||||
"version": "3.10",
|
||||
"recipe": {
|
||||
|
@ -28,5 +28,8 @@
|
||||
<a href="{{ path('user_login') }}">{{ 'login' | trans }}</a>
|
||||
{% endif %}
|
||||
<button type="submit">{{ 'send' | trans }}</button>
|
||||
{% if cost %}
|
||||
<span>{{ 'cost: %s KVA' | format(cost) | trans }}</span>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
@ -1,7 +1,13 @@
|
||||
<form name="room" action="{{ path('room_add', { mode : request.get('mode') }) }}" method="post">
|
||||
{% if request.get('error') %}
|
||||
<output name="error" for="form-room-name">{{ request.get('error') }}</output>
|
||||
{% endif %}
|
||||
<input type="text" name="name" id="form-room-name" value="{{ request.get('name') }}" placeholder="{{ 'enter new room name...' | trans }}" />
|
||||
<button type="submit">{{ 'add' | trans }}</button>
|
||||
</form>
|
||||
<form name="room" action="{{ path('room_add', { mode : request.get('mode') }) }}" method="post">
|
||||
{% if request.get('error') %}
|
||||
<output name="error" for="form-room-name">{{ request.get('error') }}</output>
|
||||
{% endif %}
|
||||
{% if request.get('warning') %}
|
||||
<output name="warning" for="form-room-name">{{ request.get('warning') }}</output>
|
||||
{% endif %}
|
||||
<input type="text" name="name" id="form-room-name" value="{{ request.get('name') }}" placeholder="{{ 'enter new room name...' | trans }}" />
|
||||
<button type="submit">{{ 'add' | trans }}</button>
|
||||
{% if cost %}
|
||||
<span>{{ 'cost: %s KVA' | format(cost) | trans }}</span>
|
||||
{% endif %}
|
||||
</form>
|
@ -17,15 +17,21 @@
|
||||
</strong>
|
||||
{% endif %}
|
||||
•
|
||||
<a rel="nofollow" href="{{ path('room_namespace', { mode : mode, namespace : namespace, _fragment : post.id }) }}" title="{{ post.time | date('c') }}">{{ post.time | format_ago }}</a>
|
||||
•
|
||||
<a rel="nofollow" href="{{ path('room_namespace', { mode : mode, namespace : namespace, txid : post.id, _fragment : post.id }) }}">{{ 'reply' | trans }}</a>
|
||||
{% if post.pending %}
|
||||
{{ post.time | format_ago }}
|
||||
{% if post.pool %}
|
||||
•
|
||||
{{ 'pending %s KVA to %s (%s)' | trans | format(post.pool.cost, post.pool.address, post.pool.expires | format_expire ) }}
|
||||
{% endif %}
|
||||
<span title="{{ 'pending in pool' | trans }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71z"/>
|
||||
</svg>
|
||||
</span>
|
||||
{% else %}
|
||||
<a rel="nofollow" href="{{ path('room_namespace', { mode : mode, namespace : namespace, _fragment : post.id }) }}" title="{{ post.time | date('c') }}">{{ post.time | format_ago }}</a>
|
||||
•
|
||||
<a rel="nofollow" href="{{ path('room_namespace', { mode : mode, namespace : namespace, txid : post.id, _fragment : post.id }) }}">{{ 'reply' | trans }}</a>
|
||||
{% endif %}
|
||||
{# apply markdown whitelist filters only to prevent ping from remote includes #}
|
||||
{{
|
||||
|
@ -5,6 +5,9 @@
|
||||
{% if request.get('error') %}
|
||||
<output name="error">{{ request.get('error') }}</output>
|
||||
{% endif %}
|
||||
{% if request.get('warning') %}
|
||||
<output name="warning">{{ request.get('warning') }}</output>
|
||||
{% endif %}
|
||||
<label for="username">{{ 'Username' | trans }}</label>
|
||||
<input type="text" name="username" id="username" value="{{ request.get('username') }}" placeholder="{{ 'Your public identity for this instance, permanently stored in blockchain' | trans }}" />
|
||||
<label for="password">{{ 'Password' | trans }}</label>
|
||||
@ -13,5 +16,8 @@
|
||||
<input type="password" name="repeat" id="repeat" value="" placeholder="{{ 'Make sure your password is correct, you can not reset it later!' | trans }}" />
|
||||
<a href="{{ path('user_login') }}">{{ 'I have account' | trans }}</a>
|
||||
<button type="submit">{{ 'join' | trans }}</button>
|
||||
{% if cost %}
|
||||
<span>{{ 'cost: %s KVA' | format(cost) | trans }}</span>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user