This commit is contained in:
n1rwana 2022-08-23 02:40:11 +03:00
parent b318e184e7
commit 7e5a114064
11 changed files with 598 additions and 28 deletions

View file

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\{RowModel};
use openvk\Web\Models\Entities\{User, BugtrackerProduct};
use openvk\Web\Models\Repositories\{Users, BugtrackerProducts};
class BugtrackerPrivateProduct extends BugtrackerProduct
{
protected $tableName = "bt_products_access";
function toProduct(): ?BugtrackerProduct
{
return (new BugtrackerProducts)->get($this->getId());
}
function getName(): string
{
return $this->toProduct()->getName();
}
function isClosed(): ?bool
{
return $this->toProduct()->isClosed();
}
function getCreator(): ?User
{
return $this->toProduct()->getCreator();
}
function isPrivate(): ?bool
{
return true;
}
function getModerator(): ?User
{
return (new Users)->get($this->getRecord("moderator"));
}
}

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\{RowModel};
use openvk\Web\Models\Entities\{User};
use openvk\Web\Models\Repositories\{Users};
@ -43,4 +44,25 @@ class BugtrackerProduct extends RowModel
{
return new DateTime($this->getRecord()->created);
}
function isPrivate(): ?bool
{
return (bool) $this->getRecord()->private;
}
function hasAccess(User $user): bool
{
if ($user->isBtModerator() || !$this->isPrivate())
return true;
$check = DB::i()->getContext()->table("bt_products_access")->where([
"tester" => $user->getId(),
"product" => $this->getId()
]);
if (sizeof($check) > 0)
return true;
return false;
}
}

View file

@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\{BugtrackerProduct, BugtrackerPrivateProduct, User};
use openvk\Web\Models\Repositories\BugtrackerProducts;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class BugtrackerPrivateProducts
{
private $context;
private $private_products;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->private_products = $this->context->table("bt_products_access");
}
private function toPrivateProduct(?ActiveRow $ar)
{
return is_null($ar) ? NULL : new BugtrackerPrivateProduct($ar);
}
function get(int $id): ?BugtrackerPrivateProduct
{
return $this->toPrivateProduct($this->private_products->get($id));
}
function getAll(int $page = 1): \Traversable
{
$products = $this->private_products
->order("created DESC")
->page($page, 5);
foreach($products as $product)
yield (new BugtrackerProducts)->get($product->product);
}
function getForUser(User $user, int $page = 1): \Traversable
{
$products = $this->private_products
->where(["tester" => $user->getId()])
->order("id ASC")
->page($page, 5);
foreach($products as $product)
yield (new BugtrackerProducts)->get($product->product);
}
function getCount(User $user): ?int
{
if ($user->isBtModerator())
return sizeof($this->getAll());
return sizeof($this->private_products->where(["tester" => $user->getId()]));
}
}

View file

