pridana implementacia backend s composerom a kniznicami TPsoft/APIlite a TPsoft/DBmodel
29
backend/composer.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "tpsoft/bugreport-backend",
|
||||
"description": "Backend for project BugReport",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"tpsoft/apilite": "^1.0",
|
||||
"tpsoft/dbmodel": "^1.0"
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TPsoft\\BugreportBackend\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"config/Configuration.php"
|
||||
]
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "igor",
|
||||
"email": "mino@tpsoft.org"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"scripts": {
|
||||
"model": "php scripts/createModel.php",
|
||||
"build": "php scripts/buildTypeScript.php"
|
||||
}
|
||||
}
|
108
backend/composer.lock
generated
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "61c996f7e342564a37ff61ac90fcdc7b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "tpsoft/apilite",
|
||||
"version": "v1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://gitea.tpsoft.org/TPsoft.org/APIlite.git",
|
||||
"reference": "2d3f8bfdd46d0d304bac44057ff8444bfb2a4a17"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TPsoft\\APIlite\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Igor Mino",
|
||||
"email": "mino@tpsoft.org",
|
||||
"homepage": "https://www.tpsoft.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A set of tools to simplify the work of creating backend APIs for your frontend projects.",
|
||||
"keywords": [
|
||||
"api",
|
||||
"json",
|
||||
"php",
|
||||
"rest",
|
||||
"typescript"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.anycoin.cz/donate/igormino",
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-12T05:54:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tpsoft/dbmodel",
|
||||
"version": "v1.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://gitea.tpsoft.org/TPsoft.org/DBmodel.git",
|
||||
"reference": "ae59ffaa97094854bcd1d863c6648a5b4dada671"
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TPsoft\\DBmodel\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Igor Mino",
|
||||
"email": "mino@tpsoft.org",
|
||||
"homepage": "https://www.tpsoft.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "This library extends the builtin PDO object by several useful features. ",
|
||||
"keywords": [
|
||||
"db",
|
||||
"model",
|
||||
"pdo"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.anycoin.cz/donate/igormino",
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-15T17:07:42+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
41
backend/config/Configuration.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
if (file_exists('c:/php/includes/igor.php')) {
|
||||
require_once 'c:/php/includes/igor.php';
|
||||
}
|
||||
|
||||
$others_config = scandir(__DIR__);
|
||||
$loaded = false;
|
||||
foreach ($others_config as $file) {
|
||||
if ($file == basename(__FILE__)) continue;
|
||||
if (substr($file, -4) == '.php') {
|
||||
require_once __DIR__ . '/' . $file;
|
||||
$loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$loaded) {
|
||||
class Configuration
|
||||
{
|
||||
public const DB_TYPE = 'sqlite';
|
||||
// MySQL
|
||||
public const DB_HOST = 'localhost';
|
||||
public const DB_USER = 'tpsoft_bugreport';
|
||||
public const DB_PASS = '****************';
|
||||
public const DB_NAME = 'tpsoft_bugreport';
|
||||
// SQLite
|
||||
public const DB_FILEPATH = __DIR__ . '/../../data/database.db';
|
||||
}
|
||||
}
|
||||
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
$uri = $_SERVER['REQUEST_URI']; // obsahuje aj query string
|
||||
|
||||
define('URL_PREFIX', $protocol.$host.str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']));
|
||||
|
||||
define('UPLOAD_DIR_ATTACHMENTS', __DIR__.'/../../data/attachments/');
|
||||
if (!file_exists(UPLOAD_DIR_ATTACHMENTS)) {
|
||||
mkdir(UPLOAD_DIR_ATTACHMENTS, 0777, true);
|
||||
}
|
||||
define('UPLOAD_URL_ATTACHMENTS', URL_PREFIX.'data/attachments/');
|
4
backend/public/API.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
require_once __DIR__.'/../src/API.php';
|
||||
use TPsoft\BugreportBackend\API;
|
||||
new API();
|
19
backend/scripts/buildTypeScript.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
ob_start();
|
||||
|
||||
$backend_api = new TPsoft\BugreportBackend\API('typescript', 'import.meta.env.VITE_BACKENDAPI_URL');
|
||||
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$ts_path = realpath(__DIR__ . '/../../frontend/src').'/backend.js';
|
||||
$suc = file_put_contents($ts_path, $output);
|
||||
if ($suc === false) {
|
||||
echo "✗ TypeScript store into file failed\n";
|
||||
exit(2);
|
||||
}
|
||||
|
||||
echo "✓ TypeScript backend script created\n";
|
9
backend/scripts/createModel.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../src/Init.php';
|
||||
use \TPsoft\DBmodel\Creator;
|
||||
|
||||
global $dbh;
|
||||
$creator = new Creator($dbh);
|
||||
$creator->rootDir(realpath(__DIR__.'/../src/'));
|
||||
$creator->interact();
|
257
backend/src/API.php
Normal file
@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace TPsoft\BugreportBackend;
|
||||
|
||||
require_once __DIR__ . '/Init.php';
|
||||
|
||||
use TPsoft\APIlite\APIlite;
|
||||
use TPsoft\BugreportBackend\Models\Reports;
|
||||
use TPsoft\BugreportBackend\Models\Attachments;
|
||||
use TPsoft\BugreportBackend\Models\Options;
|
||||
|
||||
|
||||
class API extends APIlite
|
||||
{
|
||||
/**
|
||||
* Add new report
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $description
|
||||
* @param int $status 0 = Uncategorized, 1 = Waiting, 2 = InProgress, 3 = Blocked, 4 = Archived
|
||||
* @param string $group
|
||||
* @param int $priority 0 = Low, 1 = Medium, 2 = High, 3 = Urgent
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function add(string $title, string $description, int $status = 0, ?string $group = null, int $priority = 0): int
|
||||
{
|
||||
$status = intval($status);
|
||||
$priority = intval($priority);
|
||||
$reports = new Reports();
|
||||
$report_id = $reports->report(null, [
|
||||
'report_title' => $title,
|
||||
'report_description' => $description,
|
||||
'report_status' => $status,
|
||||
'report_group' => $group,
|
||||
'report_priority' => $priority,
|
||||
]);
|
||||
return $report_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report
|
||||
*
|
||||
* @param int $report_id
|
||||
* @param array $report_data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update(int $report_id, array $report_data): bool
|
||||
{
|
||||
$reports = new Reports();
|
||||
$suc = $reports->report($report_id, $report_data);
|
||||
return $suc !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete report
|
||||
*
|
||||
* @param int $report_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(int $report_id): bool
|
||||
{
|
||||
$reports = new Reports();
|
||||
$suc = $reports->report($report_id, null);
|
||||
return $suc !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report
|
||||
*
|
||||
* @param int $report_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get(int $report_id): array
|
||||
{
|
||||
$reports = new Reports();
|
||||
return $reports->report($report_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports
|
||||
*
|
||||
* @param array $status 0 = Uncategorized, 1 = Waiting, 2 = InProgress, 3 = Blocked, 4 = Archived
|
||||
* @param int $page Pagination from 0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAll(?array $status = null, int $page = 0): array
|
||||
{
|
||||
$page = intval($page);
|
||||
$reports = new Reports();
|
||||
if ($status === null) $status = array(0, 1, 2, 3);
|
||||
$ret = $reports->search('reports')
|
||||
->where(['report_status' => $status])
|
||||
->order(array('report_priority' => 'DESC', 'ordnum' => 'ASC'))
|
||||
->limit($page * 10, 10)
|
||||
->toArray();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports grouped by status
|
||||
*
|
||||
* @param array $status 0 = Uncategorized, 1 = Waiting, 2 = InProgress, 3 = Blocked, 4 = Archived
|
||||
* @param int $page Pagination from 0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllGrouped(?array $status = null, int $page = 0): array
|
||||
{
|
||||
$page = intval($page);
|
||||
$all = $this->getAll($status, $page);
|
||||
$groups = [];
|
||||
foreach ($all as $report) {
|
||||
$groups[$report['report_status']][] = $report;
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archived reports
|
||||
*
|
||||
* @param int $page Pagination from 0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getArchived(int $page = 0): array
|
||||
{
|
||||
$page = intval($page);
|
||||
$reports = new Reports();
|
||||
$ret = $reports->search('reports')
|
||||
->where(['report_status' => 4])
|
||||
->order(array('created_dt' => 'DESC'))
|
||||
->limit($page * 10, 10)
|
||||
->toArray();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report order number
|
||||
*
|
||||
* @param array $ordnums report_id => ordnum
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateOrdNum(array $ordnums): bool
|
||||
{
|
||||
$reports = new Reports();
|
||||
$suc = true;
|
||||
foreach ($ordnums as $report_id => $ordnum) {
|
||||
$suc &= $reports->report($report_id, ['ordnum' => $ordnum]);
|
||||
}
|
||||
return $suc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report status
|
||||
*
|
||||
* @param int $report_id
|
||||
* @param int $status 0 = Uncategorized, 1 = Waiting, 2 = InProgress, 3 = Blocked, 4 = Archived
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateStatus(int $report_id, int $status): bool
|
||||
{
|
||||
$reports = new Reports();
|
||||
$suc = $reports->report($report_id, ['report_status' => $status]);
|
||||
return $suc !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add report attachment
|
||||
*
|
||||
* @param int $report_id
|
||||
* @param string $attachment_type "comment" or "file"
|
||||
* @param string $attachment_content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function attachmentAdd(int $report_id, string $attachment_type, string $attachment_content): bool
|
||||
{
|
||||
if ($attachment_type == 'file') {
|
||||
$data = json_decode($attachment_content, true);
|
||||
if (!is_array($data)) return false;
|
||||
$base64 = preg_replace('/^data:.*?;base64,/', '', $data['base64']);
|
||||
$base64_data = base64_decode($base64);
|
||||
$filename = 'report_' . $report_id . '_' . time() . '_' . sanitizeFilename($data['filename']);
|
||||
file_put_contents(UPLOAD_DIR_ATTACHMENTS . $filename, $base64_data);
|
||||
$attachment_content = $filename;
|
||||
}
|
||||
$attachments = new Attachments();
|
||||
$suc = $attachments->attachment(null, [
|
||||
'report_id' => $report_id,
|
||||
'attachment_type' => $attachment_type,
|
||||
'attachment_content' => $attachment_content,
|
||||
'created_dt' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
return $suc !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report attachment
|
||||
*
|
||||
* @param int $attachment_id
|
||||
* @param string $attachment_content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function attachmentUpdate(int $attachment_id, string $attachment_content): bool
|
||||
{
|
||||
if (strlen(trim($attachment_content)) <= 0) return $this->attachmentDelete($attachment_id);
|
||||
$attachments = new Attachments();
|
||||
$suc = $attachments->attachment($attachment_id, [
|
||||
'attachment_content' => $attachment_content,
|
||||
'updated_dt' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
return $suc !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all report attachments
|
||||
*
|
||||
* @param int $report_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attachmentGetAll(int $report_id): array
|
||||
{
|
||||
$attachments = new Attachments();
|
||||
$all = $attachments->search('attachments')
|
||||
->where(['report_id' => $report_id])
|
||||
->order(array('created_dt' => 'ASC'))
|
||||
->toArray();
|
||||
if (is_array($all)) foreach ($all as $key => $row) {
|
||||
if ($all[$key]['attachment_type'] == 'file') {
|
||||
$all[$key]['attachment_content'] = UPLOAD_URL_ATTACHMENTS . $all[$key]['attachment_content'];
|
||||
}
|
||||
}
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete report attachment
|
||||
*
|
||||
* @param int $attachment_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function attachmentDelete(int $attachment_id): bool {
|
||||
$attachments = new Attachments();
|
||||
$suc = $attachments->attachment($attachment_id, null);
|
||||
return $suc !== false;
|
||||
}
|
||||
}
|
33
backend/src/Init.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use \Exception;
|
||||
use \TPsoft\DBmodel\DBmodel;
|
||||
|
||||
global $dbh;
|
||||
|
||||
if (Configuration::DB_TYPE == 'mysql') {
|
||||
$dbh = new DBmodel(sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', Configuration::DB_HOST, Configuration::DB_NAME), Configuration::DB_USER, Configuration::DB_PASS);
|
||||
} else if (Configuration::DB_TYPE == 'sqlite') {
|
||||
$dbh = new DBmodel(sprintf('sqlite:%s', Configuration::DB_FILEPATH));
|
||||
} else {
|
||||
throw new Exception('Unknown database type');
|
||||
}
|
||||
/*
|
||||
$dbh->tables = [
|
||||
'reports' => [
|
||||
'name' => 'reports',
|
||||
'primary_key_name' => 'report_id',
|
||||
'allow_attributes' => [
|
||||
'report_id' => 'varchar()',
|
||||
'report_title' => 'varchar()',
|
||||
'report_description' => 'varchar()',
|
||||
'report_status' => 'varchar()',
|
||||
'report_group' => 'varchar()',
|
||||
'report_priority' => 'varchar()',
|
||||
'created_dt' => 'varchar()',
|
||||
],
|
||||
],
|
||||
];
|
||||
*/
|
88
backend/src/Models/Attachments.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/*
|
||||
TPsoft.org 2000-2025
|
||||
file for controlers/*.php
|
||||
|
||||
Milestones:
|
||||
2025-09-30 22:49 Created
|
||||
*/
|
||||
|
||||
namespace TPsoft\BugreportBackend\Models;
|
||||
|
||||
class Attachments extends \TPsoft\DBmodel\DBmodel {
|
||||
|
||||
public $tables = array(
|
||||
'attachments' => array(
|
||||
'name' => 'attachments',
|
||||
'primary_key_name' => 'attachment_id',
|
||||
'allow_attributes' => array(
|
||||
'report_id' => 'INTEGER',
|
||||
'attachment_type' => 'VARCHAR(255)',
|
||||
'attachment_content' => 'TEXT',
|
||||
'created_dt' => 'DATETIME',
|
||||
'updated_dt' => 'DATETIME'
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
public function exist($primary_key = null) {
|
||||
return $this->existRecord('attachments', $primary_key);
|
||||
}
|
||||
|
||||
public function attachment($primary_key = null, $data = array()) {
|
||||
if (is_null($primary_key)
|
||||
&& !isset($data['created_dt']))
|
||||
{
|
||||
$data['created_dt'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
return $this->record('attachments', $primary_key, $data);
|
||||
}
|
||||
|
||||
public function attachmentBy($colname, $colvalue) {
|
||||
return $this->recordBy('attachments', $colname, $colvalue);
|
||||
}
|
||||
|
||||
public function attachmentSave($data = array()) {
|
||||
return $this->attachment($this->exist($data) ? $data : null, $data);
|
||||
}
|
||||
|
||||
public function attachmentEmpty() {
|
||||
return $this->recordEmpty('attachments');
|
||||
}
|
||||
|
||||
public function attachmentAttributes() {
|
||||
return $this->typesAttributes('attachments');
|
||||
}
|
||||
|
||||
public function attachmentCount() {
|
||||
return $this->count('attachments');
|
||||
}
|
||||
|
||||
public function getList($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->search('attachments')
|
||||
->where($search, $concat_or)
|
||||
->order(array('attachment_id' => $reverse ? 'DESC' : 'ASC'))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getListOrganize($cola_name, $search = array(), $reverse = false, $concat_or = false) {
|
||||
$all = $this->getList($search, $reverse, $concat_or);
|
||||
$ret = array();
|
||||
if (is_array($all)) foreach ($all as $key => $row) {
|
||||
$ret[$row[$cola_name]] = $row;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getListByID($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->getListOrganize('attachment_id', $search, $reverse, $concat_or);
|
||||
}
|
||||
|
||||
public function attachmentCombo($col_key, $col_value, $add_empty = false) {
|
||||
return $this->search('attachments')
|
||||
->toCombo($col_key, $col_value, $add_empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
81
backend/src/Models/Options.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/*
|
||||
TPsoft.org 2000-2025
|
||||
file for controlers/*.php
|
||||
|
||||
Milestones:
|
||||
2025-09-30 22:49 Created
|
||||
*/
|
||||
|
||||
namespace TPsoft\BugreportBackend\Models;
|
||||
|
||||
class Options extends \TPsoft\DBmodel\DBmodel {
|
||||
|
||||
public $tables = array(
|
||||
'options' => array(
|
||||
'name' => 'options',
|
||||
'primary_key_name' => '',
|
||||
'allow_attributes' => array(
|
||||
'key' => 'VARCHAR(64)',
|
||||
'value' => 'TEXT',
|
||||
'created_at' => 'DATETIME'
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
public function exist($primary_key = null) {
|
||||
return $this->existRecord('options', $primary_key);
|
||||
}
|
||||
|
||||
public function option($primary_key = null, $data = array()) {
|
||||
return $this->record('options', $primary_key, $data);
|
||||
}
|
||||
|
||||
public function optionBy($colname, $colvalue) {
|
||||
return $this->recordBy('options', $colname, $colvalue);
|
||||
}
|
||||
|
||||
public function optionSave($data = array()) {
|
||||
return $this->option($this->exist($data) ? $data : null, $data);
|
||||
}
|
||||
|
||||
public function optionEmpty() {
|
||||
return $this->recordEmpty('options');
|
||||
}
|
||||
|
||||
public function optionAttributes() {
|
||||
return $this->typesAttributes('options');
|
||||
}
|
||||
|
||||
public function optionCount() {
|
||||
return $this->count('options');
|
||||
}
|
||||
|
||||
public function getList($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->search('options')
|
||||
->where($search, $concat_or)
|
||||
->order(array('' => $reverse ? 'DESC' : 'ASC'))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getListOrganize($cola_name, $search = array(), $reverse = false, $concat_or = false) {
|
||||
$all = $this->getList($search, $reverse, $concat_or);
|
||||
$ret = array();
|
||||
if (is_array($all)) foreach ($all as $key => $row) {
|
||||
$ret[$row[$cola_name]] = $row;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getListByID($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->getListOrganize('', $search, $reverse, $concat_or);
|
||||
}
|
||||
|
||||
public function optionCombo($col_key, $col_value, $add_empty = false) {
|
||||
return $this->search('options')
|
||||
->toCombo($col_key, $col_value, $add_empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
90
backend/src/Models/Reports.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/*
|
||||
TPsoft.org 2000-2025
|
||||
file for controlers/*.php
|
||||
|
||||
Milestones:
|
||||
2025-09-30 22:21 Created
|
||||
*/
|
||||
|
||||
namespace TPsoft\BugreportBackend\Models;
|
||||
|
||||
class Reports extends \TPsoft\DBmodel\DBmodel {
|
||||
|
||||
public $tables = array(
|
||||
'reports' => array(
|
||||
'name' => 'reports',
|
||||
'primary_key_name' => 'report_id',
|
||||
'allow_attributes' => array(
|
||||
'report_title' => 'VARCHAR(255)',
|
||||
'report_description' => 'TEXT',
|
||||
'report_status' => 'INTEGER',
|
||||
'report_group' => 'VARCHAR(255)',
|
||||
'report_priority' => 'INTEGER',
|
||||
'created_dt' => 'DATETIME',
|
||||
'ordnum' => 'INTEGER'
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
public function exist($primary_key = null) {
|
||||
return $this->existRecord('reports', $primary_key);
|
||||
}
|
||||
|
||||
public function report($primary_key = null, $data = array()) {
|
||||
if (is_null($primary_key)
|
||||
&& !isset($data['created_dt']))
|
||||
{
|
||||
$data['created_dt'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
return $this->record('reports', $primary_key, $data);
|
||||
}
|
||||
|
||||
public function reportBy($colname, $colvalue) {
|
||||
return $this->recordBy('reports', $colname, $colvalue);
|
||||
}
|
||||
|
||||
public function reportSave($data = array()) {
|
||||
return $this->report($this->exist($data) ? $data : null, $data);
|
||||
}
|
||||
|
||||
public function reportEmpty() {
|
||||
return $this->recordEmpty('reports');
|
||||
}
|
||||
|
||||
public function reportAttributes() {
|
||||
return $this->typesAttributes('reports');
|
||||
}
|
||||
|
||||
public function reportCount() {
|
||||
return $this->count('reports');
|
||||
}
|
||||
|
||||
public function getList($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->search('reports')
|
||||
->where($search, $concat_or)
|
||||
->order(array('report_id' => $reverse ? 'DESC' : 'ASC'))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getListOrganize($cola_name, $search = array(), $reverse = false, $concat_or = false) {
|
||||
$all = $this->getList($search, $reverse, $concat_or);
|
||||
$ret = array();
|
||||
if (is_array($all)) foreach ($all as $key => $row) {
|
||||
$ret[$row[$cola_name]] = $row;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getListByID($search = array(), $reverse = false, $concat_or = false) {
|
||||
return $this->getListOrganize('report_id', $search, $reverse, $concat_or);
|
||||
}
|
||||
|
||||
public function reportCombo($col_key, $col_value, $add_empty = false) {
|
||||
return $this->search('reports')
|
||||
->toCombo($col_key, $col_value, $add_empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
84
backend/tests/testDB.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../src/Init.php';
|
||||
use TPsoft\BugreportBackend\Models\Reports;
|
||||
|
||||
global $dbh;
|
||||
|
||||
$test_id = $argv[1];
|
||||
|
||||
if ($test_id == 1) { // cista query
|
||||
$query = sprintf('SELECT * FROM reports');
|
||||
$list = $dbh->getAll($query);
|
||||
print_r($list);
|
||||
} else if ($test_id == 2) { // zistenie typu DB
|
||||
$type = $dbh->getDBtype();
|
||||
echo "DB type: '$type'\n";
|
||||
} else if ($test_id == 3) { // ziskanie stlpcov tabulky
|
||||
$table_columns = $dbh->getTableColumns('reports');
|
||||
print_r($table_columns);
|
||||
} else if ($test_id == 4) { // test modelu a ziskanie zoznamu
|
||||
$reports = new Reports();
|
||||
$ret = $reports->getList();
|
||||
print_r($ret);
|
||||
} else if ($test_id == 5) { // test SELECT
|
||||
$reports = new Reports();
|
||||
$ret = $reports->report(52);
|
||||
print_r($ret);
|
||||
} else if ($test_id == 6) { // test UPDATE
|
||||
$reports = new Reports();
|
||||
$ret = $reports->report(52, array('report_description' => 'zmenene o '.date('Y-m-d H:i:s')));
|
||||
print_r($ret);
|
||||
} else if ($test_id == 7) { // test INSERT
|
||||
$reports = new Reports();
|
||||
$ret = $reports->report(null, array('report_title' => 'napis pre bug', 'report_description' => 'vytvorene o '.date('Y-m-d H:i:s')));
|
||||
print_r($ret);
|
||||
} else if ($test_id == 8) { // test DELETE
|
||||
$reports = new Reports();
|
||||
$ret = $reports->report(55, null);
|
||||
print_r($ret);
|
||||
} else if ($test_id == 9) { // test exists()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->exist(52);
|
||||
var_dump($ret);
|
||||
$ret = $reports->exist(525);
|
||||
var_dump($ret);
|
||||
} else if ($test_id == 10) { // test reportBy()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->reportBy('report_title', 'test9');
|
||||
print_r($ret);
|
||||
} else if ($test_id == 11) { // test reportSave()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->reportSave(array('report_id' => 54, 'report_description' => 'zmenene o '.date('Y-m-d H:i:s')));
|
||||
print_r($ret);
|
||||
} else if ($test_id == 12) { // test reportEmpty()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->reportEmpty();
|
||||
print_r($ret);
|
||||
} else if ($test_id == 13) { // test reportAttributes()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->reportAttributes();
|
||||
print_r($ret);
|
||||
} else if ($test_id == 14) { // test reportCount()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->reportCount();
|
||||
print_r($ret);
|
||||
} else if ($test_id == 15) { // test getList()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->getList(array('report_status' => 3), true);
|
||||
print_r($ret);
|
||||
} else if ($test_id == 16) { // test getListOrganize()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->getListOrganize('report_id', array('report_status' => 3));
|
||||
print_r($ret);
|
||||
} else if ($test_id == 17) { // test getListByID()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->getListByID(array('report_status' => 3));
|
||||
print_r($ret);
|
||||
} else if ($test_id == 18) { // test reportCombo()
|
||||
$reports = new Reports();
|
||||
$ret = $reports->reportCombo('report_id', 'report_title');
|
||||
print_r($ret);
|
||||
} else {
|
||||
echo "Unknown test id (first argument): '$test_id'\n";
|
||||
}
|
0
webapp/.gitignore → frontend/.gitignore
vendored
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -46,7 +46,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { backend } from "./backend";
|
||||
import backend from "./backend";
|
||||
import events from "./events";
|
||||
|
||||
const short_bug = ref("");
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 949 B After Width: | Height: | Size: 949 B |
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 496 B |
@ -1,10 +1,14 @@
|
||||
export const backend = {
|
||||
endpont: __IS_BUILD__
|
||||
? window.location.origin + __SUBPATH__ + "api.php"
|
||||
: "http://localhost/bugreport/api.php",
|
||||
/**
|
||||
* Generated by APIlite
|
||||
* https://gitea.tpsoft.org/TPsoft.org/APIlite
|
||||
*
|
||||
* 2025-10-01 00:23:37 */
|
||||
|
||||
class backend {
|
||||
endpont = import.meta.env.VITE_BACKENDAPI_URL;
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* Vsebecne API volanie
|
||||
* General API call
|
||||
*/
|
||||
call(method, data, callback) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
@ -25,13 +29,16 @@ export const backend = {
|
||||
form_data.append(key, val);
|
||||
});
|
||||
xhttp.open('POST', this.endpont + '?action=' + method);
|
||||
//xhttp.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
|
||||
xhttp.send(form_data);
|
||||
},
|
||||
}
|
||||
|
||||
callPromise(method, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.call(method, data, function(response) {
|
||||
if (method == '__HELP__') {
|
||||
resolve(response);
|
||||
return;
|
||||
}
|
||||
if (response.status == 'OK') {
|
||||
resolve(response.data);
|
||||
} else {
|
||||
@ -39,70 +46,68 @@ export const backend = {
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* API akcie
|
||||
* API actions
|
||||
*/
|
||||
help() {
|
||||
return this.callPromise('help', {});
|
||||
},
|
||||
return this.callPromise('__HELP__', {});
|
||||
}
|
||||
|
||||
add(title, description, status, group, priority) {
|
||||
return this.callPromise('add', {
|
||||
title: title,
|
||||
description: description,
|
||||
status: status,
|
||||
group: group,
|
||||
priority: priority});
|
||||
},
|
||||
return this.callPromise('add', {title: title, description: description, status: status, group: group, priority: priority});
|
||||
}
|
||||
|
||||
update(id, report_data) {
|
||||
return this.callPromise('update', {report_id: id, report_data: report_data});
|
||||
},
|
||||
update(report_id, report_data) {
|
||||
return this.callPromise('update', {report_id: report_id, report_data: report_data});
|
||||
}
|
||||
|
||||
delete(id) {
|
||||
return this.callPromise('delete', {report_id: id});
|
||||
},
|
||||
delete(report_id) {
|
||||
return this.callPromise('delete', {report_id: report_id});
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this.callPromise('get', {report_id: id});
|
||||
},
|
||||
get(report_id) {
|
||||
return this.callPromise('get', {report_id: report_id});
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.callPromise('getAll', {});
|
||||
},
|
||||
getAll(status, page) {
|
||||
return this.callPromise('getAll', {status: status, page: page});
|
||||
}
|
||||
|
||||
getAllGrouped(status, page = null) {
|
||||
getAllGrouped(status, page) {
|
||||
return this.callPromise('getAllGrouped', {status: status, page: page});
|
||||
},
|
||||
}
|
||||
|
||||
getArchived(page = null) {
|
||||
getArchived(page) {
|
||||
return this.callPromise('getArchived', {page: page});
|
||||
},
|
||||
}
|
||||
|
||||
updateOrdnum(ordnums) {
|
||||
updateOrdNum(ordnums) {
|
||||
return this.callPromise('updateOrdNum', {ordnums: ordnums});
|
||||
},
|
||||
}
|
||||
|
||||
updateStatus(id, status) {
|
||||
return this.callPromise('updateStatus', {report_id: id, status: status});
|
||||
},
|
||||
updateStatus(report_id, status) {
|
||||
return this.callPromise('updateStatus', {report_id: report_id, status: status});
|
||||
}
|
||||
|
||||
attachmentAdd(report_id, attachment_type, attachment_content) {
|
||||
return this.callPromise('attachmentAdd', {report_id: report_id, attachment_type: attachment_type, attachment_content: attachment_content});
|
||||
},
|
||||
}
|
||||
|
||||
attachmentUpdate(attachment_id, attachment_content) {
|
||||
return this.callPromise('attachmentUpdate', {attachment_id: attachment_id, attachment_content: attachment_content});
|
||||
},
|
||||
}
|
||||
|
||||
attachmentGetAll(report_id) {
|
||||
return this.callPromise('attachmentGetAll', {report_id: report_id});
|
||||
},
|
||||
}
|
||||
|
||||
attachmentDelete(attachment_id) {
|
||||
return this.callPromise('attachmentDelete', {attachment_id: attachment_id});
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default new backend();
|
@ -39,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { backend } from "../backend";
|
||||
import backend from "../backend";
|
||||
|
||||
export default {
|
||||
name: "API",
|
@ -31,7 +31,7 @@
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onBeforeUnmount, ref } from "vue";
|
||||
import { backend } from "../backend";
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
|
||||
const reports = ref([]);
|
||||
@ -41,12 +41,12 @@ const loadedAll = ref(false);
|
||||
|
||||
function loadMoreReports() {
|
||||
loading.value = true;
|
||||
backend.getArchived(page.value).then((data) => {
|
||||
backend.getArchived(page.value).then((response) => {
|
||||
// console.log(data);
|
||||
if (data.length == 0) {
|
||||
if (response.data.length == 0) {
|
||||
loadedAll.value = true;
|
||||
} else {
|
||||
reports.value.push(...data);
|
||||
reports.value.push(...response.data);
|
||||
}
|
||||
page.value++;
|
||||
loading.value = false;
|
@ -79,7 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { backend } from "../backend";
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
|
||||
export default {
|
@ -139,7 +139,7 @@ function isDragable(element) {
|
||||
<script>
|
||||
import ReportBox from "../components/ReportBox.vue";
|
||||
import draggable from "vuedraggable";
|
||||
import { backend } from "../backend";
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
import events from "../events";
|
||||
|
||||
@ -161,7 +161,8 @@ export default {
|
||||
methods: {
|
||||
loadData(use_loader = true) {
|
||||
if (use_loader) this.loading = true;
|
||||
backend.getAllGrouped(Array(0, 1, 2, 3)).then((all_grouped) => {
|
||||
backend.getAllGrouped(Array(0, 1, 2, 3)).then((response) => {
|
||||
let all_grouped = response.data;
|
||||
this.itemsUncategorized = all_grouped[0];
|
||||
this.itemsWaiting = all_grouped[1];
|
||||
this.itemsInProgress = all_grouped[2];
|
@ -145,7 +145,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { backend } from "../backend";
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
import JSConfetti from 'js-confetti'
|
||||
|
||||
@ -189,13 +189,13 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
loadReportData() {
|
||||
backend.get(this.report_id).then((report) => {
|
||||
this.report = report;
|
||||
backend.get(this.report_id).then((response) => {
|
||||
this.report = response.data;
|
||||
// console.log(this.report);
|
||||
this.editable = report.report_status < 4;
|
||||
this.editable = response.data.report_status < 4;
|
||||
});
|
||||
backend.attachmentGetAll(this.report_id).then((attachments) => {
|
||||
this.attachments = attachments;
|
||||
backend.attachmentGetAll(this.report_id).then((response) => {
|
||||
this.attachments = response.data;
|
||||
// console.log(this.attachments);
|
||||
});
|
||||
},
|