@ -1,6 +1,8 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\{BugtrackerProduct};
use openvk\Web\Models\Entities\{BugtrackerProduct, BugtrackerPrivateProduct, User};
use openvk\Web\Models\Repositories\{BugtrackerPrivateProducts};
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@ -8,6 +10,7 @@ class BugtrackerProducts
{
private $context;
private $products;
private $private_products;
function __construct()
{
@ -27,18 +30,106 @@ class BugtrackerProducts
function getAll(int $page = 1): \Traversable
{
foreach($this->products->order("id ASC")->page($page, 5) as $product)
$products = $this->products
->where(["private" => 0])
->order("created DESC")
->page($page, 5);
foreach($products as $product)
yield new BugtrackerProduct($product);
}
function getOpen(int $page = 1): \Traversable
function getOpen(int $page = 1, bool $private = FALSE): \Traversable
{
foreach($this->products->where(["closed" => 0])->order("id ASC")->page($page, 5) as $product)
$products = $this->products
->where([
"closed" => 0,
"private" => $private
])
->order("id ASC")
->page($page, 5);
foreach($products as $product)
yield new BugtrackerProduct($product);
}
function getCount(): ?int
function getClosed(int $page = 1, bool $private = FALSE): \Traversable
{
return sizeof($this->products->where(["closed" => 0]));
$products = $this->products
->where([
"closed" => 1,
"private" => $private
])
->order("id ASC")
->page($page, 5);
foreach($products as $product)
yield new BugtrackerProduct($product);
}
function getPrivate(int $page = 1): \Traversable
{
$products = $this->products
->where([
"private" => 1
])
->order("id ASC")
->page($page, 5);
foreach($products as $product)
yield new BugtrackerProduct($product);
}
function getPrivateForUser(User $user, int $page = 1): \Traversable
{
if (!$user->isBtModerator()) {
return (new BugtrackerPrivateProducts)->getForUser($user, $page);
} else {
return (new BugtrackerPrivateProducts)->getAll($page);
}
}
function getFiltered(User $tester, string $type = "all", int $page = 1): \Traversable
{
switch ($type) {
case 'open':
return $this->getOpen($page);
break;
case 'closed':
return $this->getClosed($page);
break;
case 'private':
if ($tester->isBtModerator())
return $this->getPrivate($page);
return (new BugtrackerPrivateProducts)->getForUser($tester, $page);
break;
default:
return $this->getAll($page);
break;
}
}
function getCount(string $filter = "all", User $user = NULL): ?int
{
switch ($filter) {
case 'open':
return sizeof($this->products->where(["closed" => 0, "private" => 0]));
break;
case 'closed':
return sizeof($this->products->where(["closed" => 1, "private" => 0]));
case 'private':
return (new BugtrackerPrivateProducts)->getCount($user);
break;
default:
return sizeof($this->products->where(["private" => 0]));
break;
}
}
}

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\{BugReport};
use openvk\Web\Models\Entities\{BugReport, User};
use openvk\Web\Models\Repositories\{BugtrackerProducts};
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@ -26,18 +27,24 @@ class BugtrackerReports
return $this->toReport($this->reports->get($id));
}
function getAllReports(int $page = 1): \Traversable
function getAllReports(User $user, int $page = 1): \Traversable
{
foreach($this->reports->where(["deleted" => NULL])->order("created DESC")->page($page, 5) as $report)
$reports = $this->reports->where(["deleted" => NULL])->order("created DESC")->page($page, 5);
foreach($reports as $report)
yield new BugReport($report);
}
function getReports(int $product_id = 0, int $priority = 0, int $page = 1): \Traversable
function getReports(int $product_id = 0, int $priority = 0, int $page = 1, User $user = NULL): \Traversable
{
$filter = ["deleted" => NULL];
$product_id && $filter["product_id"] = $product_id;
$priority && $filter["priority"] = $priority;
$product = (new BugtrackerProducts)->get($product_id);
if (!$product->hasAccess($user))
return false;
foreach($this->reports->where($filter)->order("created DESC")->page($page, 5) as $report)
yield new BugReport($report);
}

View file

@ -43,15 +43,16 @@ final class BugtrackerPresenter extends OpenVKPresenter
break;
case 'products':
$this->template->count = $this->products->getCount();
$this->template->iterator = $this->products->getAll($this->template->page);
$this->template->filter = $this->queryParam("filter") ?? "all";
$this->template->count = $this->products->getCount($this->template->filter, $this->user->identity);
$this->template->iterator = $this->products->getFiltered($this->user->identity, $this->template->filter, $this->template->page);
break;
default:
$this->template->count = $this->reports->getReportsCount((int) $this->queryParam("product"), (int) $this->queryParam("priority"));
$this->template->count = $this->reports->getReportsCount((int) $this->queryParam("product"), (int) $this->queryParam("priority"), $this->user->identity);
$this->template->iterator = $this->queryParam("product")
? $this->reports->getReports((int) $this->queryParam("product"), (int) $this->queryParam("priority"), $this->template->page)
: $this->reports->getAllReports($this->template->page);
? $this->reports->getReports((int) $this->queryParam("product"), (int) $this->queryParam("priority"), $this->template->page, $this->user->identity)
: $this->reports->getAllReports($this->user->identity, $this->template->page);
break;
}
@ -64,6 +65,9 @@ final class BugtrackerPresenter extends OpenVKPresenter
$this->template->user = $this->user;
if (!$this->reports->get($id)->getProduct()->hasAccess($this->template->user->identity))
$this->flashFail("err", tr("forbidden"));
if ($this->reports->get($id)) {
$this->template->bug = $this->reports->get($id);
$this->template->reporter = $this->template->bug->getReporter();
@ -203,19 +207,21 @@ final class BugtrackerPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$moder = $this->user->identity->isBtModerator();
if (!$moder)
if (!$this->user->identity->isBtModerator())
$this->flashFail("err", tr("forbidden"));
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_closed = (bool) $this->postParam("is_closed");
$is_private = (bool) $this->postParam("is_private");
DB::i()->getContext()->table("bt_products")->insert([
"creator_id" => $this->user->identity->getId(),
"title" => $title,
"description" => $description,
"created" => time()
"created" => time(),
"closed" => $is_closed,
"private" => $is_private
]);
$this->redirect("/bugtracker?act=products");
@ -235,4 +241,102 @@ final class BugtrackerPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("bug_tracker_success"), tr("bug_tracker_reproduced_text"));
}
function renderManageAccess(int $product_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if (!$this->user->identity->isBtModerator())
$this->flashFail("err", tr("forbidden"));
$user = (new Users)->get((int) $this->postParam("uid"));
$product = $this->products->get($product_id);
$action = $this->postParam("action");
if ($action === "give") {
if (!$product->isPrivate() || $product->hasAccess($user))
$this->flashFail("err", "Ошибка", $user->getCanonicalName() . " уже имеет доступ к продукту " . $product->getCanonicalName());
DB::i()->getContext()->table("bt_products_access")->insert([
"created" => time(),
"tester" => $user->getId(),
"product" => $product_id,
"moderator" => $this->user->identity->getId()
]);
$this->flashFail("succ", "Успех", $user->getCanonicalName() . " теперь имеет доступ к продукту " . $product->getCanonicalName());
} else {
if ($user->isBtModerator())
$this->flashFail("err", "Ошибка", "Невозможно забрать доступ к продукту у модератора.");
if (!$product->hasAccess($user))
$this->flashFail("err", "Ошибка", $user->getCanonicalName() . " и так не имеет доступа к продукту " . $product->getCanonicalName());
DB::i()->getContext()->table("bt_products_access")->where([
"tester" => $user->getId(),
"product" => $product_id,
])->delete();
$this->flashFail("succ", "Успех", $user->getCanonicalName() . " теперь не имеет доступа к продукту " . $product->getCanonicalName());
}
}
function renderManagePrivacy(int $product_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if (!$this->user->identity->isBtModerator())
$this->flashFail("err", tr("forbidden"));
$user = (new Users)->get((int) $this->postParam("uid"));
$product = $this->products->get($product_id);
$action = $this->postParam("action");
if ($action == "open") {
if (!$product->isPrivate())
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так открытый.");
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["private" => 0]);
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь открытый.");
} else {
if ($product->isPrivate())
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так приватный.");
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["private" => 1]);
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь приватный.");
}
}
function renderManageStatus(int $product_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if (!$this->user->identity->isBtModerator())
$this->flashFail("err", tr("forbidden"));
$user = (new Users)->get((int) $this->postParam("uid"));
$product = $this->products->get($product_id);
$action = $this->postParam("action");
if ($action == "open") {
if (!$product->isClosed())
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так открытый.");
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["closed" => 0]);
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь открытый.");
} else {
if ($product->isClosed())
$this->flashFail("err", "Ошибка", "Продукт " . $product->getCanonicalName() . " и так закрытый.");
DB::i()->getContext()->table("bt_products")->where("id", $product_id)->update(["closed" => 1]);
$this->flashFail("succ", "Успех", "Продукт " . $product->getCanonicalName() . " теперь закрытый.");
}
}
}

View file

@ -28,7 +28,7 @@
{_create}
</a>
</div>
<div n:if='in_array($mode, ["products", "new_product"])' n:attr='id => $mode === "new_product" ? "activetabs" : false' class="tab" style="float: right;">
<div n:if='in_array($mode, ["products", "new_product"]) AND $isModerator' n:attr='id => $mode === "new_product" ? "activetabs" : false' class="tab" style="float: right;">
<a n:attr='id => $mode === "new_product" ? "act_tab_a" : false' href="/bugtracker?act=new_product">
{_create}
</a>
@ -43,20 +43,23 @@
<table border="0" style="font-size: 11px; width: 100%;" class="post">
<tbody>
<tr n:foreach="$iterator as $bug">
<td width="54" >
{var $isHidden = !$bug->getProduct()->hasAccess($user->identity)}
<td width="54">
<center>
<img src="/assets/packages/static/openvk/img/note_icon.png">
</center>
</td>
<td width="92%" valign="top">
<div class="post-author" href="#">
<a href="/bug{$bug->getId()}">
<b>{$bug->getCanonicalName()}</b>
<span>#{$bug->getId()}</span>
<a n:attr="href => !$isHidden ? '/bug' . $bug->getId() : false">
<b>
{!$isHidden ? $bug->getCanonicalName() : "Скрытый отчёт"}
</b>
<span n:if="!$isHidden">#{$bug->getId()}</span>
</a>
</div>
<div class="post-content" style="padding: 4px 0; font-size: 11px;">
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<table n:if="!$isHidden" id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td class="label"><span class="nobold">{_bug_tracker_product}:</span></td>
@ -86,6 +89,58 @@
</tr>
</tbody>
</table>
<table n:if="$isHidden" id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0" style="filter: blur(3px)">
<tbody>
<tr>
<td class="label">
<span class="nobold">{_bug_tracker_product}:</span>
</td>
<td class="data">
<a>AbcdEFGH</a>
</td>
</tr>
<tr>
<td class="label">
<span class="nobold">{_bug_tracker_sent_by}: </span>
</td>
<td class="data">
<a>John Doe</a>
</td>
</tr>
<tr>
<td class="label">
<span class="nobold">{_bug_tracker_reproduced}:</span>
</td>
<td class="data">
<a>...</a>
</td>
</tr>
<tr>
<td class="label">
<span class="nobold">{_status}:</span>
</td>
<td class="data">
<a href="#">...</a>
</td>
</tr>
<tr>
<td class="label">
<span class="nobold">{_bug_tracker_priority}:</span>
</td>
<td class="data">
<a>...</a>
</td>
</tr>
<tr>
<td class="label">
<span class="nobold">{_created}:</span>
</td>
<td class="data">
<a>...</a>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
@ -151,10 +206,36 @@
</form>
{elseif $mode === "products"}
<div>
<div n:attr='id => $filter === "all" ? "activetabs" : false' class="tab">
<a n:attr='id => $filter === "all" ? "act_tab_a" : false' href="/bugtracker?act=products">
Все
</a>
</div>
<div n:attr='id => $filter === "open" ? "activetabs" : false' class="tab">
<a n:attr='id => $filter === "open" ? "act_tab_a" : false' href="/bugtracker?act=products&filter=open">
Открытые
</a>
</div>
<div n:attr='id => $filter === "closed" ? "activetabs" : false' class="tab">
<a n:attr='id => $filter === "closed" ? "act_tab_a" : false' href="/bugtracker?act=products&filter=closed">
Закрытые
</a>
</div>
<div n:attr='id => $filter === "private" ? "activetabs" : false' class="tab">
<a n:attr='id => $filter === "private" ? "act_tab_a" : false' href="/bugtracker?act=products&filter=private">
Приватные
</a>
</div>
</div>
<br>
{if $count < 1}
{include "../components/nothing.xml"}
{/if}
<table border="0" style="font-size: 11px; width: 100%;" class="post">
<tbody>
<tr n:foreach="$iterator as $product">
<td width="54" >
<td width="54">
<center>
<img src="/assets/packages/static/openvk/img/note_icon.png">
</center>
@ -167,6 +248,10 @@
</a>
</div>
<div class="post-content" style="padding: 4px 0; font-size: 11px;">
<div n:if="$product->getDescription()" style="padding: 4px; font-size: 11px;">
{$product->getDescription()}
<hr color="#DAE1E8" size="1">
</div>
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
@ -186,6 +271,28 @@
<td class="label"><span class="nobold">{_bug_tracker_product_creation_date}:</span></td>
<td class="data"><a href="#">{$product->getCreationTime()}</a></td>
</tr>
<tr n:if="$isModerator">
<td class="label"><span class="nobold">действия:</span></td>
<td class="data" style="width: 100% !important;">
<span>
<a
onClick="showBtProductStatusDialog([{$product->getId()}, {$product->getCanonicalName()}], {$csrfToken})"
>
закрытый
</a>
<a
onClick="showBtPrivateProductDialog([{$product->getId()}, {$product->getCanonicalName()}], {$csrfToken})"
>
приватный
</a>
<a onClick="showBtProductAccessDialog([{$product->getId()}, {$product->getCanonicalName()}], {$csrfToken})">
доступ
</a>
</span>
</td>
</tr>
</tbody>
</table>
</div>
@ -208,7 +315,13 @@
<input type="text" name="title" placeholder="{_bug_tracker_product_name}">
<br><br>
<textarea placeholder="{_bug_tracker_product_description}" name="description" style="width: 100%; resize: vertical;"></textarea>
<div style="display: inline;">
<input id="is_closed" type="checkbox" name="is_closed">
<label for="is_closed">Закрытый</label>
<input id="is_private" type="checkbox" name="is_private">
<label for="is_private">Приватный</label>
</div>
<input type="hidden" name="hash" value="{$csrfToken}" />
<br><br>
<input type="submit" value="{_create}" class="button" style="float: right;" />

View file

@ -33,6 +33,7 @@ services:
- openvk\Web\Models\Repositories\Tickets
- openvk\Web\Models\Repositories\BugtrackerReports
- openvk\Web\Models\Repositories\BugtrackerProducts
- openvk\Web\Models\Repositories\BugtrackerPrivateProducts
- openvk\Web\Models\Repositories\BugtrackerComments
- openvk\Web\Models\Repositories\Messages
- openvk\Web\Models\Repositories\Restores

View file

@ -49,6 +49,12 @@ routes:
handler: "Bugtracker->createProduct"
- url: "/bug{num}/reproduce"
handler: "Bugtracker->reproduced"
- url: "/bt_product{num}/manageAccess"
handler: "Bugtracker->manageAccess"
- url: "/bt_product{num}/managePrivacy"
handler: "Bugtracker->managePrivacy"
- url: "/bt_product{num}/manageStatus"
handler: "Bugtracker->manageStatus"
- url: "/language"
handler: "About->language"
- url: "/language/{text}.js"

View file

@ -551,3 +551,109 @@ function showBtPriorityChangeDialog(report, currentBalance, hash) {
Function.noop
]);
}
function showBtGiveProductAccessDialog(product, hash) {
MessageBox("Выдать доступ", `<form action="/bt_product${product[0]}/giveAccess" method="post" id="give_product_access_dialog">
<div>
Выдать пользователю <b>ID</b>&nbsp
<input style="width: 45px; height: 9px;" type="number" name="uid" value="1" min="1">
&nbsp; доступ к продукту <b>${product[1]}</b> (#${product[0]}).
</div>
<input type="hidden" name="hash" value="${hash}" />
</form>`, ["Продолжить", tr("cancel")], [
() => {
$("#give_product_access_dialog").submit();
},
Function.noop
]);
}
function showBtRevokeProductAccessDialog(product, hash) {
MessageBox("Забрать доступ", `<form action="/bt_product${product[0]}/revokeAccess" method="post" id="revoke_product_access_dialog">
<div>
Забрать у пользователя <b>ID</b>&nbsp
<input style="width: 45px; height: 9px;" type="number" name="uid" value="1" min="1">
&nbsp; доступ к продукту <b>${product[1]}</b> (#${product[0]}).
</div>
<input type="hidden" name="hash" value="${hash}" />
</form>`, ["Продолжить", tr("cancel")], [
() => {
$("#revoke_product_access_dialog").submit();
},
Function.noop
]);
}
function showBtProductAccessDialog(product, hash) {
MessageBox(`Доступ к ${product[1]} (#${product[0]})`, `<form action="/bt_product${product[0]}/manageAccess" method="post" id="give_product_access_dialog">
<table>
<tbody>
<tr>
<td><input type="radio" name="action" value="give"></td>
<td><label for="priority_1">Выдать</label></td>
</tr>
<tr>
<td><input type="radio" name="action" value="revoke"></td>
<td><label for="priority_2">Забрать</label></td>
</tr>
</tbody>
</table>
<br>
<div>
<b>ID</b> пользователя&nbsp
<input style="width: 45px; height: 9px;" type="number" name="uid" value="1" min="1">
</div>
<input type="hidden" name="hash" value="${hash}" />
</form>`, ["Продолжить", tr("cancel")], [
() => {
$("#give_product_access_dialog").submit();
},
Function.noop
]);
}
function showBtPrivateProductDialog(product, hash) {
MessageBox(`Настройки продукта ${product[1]} (#${product[0]})`, `<form action="/bt_product${product[0]}/managePrivacy" method="post" id="give_product_access_dialog">
<table>
<tbody>
<tr>
<td><input type="radio" name="action" value="open"></td>
<td><label for="priority_1">Открытый</label></td>
</tr>
<tr>
<td><input type="radio" name="action" value="private"></td>
<td><label for="priority_2">Приватный</label></td>
</tr>
</tbody>
</table>
<input type="hidden" name="hash" value="${hash}" />
</form>`, ["Продолжить", tr("cancel")], [
() => {
$("#give_product_access_dialog").submit();
},
Function.noop
]);
}
function showBtProductStatusDialog(product, hash) {
MessageBox(`Статус продукта ${product[1]} (#${product[0]})`, `<form action="/bt_product${product[0]}/manageStatus" method="post" id="give_product_access_dialog">
<table>
<tbody>
<tr>
<td><input type="radio" name="action" value="open"></td>
<td><label for="priority_1">Открытый</label></td>
</tr>
<tr>
<td><input type="radio" name="action" value="closed"></td>
<td><label for="priority_2">Закрытый</label></td>
</tr>
</tbody>
</table>
<input type="hidden" name="hash" value="${hash}" />
</form>`, ["Продолжить", tr("cancel")], [
() => {
$("#give_product_access_dialog").submit();
},
Function.noop
]);
}

View file

@ -29,7 +29,9 @@ CREATE TABLE `bt_products` (
`title` varchar(255) NOT NULL,
`description` varchar(10000) NOT NULL,
`created` bigint(20) NOT NULL,
`closed` tinyint(1) DEFAULT 0
`closed` tinyint(1) DEFAULT 0,
`private` tinyint(4) DEFAULT 0,
`deleted` tinyint(1) DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `bt_products`
@ -59,3 +61,21 @@ ALTER TABLE `bt_comments`
ALTER TABLE `bt_comments`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
COMMIT;
/* bt_products_access */
CREATE TABLE `bt_products_access` (
`id` bigint(20) NOT NULL,
`created` bigint(20) NOT NULL,
`tester` bigint(20) NOT NULL,
`product` bigint(20) NOT NULL,
`moderator` bigint(20) NOT NULL,
`access` tinyint(1) DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `bt_products_access`
ADD PRIMARY KEY (`id`);
ALTER TABLE `bt_products_access`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
COMMIT;