Compare commits
11 Commits
b51ae03fcf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| afc5229f5b | |||
| e50c53e50a | |||
| 3e9056f4a6 | |||
| ed3d202488 | |||
| e67d4843bd | |||
| fbfe46542b | |||
| 948fce896f | |||
| 027815ee3f | |||
| 19600ac7bf | |||
| e63be43639 | |||
| 995e0e40a5 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "lib/Medoo"]
|
|
||||||
path = lib/Medoo
|
|
||||||
url = https://github.com/catfan/Medoo.git
|
|
||||||
|
|||||||
55
README.md
55
README.md
@ -6,7 +6,7 @@ BugReport je webová aplikácia na sledovanie a správu chýb (bug tracking syst
|
|||||||
|
|
||||||
<br /><br /><br />
|
<br /><br /><br />
|
||||||
|
|
||||||
## Funkcie
|
## 🚀 Funkcie
|
||||||
|
|
||||||
- **Kanban rozhranie** - vizuálne sledovanie stavu bug reportov v štyroch kategóriách (Nezaradené, Čakajúce, Rozpracované, Blokované)
|
- **Kanban rozhranie** - vizuálne sledovanie stavu bug reportov v štyroch kategóriách (Nezaradené, Čakajúce, Rozpracované, Blokované)
|
||||||
- **Drag-and-drop** - jednoduché presúvanie reportov medzi kategóriami
|
- **Drag-and-drop** - jednoduché presúvanie reportov medzi kategóriami
|
||||||
@ -15,23 +15,23 @@ BugReport je webová aplikácia na sledovanie a správu chýb (bug tracking syst
|
|||||||
- **Detailný pohľad** - zobrazenie a úprava detailov reportu
|
- **Detailný pohľad** - zobrazenie a úprava detailov reportu
|
||||||
- **REST API** - prístup k dátam cez API endpoint
|
- **REST API** - prístup k dátam cez API endpoint
|
||||||
|
|
||||||
## Diagram stavov pre BUG
|
## 🔷 Diagram stavov pre BUG
|
||||||
|
|
||||||
<img src="frontend/src/assets/images/FlowDiagram.drawio.svg" />
|
<img src="frontend/src/assets/images/FlowDiagram.drawio.svg" />
|
||||||
|
|
||||||
## Screenshot
|
## 🖼️ Screenshot
|
||||||
<img src="doc/Screenshot_2025-05-17_111345.png" />
|
<img src="doc/Screenshot_2025-05-17_111345.png" />
|
||||||
|
|
||||||
## Technológie
|
## 🖥️ Technológie
|
||||||
|
|
||||||
### Backend
|
### ⚙️ Backend
|
||||||
|
|
||||||
- PHP
|
- PHP
|
||||||
- SQLite databáza
|
- SQLite databáza
|
||||||
- [TPsoft/DBmodel](https://gitea.tpsoft.org/TPsoft.org/DBmodel) - PHP databázové rozšírenie
|
- [TPsoft/DBmodel](https://gitea.tpsoft.org/TPsoft.org/DBmodel) - PHP databázové rozšírenie
|
||||||
- [TPsoft/APIlite](https://gitea.tpsoft.org/TPsoft.org/APIlite) - Jednoduchý nástroj pre zostavenie API
|
- [TPsoft/APIlite](https://gitea.tpsoft.org/TPsoft.org/APIlite) - Jednoduchý nástroj pre zostavenie API
|
||||||
|
|
||||||
### Frontend
|
### 📺 Frontend
|
||||||
|
|
||||||
- [Vue.js 3](https://vuejs.org/) - JavaScript framework
|
- [Vue.js 3](https://vuejs.org/) - JavaScript framework
|
||||||
- [Vue Router](https://router.vuejs.org/) - smerovanie v aplikácii
|
- [Vue Router](https://router.vuejs.org/) - smerovanie v aplikácii
|
||||||
@ -40,17 +40,24 @@ BugReport je webová aplikácia na sledovanie a správu chýb (bug tracking syst
|
|||||||
- [Mitt](https://github.com/developit/mitt) - knižnica pre správu udalostí
|
- [Mitt](https://github.com/developit/mitt) - knižnica pre správu udalostí
|
||||||
- [Vite](https://vitejs.dev/) - build nástroj
|
- [Vite](https://vitejs.dev/) - build nástroj
|
||||||
|
|
||||||
## Štruktúra projektu
|
## 🌳 Štruktúra projektu
|
||||||
|
|
||||||
```
|
```
|
||||||
BugReport/
|
BugReport/
|
||||||
├── backend/ # Backend aplikácia (PHP)
|
├── backend/ # Backend aplikácia (PHP)
|
||||||
│ ├── src/ # Zdrojový kód
|
|
||||||
│ ├── config/ # Konfigurácia aplikácie
|
│ ├── config/ # Konfigurácia aplikácie
|
||||||
│ └── public/ # Vstupný bod aplikácie
|
│ ├── public/ # Vstupný bod aplikácie, tu nasmerovat vo webserveri DOCUMENT ROOT
|
||||||
|
│ ├── scripts/ # Skripty pre pred-spracovanie, build a ine
|
||||||
|
│ ├── src/ # Zdrojový kód
|
||||||
|
│ │ ├── Models/ # Classy pre jednotlive DB tabulky pouziva TPsoft\DBmodel
|
||||||
|
│ │ ├── API.php # Logika pre API BugReport pouziva TPsoft\APIlite
|
||||||
|
│ │ ├── Init.php # Inicializacia pripojenia k DB, kontroly a iné
|
||||||
|
│ │ └── Maintenance.php # Údržba pre projekt, zatiaľ hlavne pre upgrade databazovej štruktúry, používa \TPsoft\DBmodel\Maintenance
|
||||||
|
│ └── test/ # Testovacie skripty
|
||||||
├── data/ # Dáta aplikácie
|
├── data/ # Dáta aplikácie
|
||||||
│ ├── attachments/ # Súborové uložisko príloh
|
│ ├── attachments/ # Súborové uložisko príloh
|
||||||
│ └── database.db # SQLite databáza
|
│ └── database.db # SQLite databáza
|
||||||
|
├── doc/ # Dokumentácia a iné súbory počas vývoja
|
||||||
└── frontend/ # Frontend aplikácia (Vue.js)
|
└── frontend/ # Frontend aplikácia (Vue.js)
|
||||||
├── public/ # Statické súbory
|
├── public/ # Statické súbory
|
||||||
├── scripts/ # Skripty pre build
|
├── scripts/ # Skripty pre build
|
||||||
@ -65,21 +72,22 @@ BugReport/
|
|||||||
└── router.js # Vue Router konfigurácia
|
└── router.js # Vue Router konfigurácia
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inštalácia a spustenie
|
## 🛠️ Inštalácia a spustenie
|
||||||
|
|
||||||
### Požiadavky
|
### 📋 Požiadavky
|
||||||
|
|
||||||
- PHP 7.4 alebo novší
|
- PHP 8.2 alebo novší
|
||||||
- Webový server (Apache, Nginx)
|
- Webový server (Apache, Nginx)
|
||||||
- Node.js a npm pre vývoj frontendu
|
- Node.js a npm pre vývoj frontendu
|
||||||
|
|
||||||
### Backend
|
### ⚙️ Backend
|
||||||
|
|
||||||
1. Skopírujte súbory do webového adresára
|
1. Skopírujte súbory do webového adresára
|
||||||
2. Uistite sa, že adresár `data` má práva na zápis
|
2. Uistite sa, že adresár `data` má práva na zápis
|
||||||
|
3. Nastavte DOCUMENT ROOT na adresár `backend/public`
|
||||||
3. Prístup k aplikácii cez webový prehliadač
|
3. Prístup k aplikácii cez webový prehliadač
|
||||||
|
|
||||||
### Frontend (pre vývoj)
|
### 📺 Frontend (pre vývoj)
|
||||||
|
|
||||||
1. Prejdite do adresára`frontend`
|
1. Prejdite do adresára`frontend`
|
||||||
2. Nainštalujte závislosti:
|
2. Nainštalujte závislosti:
|
||||||
@ -95,11 +103,11 @@ BugReport/
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## API dokumentácia
|
## 📖 API dokumentácia
|
||||||
|
|
||||||
API je dostupné cez `api.php` endpoint. Všetky požiadavky vracajú JSON odpoveď.
|
API je dostupné cez `API.php` endpoint fyzicky umiestnený v DOCUMENT ROOT `backend/public/API.php`. Všetky požiadavky vracajú JSON odpoveď.kompletnú a aktuálnu dokumentáciu je možné získať aj HTML formáte `API.php?format=html`.
|
||||||
|
|
||||||
### Dostupné endpointy
|
### 🚏 Dostupné endpointy
|
||||||
|
|
||||||
| Akcia | Popis | Parametre |
|
| Akcia | Popis | Parametre |
|
||||||
| -------------------- | -------------------------------------- | --------------------------------------------------------------- |
|
| -------------------- | -------------------------------------- | --------------------------------------------------------------- |
|
||||||
@ -116,7 +124,7 @@ API je dostupné cez `api.php` endpoint. Všetky požiadavky vracajú JSON odpov
|
|||||||
| `attachmentUpdate` | Aktualizuje prílohu | `attachment_id`, `attachment_content` |
|
| `attachmentUpdate` | Aktualizuje prílohu | `attachment_id`, `attachment_content` |
|
||||||
| `attachmentGetAll` | Získa všetky prílohy reportu | `report_id` |
|
| `attachmentGetAll` | Získa všetky prílohy reportu | `report_id` |
|
||||||
|
|
||||||
### Príklad API volania
|
### 🔍 Príklad API volania
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Získanie všetkých reportov
|
// Získanie všetkých reportov
|
||||||
@ -133,7 +141,7 @@ fetch('api.php?action=add', {
|
|||||||
.then(data => console.log(data));
|
.then(data => console.log(data));
|
||||||
```
|
```
|
||||||
|
|
||||||
## Stavy reportov
|
## 🏗️ Stavy reportov
|
||||||
|
|
||||||
| ID | Stav |
|
| ID | Stav |
|
||||||
| -- | ------------- |
|
| -- | ------------- |
|
||||||
@ -143,7 +151,7 @@ fetch('api.php?action=add', {
|
|||||||
| 3 | Blokované |
|
| 3 | Blokované |
|
||||||
| 4 | Vyriešený |
|
| 4 | Vyriešený |
|
||||||
|
|
||||||
## Priority reportov
|
## 📣 Priority reportov
|
||||||
|
|
||||||
| ID | Priorita |
|
| ID | Priorita |
|
||||||
| -- | --------- |
|
| -- | --------- |
|
||||||
@ -152,13 +160,16 @@ fetch('api.php?action=add', {
|
|||||||
| 2 | Vysoká |
|
| 2 | Vysoká |
|
||||||
| 3 | Kritická |
|
| 3 | Kritická |
|
||||||
|
|
||||||
## Skupiny reportov
|
## 🕸️ Skupiny reportov
|
||||||
|
|
||||||
- `cp` - Control Panel
|
- `cp` - Control Panel
|
||||||
- `task` - Task.Platon.sk
|
- `task` - Task.Platon.sk
|
||||||
- `websiteip` - WebsiteIP
|
- `websiteip` - WebsiteIP
|
||||||
- `antispam` - Antispam
|
- `antispam` - Antispam
|
||||||
|
|
||||||
## Licencia
|
## ✨ Plánované vylepšenia - Planned Features
|
||||||
|
- Skupiny reportov v samostatnej tabuľke s CRUD manažmentom
|
||||||
|
|
||||||
|
## ⚖️ Licencia
|
||||||
|
|
||||||
Tento projekt je licencovaný pod [MIT licenciou](https://opensource.org/licenses/MIT).
|
Tento projekt je licencovaný pod [MIT licenciou](https://opensource.org/licenses/MIT).
|
||||||
|
|||||||
201
api.php
201
api.php
@ -1,201 +0,0 @@
|
|||||||
<?php
|
|
||||||
include_once 'config.php';
|
|
||||||
|
|
||||||
$action = $_REQUEST['action'];
|
|
||||||
$result = null;
|
|
||||||
$error = null;
|
|
||||||
|
|
||||||
switch ($action) {
|
|
||||||
default:
|
|
||||||
case 'help':
|
|
||||||
$result = help();
|
|
||||||
break;
|
|
||||||
case 'add':
|
|
||||||
$report_id = reportAdd($_REQUEST['title'], $_REQUEST['description'], $_REQUEST['status'], $_REQUEST['group'], $_REQUEST['priority']);
|
|
||||||
if ($report_id === false) $error = 'Report add failed';
|
|
||||||
$result = array('report_id' => $report_id);
|
|
||||||
break;
|
|
||||||
case 'update':
|
|
||||||
$suc = reportUpdate($_REQUEST['report_id'], json_decode($_REQUEST['report_data'], true));
|
|
||||||
if ($suc === false) $error = 'Update failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
$suc = reportDelete($_REQUEST['report_id']);
|
|
||||||
if ($suc === false) $error = 'Update failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
case 'get':
|
|
||||||
$result = reportGet($_REQUEST['report_id']);
|
|
||||||
break;
|
|
||||||
case 'getAll':
|
|
||||||
$result = reportGetAll($_REQUEST['status']);
|
|
||||||
break;
|
|
||||||
case 'getAllGrouped':
|
|
||||||
$result = reportGetAllGrouped(json_decode($_REQUEST['status'], true), $_REQUEST['page'] == 'null' ? null : $_REQUEST['page']);
|
|
||||||
break;
|
|
||||||
case 'getArchived':
|
|
||||||
$result = reportGetArchived($_REQUEST['page'] == 'null' ? null : $_REQUEST['page']);
|
|
||||||
break;
|
|
||||||
case 'updateOrdNum':
|
|
||||||
$suc = reportUpdateOrdnum($_REQUEST['ordnums']);
|
|
||||||
if ($suc === false) $error = 'Update Ordnum failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
case 'updateStatus':
|
|
||||||
$suc = reportUpdateStatus($_REQUEST['report_id'], $_REQUEST['status']);
|
|
||||||
if ($suc === false) $error = 'Update Status failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
case 'attachmentAdd':
|
|
||||||
$suc = attachmentAdd($_REQUEST['report_id'], $_REQUEST['attachment_type'], $_REQUEST['attachment_content']);
|
|
||||||
if ($suc === false) $error = 'Attachment add failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
case 'attachmentUpdate':
|
|
||||||
$suc = attachmentUpdate($_REQUEST['attachment_id'], $_REQUEST['attachment_content']);
|
|
||||||
if ($suc === false) $error = 'Attachment update failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
case 'attachmentGetAll':
|
|
||||||
$result = attachmentGetAll($_REQUEST['report_id']);
|
|
||||||
break;
|
|
||||||
case 'attachmentDelete':
|
|
||||||
$suc = attachmentDelete($_REQUEST['attachment_id']);
|
|
||||||
if ($suc === false) $error = 'Attachment delete failed';
|
|
||||||
$result = array('processed' => $suc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*';
|
|
||||||
header('Access-Control-Allow-Origin: ' . $origin);
|
|
||||||
header('Access-Control-Allow-Credentials: true');
|
|
||||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
|
||||||
header('Access-Control-Allow-Headers: Origin, Content-Type, Accept');
|
|
||||||
echo json_encode(
|
|
||||||
is_null($error)
|
|
||||||
? array('status' => 'OK', 'data' => $result)
|
|
||||||
: array('status' => 'ERROR', 'msg' => $error),
|
|
||||||
);
|
|
||||||
exit;
|
|
||||||
|
|
||||||
function help()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'actions' => [
|
|
||||||
'help' => [
|
|
||||||
'name' => 'help',
|
|
||||||
'description' => 'Show this help',
|
|
||||||
'params' => []
|
|
||||||
],
|
|
||||||
'add' => [
|
|
||||||
'name' => 'add',
|
|
||||||
'description' => 'Add report',
|
|
||||||
'params' => [
|
|
||||||
'title' => 'Report title',
|
|
||||||
'description' => 'Report description',
|
|
||||||
'status' => 'Report status',
|
|
||||||
'group' => 'Report group',
|
|
||||||
'priority' => 'Report priority',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'update' => [
|
|
||||||
'name' => 'update',
|
|
||||||
'description' => 'Update report',
|
|
||||||
'params' => [
|
|
||||||
'report_id' => 'Report id',
|
|
||||||
'report_data' => [
|
|
||||||
'title' => 'Report title',
|
|
||||||
'description' => 'Report description',
|
|
||||||
'status' => 'Report status',
|
|
||||||
'group' => 'Report group',
|
|
||||||
'priority' => 'Report priority',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'delete' => [
|
|
||||||
'name' => 'delete',
|
|
||||||
'description' => 'Delete report',
|
|
||||||
'params' => [
|
|
||||||
'report_id' => 'Report id',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'get' => [
|
|
||||||
'name' => 'get',
|
|
||||||
'description' => 'Get report',
|
|
||||||
'params' => [
|
|
||||||
'report_id' => 'Report id',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'getAll' => [
|
|
||||||
'name' => 'getAll',
|
|
||||||
'description' => 'Get all reports',
|
|
||||||
'params' => [
|
|
||||||
'status' => '(ptional) Report status, default: 0,1,2,3',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'getAllGrouped' => [
|
|
||||||
'name' => 'getAllGrouped',
|
|
||||||
'description' => 'Get all reports grouped by group',
|
|
||||||
'params' => [
|
|
||||||
'status' => '(ptional) Report status, default: 0,1,2,3',
|
|
||||||
'page' => '(ptional) Page number, default: null = vsetky',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'getArchived' => [
|
|
||||||
'name' => 'getArchived',
|
|
||||||
'description' => 'Get archived reports',
|
|
||||||
'params' => [
|
|
||||||
'page' => '(ptional) Page number, default: null = vsetky',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'updateOrdNum' => [
|
|
||||||
'name' => 'updateordnum',
|
|
||||||
'description' => 'Update report ordnum',
|
|
||||||
'params' => [
|
|
||||||
'ordnums' => 'Report ordnums in json format {report_id: ordnum, ...}',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'updateStatus' => [
|
|
||||||
'name' => 'updatestatus',
|
|
||||||
'description' => 'Update report status',
|
|
||||||
'params' => [
|
|
||||||
'report_id' => 'Report id',
|
|
||||||
'status' => 'Report status',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'attachmentAdd' => [
|
|
||||||
'name' => 'attachmentAdd',
|
|
||||||
'description' => 'Add attachment to report',
|
|
||||||
'params' => [
|
|
||||||
'report_id' => 'Report id',
|
|
||||||
'content_type' => 'Attachment content type',
|
|
||||||
'content' => 'Attachment content',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'attachmentUpdate' => [
|
|
||||||
'name' => 'attachmentUpdate',
|
|
||||||
'description' => 'Update attachment',
|
|
||||||
'params' => [
|
|
||||||
'attachment_id' => 'Attachment id',
|
|
||||||
'content' => 'Attachment content; if empty, attachment will be deleted',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'attachmentGetAll' => [
|
|
||||||
'name' => 'attachmentGetAll',
|
|
||||||
'description' => 'Get all attachments for report',
|
|
||||||
'params' => [
|
|
||||||
'report_id' => 'Report id',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'attachmentGet' => [
|
|
||||||
'name' => 'attachmentGet',
|
|
||||||
'description' => 'Get attachment',
|
|
||||||
'params' => [
|
|
||||||
'attachment_id' => 'Attachment id',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
16
backend/composer.lock
generated
16
backend/composer.lock
generated
@ -8,11 +8,11 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "tpsoft/apilite",
|
"name": "tpsoft/apilite",
|
||||||
"version": "v1.0.2",
|
"version": "v1.0.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.tpsoft.org/TPsoft.org/APIlite.git",
|
"url": "https://gitea.tpsoft.org/TPsoft.org/APIlite.git",
|
||||||
"reference": "2d3f8bfdd46d0d304bac44057ff8444bfb2a4a17"
|
"reference": "c0fd7b3fe5270ee44a84a92e9255ada2438812b7"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.2"
|
"php": ">=8.2"
|
||||||
@ -49,15 +49,15 @@
|
|||||||
"type": "other"
|
"type": "other"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-06-12T05:54:37+00:00"
|
"time": "2025-10-13T21:36:34+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tpsoft/dbmodel",
|
"name": "tpsoft/dbmodel",
|
||||||
"version": "v1.0.4",
|
"version": "v1.0.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.tpsoft.org/TPsoft.org/DBmodel.git",
|
"url": "https://gitea.tpsoft.org/TPsoft.org/DBmodel.git",
|
||||||
"reference": "ae59ffaa97094854bcd1d863c6648a5b4dada671"
|
"reference": "80e889946bc4e38e987f46a13f95ee177ea934dc"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
@ -85,7 +85,9 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"db",
|
"db",
|
||||||
"model",
|
"model",
|
||||||
"pdo"
|
"mysql",
|
||||||
|
"pdo",
|
||||||
|
"sqlite"
|
||||||
],
|
],
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -93,7 +95,7 @@
|
|||||||
"type": "other"
|
"type": "other"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-06-15T17:07:42+00:00"
|
"time": "2025-10-13T21:26:19+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
|||||||
@ -4,7 +4,7 @@ require __DIR__ . '/../vendor/autoload.php';
|
|||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
$backend_api = new TPsoft\BugreportBackend\API('typescript', 'import.meta.env.VITE_BACKENDAPI_URL');
|
$backend_api = new TPsoft\BugreportBackend\API('typescript', 'import.meta.env.VITE_BACKENDAPI_URL', 'backend');
|
||||||
|
|
||||||
$output = ob_get_contents();
|
$output = ob_get_contents();
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
|
|||||||
@ -187,7 +187,7 @@ class API extends APIlite
|
|||||||
if (!is_array($data)) return false;
|
if (!is_array($data)) return false;
|
||||||
$base64 = preg_replace('/^data:.*?;base64,/', '', $data['base64']);
|
$base64 = preg_replace('/^data:.*?;base64,/', '', $data['base64']);
|
||||||
$base64_data = base64_decode($base64);
|
$base64_data = base64_decode($base64);
|
||||||
$filename = 'report_' . $report_id . '_' . time() . '_' . sanitizeFilename($data['filename']);
|
$filename = 'report_' . $report_id . '_' . time() . '_' . $this->sanitizeFilename($data['filename']);
|
||||||
file_put_contents(UPLOAD_DIR_ATTACHMENTS . $filename, $base64_data);
|
file_put_contents(UPLOAD_DIR_ATTACHMENTS . $filename, $base64_data);
|
||||||
$attachment_content = $filename;
|
$attachment_content = $filename;
|
||||||
}
|
}
|
||||||
@ -201,6 +201,26 @@ class API extends APIlite
|
|||||||
return $suc !== false;
|
return $suc !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function sanitizeFilename($filename, $allowedExtensions = [])
|
||||||
|
{
|
||||||
|
// Rozdelenie názvu a prípony
|
||||||
|
$pathInfo = pathinfo($filename);
|
||||||
|
$name = $pathInfo['filename'] ?? 'file';
|
||||||
|
$extension = strtolower($pathInfo['extension'] ?? '');
|
||||||
|
// Odstránenie nebezpečných znakov z názvu
|
||||||
|
$name = preg_replace('/[^a-zA-Z0-9_-]/', '_', $name);
|
||||||
|
$name = substr($name, 0, 100); // voliteľné obmedzenie dĺžky
|
||||||
|
// Validácia prípony, ak je zoznam povolený
|
||||||
|
if (
|
||||||
|
$allowedExtensions
|
||||||
|
&& count($allowedExtensions) > 0
|
||||||
|
&& !in_array($extension, $allowedExtensions)
|
||||||
|
) {
|
||||||
|
$extension = 'bin'; // fallback ak prípona nie je povolená
|
||||||
|
}
|
||||||
|
return $name . '.' . $extension;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update report attachment
|
* Update report attachment
|
||||||
*
|
*
|
||||||
@ -236,7 +256,7 @@ class API extends APIlite
|
|||||||
->toArray();
|
->toArray();
|
||||||
if (is_array($all)) foreach ($all as $key => $row) {
|
if (is_array($all)) foreach ($all as $key => $row) {
|
||||||
if ($all[$key]['attachment_type'] == 'file') {
|
if ($all[$key]['attachment_type'] == 'file') {
|
||||||
$all[$key]['attachment_content'] = UPLOAD_URL_ATTACHMENTS . $all[$key]['attachment_content'];
|
$all[$key]['attachment_content'] = '?action=attachmentDownload&filename=' . $all[$key]['attachment_content'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $all;
|
return $all;
|
||||||
@ -254,4 +274,28 @@ class API extends APIlite
|
|||||||
$suc = $attachments->attachment($attachment_id, null);
|
$suc = $attachments->attachment($attachment_id, null);
|
||||||
return $suc !== false;
|
return $suc !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download report attachment
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function attachmentDownload(string $filename): void {
|
||||||
|
$filename = $this->sanitizeFilename($filename);
|
||||||
|
$filename = UPLOAD_DIR_ATTACHMENTS . $filename;
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
header('Content-Description: File Transfer');
|
||||||
|
header('Content-Type: application/octet-stream');
|
||||||
|
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
|
||||||
|
header('Expires: 0');
|
||||||
|
header('Cache-Control: must-revalidate');
|
||||||
|
header('Pragma: public');
|
||||||
|
header('Content-Length: ' . filesize($filename));
|
||||||
|
readfile($filename);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ require __DIR__ . '/../vendor/autoload.php';
|
|||||||
|
|
||||||
use \Exception;
|
use \Exception;
|
||||||
use \TPsoft\DBmodel\DBmodel;
|
use \TPsoft\DBmodel\DBmodel;
|
||||||
|
use \TPsoft\BugreportBackend\Maintenance;
|
||||||
|
|
||||||
global $dbh;
|
global $dbh;
|
||||||
|
|
||||||
@ -14,20 +15,6 @@ if (Configuration::DB_TYPE == 'mysql') {
|
|||||||
} else {
|
} else {
|
||||||
throw new Exception('Unknown database type');
|
throw new Exception('Unknown database type');
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
$dbh->tables = [
|
$maintenance = new Maintenance($dbh);
|
||||||
'reports' => [
|
$maintenance->database();
|
||||||
'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()',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
*/
|
|
||||||
|
|||||||
81
backend/src/Maintenance.php
Normal file
81
backend/src/Maintenance.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace TPsoft\BugreportBackend;
|
||||||
|
|
||||||
|
class Maintenance extends \TPsoft\DBmodel\Maintenance
|
||||||
|
{
|
||||||
|
|
||||||
|
public function database()
|
||||||
|
{
|
||||||
|
if (!$this->existsTable('options')) {
|
||||||
|
$this->checkDBTable('options', '
|
||||||
|
`key` VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||||
|
`value` VARCHAR(255) NOT NULL
|
||||||
|
');
|
||||||
|
$this->dbver(1);
|
||||||
|
}
|
||||||
|
$dbver = $this->dbver();
|
||||||
|
if ($dbver == 1) {
|
||||||
|
$this->checkDBTable('reports', '
|
||||||
|
`report_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`report_title` varchar(255) DEFAULT NULL,
|
||||||
|
`report_description` text DEFAULT NULL,
|
||||||
|
`report_status` int(11) DEFAULT NULL,
|
||||||
|
`report_group` varchar(255) NOT NULL,
|
||||||
|
`report_priority` int(11) DEFAULT NULL,
|
||||||
|
`ordnum` int(11) DEFAULT NULL,
|
||||||
|
`created_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
');
|
||||||
|
$this->dbver(2);
|
||||||
|
$dbver = 2;
|
||||||
|
}
|
||||||
|
if ($dbver == 2) {
|
||||||
|
$this->checkDBTable('attachments', '
|
||||||
|
`attachment_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`report_id` int(11) NOT NULL,
|
||||||
|
`attachment_type` varchar(255) DEFAULT NULL,
|
||||||
|
`attachment_content` text DEFAULT NULL,
|
||||||
|
`created_dt` datetime DEFAULT NULL,
|
||||||
|
`updated_dt` datetime DEFAULT NULL
|
||||||
|
');
|
||||||
|
$this->dbver(3);
|
||||||
|
$dbver = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function settings(string $key, ?string $value = null): string|false
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return $this->dbh->getOne(sprintf('SELECT `value` FROM `options` WHERE `key` = %s', $this->dbh->quote($key)));
|
||||||
|
} else {
|
||||||
|
$db_type = $this->dbh->getDBtype();
|
||||||
|
switch ($db_type) {
|
||||||
|
case 'mysql':
|
||||||
|
return $this->dbh->query(sprintf(
|
||||||
|
'INSERT INTO `options` (`key`, `value`) VALUES (%s, %s) ON DUPLICATE KEY UPDATE `value` = %s',
|
||||||
|
$this->dbh->quote($key),
|
||||||
|
$this->dbh->quote($value),
|
||||||
|
$this->dbh->quote($value)
|
||||||
|
)) !== false;
|
||||||
|
break;
|
||||||
|
case 'sqlite':
|
||||||
|
return $this->dbh->query(sprintf(
|
||||||
|
'INSERT INTO `options` (`key`, `value`) VALUES (%s, %s) ON CONFLICT(`key`) DO UPDATE SET `value` = %s',
|
||||||
|
$this->dbh->quote($key),
|
||||||
|
$this->dbh->quote($value),
|
||||||
|
$this->dbh->quote($value)
|
||||||
|
)) !== false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
new \Exception('Unknown DB type: ' . $db_type);
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function dbver(?string $ver = null)
|
||||||
|
{
|
||||||
|
return $this->settings('version', $ver);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
build.bat
Normal file
46
build.bat
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
echo ➡️ Delete old DIST folder
|
||||||
|
rmdir /s /q "dist"
|
||||||
|
echo ✅ Old DIST deleted
|
||||||
|
|
||||||
|
echo ➡️ Build Backend
|
||||||
|
cd backend
|
||||||
|
call composer run build
|
||||||
|
cd ..
|
||||||
|
echo ✅ Backend built
|
||||||
|
|
||||||
|
echo ➡️ Build frontend
|
||||||
|
cd frontend
|
||||||
|
call npm run build
|
||||||
|
cd ..
|
||||||
|
echo ✅ Frontend built
|
||||||
|
|
||||||
|
echo ➡️ Make new DIST folder structure
|
||||||
|
mkdir "dist"
|
||||||
|
mkdir "dist\data"
|
||||||
|
mkdir "dist\data\attachments"
|
||||||
|
mkdir "dist\app"
|
||||||
|
echo ✅ New DIST structur created
|
||||||
|
|
||||||
|
echo ➡️ Copy APP files
|
||||||
|
robocopy backend dist\app /S /XD tests scripts /XF composer.*
|
||||||
|
robocopy frontend\dist dist\app\public /S
|
||||||
|
echo ✅ APP files copied
|
||||||
|
|
||||||
|
echo ➡️ Packaging build
|
||||||
|
for /f "tokens=2 delims=:" %%a in ('findstr /c:"\"version\"" frontend\package.json') do (
|
||||||
|
set ver=%%a
|
||||||
|
set ver=!ver: =!
|
||||||
|
set ver=!ver:"=!
|
||||||
|
set ver=!ver:,=!
|
||||||
|
)
|
||||||
|
echo 👉 Version: %ver%
|
||||||
|
cd dist
|
||||||
|
tar -a -c -f ..\build\BugReport.%ver%.zip *
|
||||||
|
cd ..
|
||||||
|
echo ✅ Build packaged
|
||||||
|
|
||||||
|
echo 🚀 Done.
|
||||||
31
config.php
31
config.php
@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
if (file_exists('c:/php/includes/igor.php')) {
|
|
||||||
require_once 'c:/php/includes/igor.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__.'/lib/functions.inc.php';
|
|
||||||
require_once __DIR__.'/lib/Medoo/src/Medoo.php';
|
|
||||||
|
|
||||||
$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/');
|
|
||||||
|
|
||||||
global $db;
|
|
||||||
$db = new Medoo\Medoo([
|
|
||||||
'type' => 'sqlite',
|
|
||||||
'database' => __DIR__ . '/data/database.db'
|
|
||||||
]);
|
|
||||||
dbCheck();
|
|
||||||
|
|
||||||
|
|
||||||
?>
|
|
||||||
21
doc/notes.txt
Normal file
21
doc/notes.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
projekt/
|
||||||
|
│
|
||||||
|
├── backend/ # PHP časť (API, logika, DB)
|
||||||
|
│ ├── public/ # root pre webserver (index.php, assets)
|
||||||
|
│ │ └── index.php
|
||||||
|
│ ├── src/ # zdrojový PHP kód (kontroléry, modely, služby)
|
||||||
|
│ ├── vendor/ # Composer balíčky (gitignore!)
|
||||||
|
│ ├── composer.json
|
||||||
|
│ └── composer.lock
|
||||||
|
│
|
||||||
|
├── frontend/ # Vue časť
|
||||||
|
│ ├── src/ # Vue komponenty, views, store
|
||||||
|
│ ├── public/ # statické súbory (favicon, index.html template)
|
||||||
|
│ ├── dist/ # build výstup (gitignore!) → deploy do backend/public
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── package-lock.json
|
||||||
|
│ └── vite.config.js / vue.config.js
|
||||||
|
│
|
||||||
|
├── docker/ (voliteľné) # ak používaš Docker (Nginx, PHP-FPM, Node build)
|
||||||
|
│
|
||||||
|
└── README.md
|
||||||
1
frontend/.env.development
Normal file
1
frontend/.env.development
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_BACKENDAPI_URL="https://192.168.0.101/BugReport/backend/public/API.php"
|
||||||
1
frontend/.env.production
Normal file
1
frontend/.env.production
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_BACKENDAPI_URL="/API.php"
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "bugreport",
|
"name": "bugreport",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div id="header">
|
<div id="header">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<img src="/public/bugreport.svg" height="48" width="48" />
|
<img src="/bugreport.svg" height="48" width="48" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<h1>Bug Report</h1>
|
<h1>Bug Report</h1>
|
||||||
|
|||||||
@ -265,6 +265,7 @@ button:focus-visible,
|
|||||||
#dashboard .report .report-description {
|
#dashboard .report .report-description {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
|
white-space: pre-line;
|
||||||
border-right: 5px var(--color-bg0) solid;
|
border-right: 5px var(--color-bg0) solid;
|
||||||
background-color: var(--color-bg2);
|
background-color: var(--color-bg2);
|
||||||
}
|
}
|
||||||
@ -462,6 +463,36 @@ button:focus-visible,
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
|
#api ul li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
#api .param-type {
|
||||||
|
background-color: var(--color-bg0);
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
#api .param-optional {
|
||||||
|
background-color: var(--color-bg1);
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
#api .param-default {
|
||||||
|
background-color: var(--color-bg2);
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
#api .param-doc {
|
||||||
|
color: var(--color-text3);
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------
|
/* ----------------------------------------------------
|
||||||
07 - ABOUT
|
07 - ABOUT
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
* Generated by APIlite
|
* Generated by APIlite
|
||||||
* https://gitea.tpsoft.org/TPsoft.org/APIlite
|
* https://gitea.tpsoft.org/TPsoft.org/APIlite
|
||||||
*
|
*
|
||||||
* 2025-10-01 00:23:37 */
|
* 2025-10-16 01:38:51 */
|
||||||
|
|
||||||
class backend {
|
class backend {
|
||||||
endpont = import.meta.env.VITE_BACKENDAPI_URL;
|
endpoint = import.meta.env.VITE_BACKENDAPI_URL;
|
||||||
|
|
||||||
/* ----------------------------------------------------
|
/* ----------------------------------------------------
|
||||||
* General API call
|
* General API call
|
||||||
@ -28,7 +28,7 @@ class backend {
|
|||||||
if (typeof val == 'object') val = JSON.stringify(val);
|
if (typeof val == 'object') val = JSON.stringify(val);
|
||||||
form_data.append(key, val);
|
form_data.append(key, val);
|
||||||
});
|
});
|
||||||
xhttp.open('POST', this.endpont + '?action=' + method);
|
xhttp.open('POST', this.endpoint + '?action=' + method);
|
||||||
xhttp.send(form_data);
|
xhttp.send(form_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +107,10 @@ class backend {
|
|||||||
return this.callPromise('attachmentDelete', {attachment_id: attachment_id});
|
return this.callPromise('attachmentDelete', {attachment_id: attachment_id});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachmentDownload(filename) {
|
||||||
|
return this.callPromise('attachmentDownload', {filename: filename});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -24,51 +24,42 @@
|
|||||||
<h4>Parametre</h4>
|
<h4>Parametre</h4>
|
||||||
<p v-if="Object.keys(action.params).length == 0">
|
<p v-if="Object.keys(action.params).length == 0">
|
||||||
<font-awesome-icon :icon="['fas', 'circle-info']" />
|
<font-awesome-icon :icon="['fas', 'circle-info']" />
|
||||||
|
Ziadne parametre
|
||||||
Ziadne parametre
|
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(param_desc, param_name) in action.params" :key="param_name">
|
<li v-for="param in action.params" :key="param_name">
|
||||||
<strong>{{ param_name }}</strong>
|
<strong>{{ param.name }}</strong>
|
||||||
–
|
<span class="param-type">{{ param.type }}</span>
|
||||||
{{ param_desc }}
|
<span v-if="param.optional" class="param-optional">optional</span>
|
||||||
|
<span v-if="param.default != null" class="param-default">{{
|
||||||
|
param.default
|
||||||
|
}}</span>
|
||||||
|
<br />
|
||||||
|
<span class="param-doc">{{ param.doc }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Return</strong>
|
||||||
|
<span class="param-type">{{ action.return }}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
import backend from "../backend";
|
import backend from "../backend";
|
||||||
|
|
||||||
export default {
|
const api_endpoint = ref(backend.endpont);
|
||||||
name: "API",
|
const help = ref({ actions: {} });
|
||||||
components: {},
|
|
||||||
data() {
|
onMounted(() => {
|
||||||
return {
|
loadHelp();
|
||||||
api_endpoint: backend.endpont,
|
|
||||||
help: {
|
|
||||||
actions: {
|
|
||||||
help: {
|
|
||||||
name: "help",
|
|
||||||
description: "This help",
|
|
||||||
params: {
|
|
||||||
foo: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.loadHelp();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadHelp() {
|
|
||||||
backend.help().then((response) => {
|
|
||||||
this.help = response;
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
},
|
function loadHelp() {
|
||||||
};
|
backend.help().then((response) => {
|
||||||
|
help.value = response;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -89,9 +89,9 @@
|
|||||||
v-else-if="attachment.attachment_type == 'file'"
|
v-else-if="attachment.attachment_type == 'file'"
|
||||||
class="attachment-file"
|
class="attachment-file"
|
||||||
>
|
>
|
||||||
<a :href="attachment.attachment_content" target="_blank">Stiahnut {{ attachment.attachment_content.split('/').pop().split('?')[0].split('#')[0] }}</a>
|
<a :href="backend.endpoint + attachment.attachment_content" target="_blank">Stiahnut {{ attachment.attachment_content.split('/').pop().split('?')[0].split('#')[0] }}</a>
|
||||||
<br>
|
<br>
|
||||||
<img :src="attachment.attachment_content" v-if="isImageUrl(attachment.attachment_content)" />
|
<img :src="backend.endpoint + attachment.attachment_content" v-if="isImageUrl(attachment.attachment_content)" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="attachment-content">
|
<div v-else class="attachment-content">
|
||||||
Neznamy typ prilohy: <strong>{{ attachment.attachment_type }}</strong>
|
Neznamy typ prilohy: <strong>{{ attachment.attachment_type }}</strong>
|
||||||
@ -102,7 +102,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="description">Nový komentár:</label>
|
<label for="description">Nový komentár:</label>
|
||||||
<textarea
|
<textarea
|
||||||
ref="attachmentNewContent"
|
v-model="attachmentNewContent"
|
||||||
rows="5"
|
rows="5"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Nove zistenia alebo riesenia"
|
placeholder="Nove zistenia alebo riesenia"
|
||||||
@ -144,7 +144,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { router } from "../router";
|
||||||
import backend from "../backend";
|
import backend from "../backend";
|
||||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||||
import JSConfetti from 'js-confetti'
|
import JSConfetti from 'js-confetti'
|
||||||
@ -152,14 +154,9 @@ import JSConfetti from 'js-confetti'
|
|||||||
let tadas = ['/sounds/tada.mp3', '/sounds/tada2.mp3', '/sounds/crazy-phrog-short.mp3'];
|
let tadas = ['/sounds/tada.mp3', '/sounds/tada2.mp3', '/sounds/crazy-phrog-short.mp3'];
|
||||||
const jungle = new Audio(tadas[Math.floor(Math.random() * tadas.length)]);
|
const jungle = new Audio(tadas[Math.floor(Math.random() * tadas.length)]);
|
||||||
|
|
||||||
export default {
|
const loading = ref(false);
|
||||||
name: "Report",
|
const report_id = router.currentRoute.value.params.id;
|
||||||
components: { FullScreenLoader },
|
const report = ref({
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
report_id: this.$route.params.id,
|
|
||||||
report: {
|
|
||||||
report_id: 0,
|
report_id: 0,
|
||||||
report_title: "Nacitavam report",
|
report_title: "Nacitavam report",
|
||||||
report_description: "...",
|
report_description: "...",
|
||||||
@ -168,137 +165,147 @@ export default {
|
|||||||
report_priority: 1,
|
report_priority: 1,
|
||||||
created_dt: "--",
|
created_dt: "--",
|
||||||
ordnum: 0,
|
ordnum: 0,
|
||||||
},
|
});
|
||||||
editable: true,
|
const editable = ref(true);
|
||||||
attachments: [
|
const attachments = ref([
|
||||||
{
|
{
|
||||||
attachment_id: 0,
|
attachment_id: 0,
|
||||||
attachment_type: "comment",
|
attachment_type: "comment",
|
||||||
attachment_content: "Nacitavam report",
|
attachment_content: "Nacitavam report",
|
||||||
created_dt: "--",
|
created_dt: "--",
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
attachmentNewFiles: null,
|
const attachmentNewContent = ref(null);
|
||||||
selectedFiles: [],
|
const attachmentNewFiles = ref(null);
|
||||||
selectedFilesContent: [],
|
const selectedFiles = ref([]);
|
||||||
};
|
const selectedFilesContent = ref([]);
|
||||||
},
|
|
||||||
mounted() {
|
onMounted(() => {
|
||||||
// console.log(this.report_id);
|
// console.log(report_id);
|
||||||
this.loadReportData();
|
loadReportData();
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadReportData() {
|
|
||||||
backend.get(this.report_id).then((response) => {
|
|
||||||
this.report = response.data;
|
|
||||||
// console.log(this.report);
|
|
||||||
this.editable = response.data.report_status < 4;
|
|
||||||
});
|
});
|
||||||
backend.attachmentGetAll(this.report_id).then((response) => {
|
|
||||||
this.attachments = response.data;
|
function loadReportData() {
|
||||||
// console.log(this.attachments);
|
backend.get(report_id).then((response) => {
|
||||||
|
report.value = response.data;
|
||||||
|
// console.log(report);
|
||||||
|
editable.value = response.data.report_status < 4;
|
||||||
});
|
});
|
||||||
},
|
backend.attachmentGetAll(report_id).then((response) => {
|
||||||
onTitleChange(event) {
|
attachments.value = response.data;
|
||||||
backend.update(this.report_id, { report_title: event.target.innerText });
|
// console.log(attachments);
|
||||||
},
|
});
|
||||||
onDescriptionChange(event) {
|
}
|
||||||
backend.update(this.report_id, {
|
|
||||||
|
function onTitleChange(event) {
|
||||||
|
backend.update(report_id, { report_title: event.target.innerText });
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDescriptionChange(event) {
|
||||||
|
backend.update(report_id, {
|
||||||
report_description: event.target.innerText,
|
report_description: event.target.innerText,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
reportDelete() {
|
|
||||||
|
function reportDelete() {
|
||||||
if (!confirm("Naozaj chcete report zmazať?")) return;
|
if (!confirm("Naozaj chcete report zmazať?")) return;
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
backend.delete(this.report_id).then(() => {
|
backend.delete(report_id).then(() => {
|
||||||
this.$router.push("/");
|
router.push("/");
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
reportDone() {
|
|
||||||
backend.update(this.report_id, {
|
function reportDone() {
|
||||||
|
backend.update(report_id, {
|
||||||
report_status: 4,
|
report_status: 4,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
jungle.play();
|
jungle.play();
|
||||||
const confetti = new JSConfetti();
|
const confetti = new JSConfetti();
|
||||||
confetti.addConfetti();
|
confetti.addConfetti();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$router.push("/");
|
router.push("/");
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
attachmentAdd() {
|
|
||||||
let comment = this.$refs.attachmentNewContent.value;
|
function attachmentAdd() {
|
||||||
|
let comment = attachmentNewContent.value;
|
||||||
if (comment.trim().length > 0) {
|
if (comment.trim().length > 0) {
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
backend
|
backend
|
||||||
.attachmentAdd(
|
.attachmentAdd(
|
||||||
this.report_id,
|
report_id,
|
||||||
"comment",
|
"comment",
|
||||||
this.$refs.attachmentNewContent.value
|
attachmentNewContent.value
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$refs.attachmentNewContent.value = "";
|
attachmentNewContent.value = "";
|
||||||
this.loadReportData();
|
loadReportData();
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.selectedFiles.length > 0) {
|
if (selectedFiles.value.length > 0) {
|
||||||
let for_upload = this.selectedFiles.length;
|
let for_upload = selectedFiles.value.length;
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
for (let i = 0; i < this.selectedFiles.length; i++) {
|
for (let i = 0; i < selectedFiles.value.length; i++) {
|
||||||
backend
|
backend
|
||||||
.attachmentAdd(this.report_id, "file", {
|
.attachmentAdd(report_id, "file", {
|
||||||
'filename': this.selectedFiles[i].name,
|
'filename': selectedFiles.value[i].name,
|
||||||
'base64': this.selectedFilesContent[i]
|
'base64': selectedFilesContent.value[i]
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
for_upload--;
|
for_upload--;
|
||||||
if (for_upload == 0) {
|
if (for_upload == 0) {
|
||||||
this.selectedFiles = [];
|
selectedFiles.value = [];
|
||||||
this.selectedFilesContent = [];
|
selectedFilesContent.value = [];
|
||||||
this.$refs.attachmentNewFiles.value = null;
|
attachmentNewFiles.value = null;
|
||||||
this.loadReportData();
|
loadReportData();
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
attachmentDelete(attachment) {
|
|
||||||
|
function attachmentDelete(attachment) {
|
||||||
if (!confirm("Naozaj chcete zmazať prilohu?")) return;
|
if (!confirm("Naozaj chcete zmazať prilohu?")) return;
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
backend.attachmentDelete(attachment.attachment_id).then(() => {
|
backend.attachmentDelete(attachment.attachment_id).then(() => {
|
||||||
this.loadReportData();
|
loadReportData();
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
updateAttachmentContent(event, attachment) {
|
|
||||||
this.loading = true;
|
function updateAttachmentContent(event, attachment) {
|
||||||
|
loading.value = true;
|
||||||
backend
|
backend
|
||||||
.attachmentUpdate(attachment.attachment_id, event.target.innerText)
|
.attachmentUpdate(attachment.attachment_id, event.target.innerText)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loadReportData();
|
loadReportData();
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
handleFileUpload(event) {
|
|
||||||
|
function handleFileUpload(event) {
|
||||||
const files = event.target.files;
|
const files = event.target.files;
|
||||||
if (files) {
|
if (files) {
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
this.selectedFiles.push(files[i]);
|
selectedFiles.value.push(files[i]);
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
this.selectedFilesContent[i] = reader.result;
|
selectedFilesContent.value[i] = reader.result;
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(files[i]);
|
reader.readAsDataURL(files[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
removeFile(index) {
|
|
||||||
this.selectedFiles.splice(index, 1);
|
function removeFile(index) {
|
||||||
this.selectedFilesContent.splice(index, 1);
|
selectedFiles.value.splice(index, 1);
|
||||||
},
|
selectedFilesContent.value.splice(index, 1);
|
||||||
formatFileSize(size) {
|
}
|
||||||
|
|
||||||
|
function formatFileSize(size) {
|
||||||
if (size < 1024) {
|
if (size < 1024) {
|
||||||
return size + " B";
|
return size + " B";
|
||||||
} else if (size < 1024 * 1024) {
|
} else if (size < 1024 * 1024) {
|
||||||
@ -306,10 +313,10 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return (size / (1024 * 1024)).toFixed(2) + " MB";
|
return (size / (1024 * 1024)).toFixed(2) + " MB";
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
isImageUrl(url) {
|
|
||||||
|
function isImageUrl(url) {
|
||||||
return /\.(jpg|jpeg|png|gif|svg|webp)$/.test(url);
|
return /\.(jpg|jpeg|png|gif|svg|webp)$/.test(url);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ const require = createRequire(import.meta.url);
|
|||||||
const pkg = require("./package.json");
|
const pkg = require("./package.json");
|
||||||
|
|
||||||
const subpath = "/bugreport/";
|
const subpath = "/bugreport/";
|
||||||
export const baseUrl = subpath + "webapp/dist/";
|
export const baseUrl = "/"; //subpath + "frontend/dist/";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig(({ command, mode }) => {
|
export default defineConfig(({ command, mode }) => {
|
||||||
@ -25,6 +25,10 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
__IS_BUILD__: JSON.stringify(isBuild),
|
__IS_BUILD__: JSON.stringify(isBuild),
|
||||||
__IS_DEV__: JSON.stringify(isDev),
|
__IS_DEV__: JSON.stringify(isDev),
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 5173
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
chunkSizeWarningLimit: 1000, // zvýšenie limitu na 1000 kB
|
chunkSizeWarningLimit: 1000, // zvýšenie limitu na 1000 kB
|
||||||
|
|||||||
11
index.php
11
index.php
@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>BugReport</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Submodule lib/Medoo deleted from 2613656761
@ -1,344 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String functions
|
|
||||||
*/
|
|
||||||
function allowedChars($str, $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-')
|
|
||||||
{
|
|
||||||
return preg_match('/^[' . $chars . ']+$/', $str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeFilename($filename, $allowedExtensions = [])
|
|
||||||
{
|
|
||||||
// Rozdelenie názvu a prípony
|
|
||||||
$pathInfo = pathinfo($filename);
|
|
||||||
$name = $pathInfo['filename'] ?? 'file';
|
|
||||||
$extension = strtolower($pathInfo['extension'] ?? '');
|
|
||||||
// Odstránenie nebezpečných znakov z názvu
|
|
||||||
$name = preg_replace('/[^a-zA-Z0-9_-]/', '_', $name);
|
|
||||||
$name = substr($name, 0, 100); // voliteľné obmedzenie dĺžky
|
|
||||||
// Validácia prípony, ak je zoznam povolený
|
|
||||||
if (
|
|
||||||
$allowedExtensions
|
|
||||||
&& count($allowedExtensions) > 0
|
|
||||||
&& !in_array($extension, $allowedExtensions)
|
|
||||||
) {
|
|
||||||
$extension = 'bin'; // fallback ak prípona nie je povolená
|
|
||||||
}
|
|
||||||
return $name . '.' . $extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check database
|
|
||||||
*/
|
|
||||||
function dbCheck()
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$db_version = option('version');
|
|
||||||
if ($db_version === null) {
|
|
||||||
$db->create('options', [
|
|
||||||
'key' => [
|
|
||||||
'VARCHAR(64)',
|
|
||||||
'NOT NULL',
|
|
||||||
'UNIQUE'
|
|
||||||
],
|
|
||||||
'value' => [
|
|
||||||
'TEXT',
|
|
||||||
'NOT NULL'
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'DATETIME',
|
|
||||||
'DEFAULT CURRENT_TIMESTAMP'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
option('version', '0');
|
|
||||||
$db_version = '0';
|
|
||||||
}
|
|
||||||
if ($db_version === '0') {
|
|
||||||
$db->create('reports', [
|
|
||||||
'report_id' => [
|
|
||||||
'INTEGER',
|
|
||||||
'PRIMARY KEY',
|
|
||||||
'AUTOINCREMENT'
|
|
||||||
],
|
|
||||||
'report_title' => [
|
|
||||||
'VARCHAR(255)',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
'report_description' => [
|
|
||||||
'TEXT',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
'report_status' => [
|
|
||||||
'INTEGER',
|
|
||||||
'DEFAULT 0'
|
|
||||||
],
|
|
||||||
'report_group' => [
|
|
||||||
'VARCHAR(255)',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
'report_priority' => [
|
|
||||||
'INTEGER',
|
|
||||||
'DEFAULT 0'
|
|
||||||
],
|
|
||||||
'created_dt' => [
|
|
||||||
'DATETIME',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
option('version', '1');
|
|
||||||
$db_version = '1';
|
|
||||||
}
|
|
||||||
if ($db_version === '1') {
|
|
||||||
$db->create('attachments', [
|
|
||||||
'attachment_id' => [
|
|
||||||
'INTEGER',
|
|
||||||
'PRIMARY KEY',
|
|
||||||
'AUTOINCREMENT'
|
|
||||||
],
|
|
||||||
'report_id' => [
|
|
||||||
'INTEGER',
|
|
||||||
'NOT NULL'
|
|
||||||
],
|
|
||||||
'attachment_type' => [
|
|
||||||
'VARCHAR(255)',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
'attachment_content' => [
|
|
||||||
'TEXT',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
'created_dt' => [
|
|
||||||
'DATETIME',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
'updated_dt' => [
|
|
||||||
'DATETIME',
|
|
||||||
'DEFAULT NULL'
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
option('version', '2');
|
|
||||||
$db_version = '2';
|
|
||||||
}
|
|
||||||
if ($db_version === '2') {
|
|
||||||
$db->query("ALTER TABLE reports ADD COLUMN ordnum INTEGER DEFAULT 0");
|
|
||||||
option('version', '3');
|
|
||||||
$db_version = '3';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function option($key, $value = null)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
if (tableExits('options') === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if ($value === null) {
|
|
||||||
return $db->get('options', 'value', [
|
|
||||||
'key' => $key
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$exits = $db->get('options', 'value', [
|
|
||||||
'key' => $key
|
|
||||||
]);
|
|
||||||
if ($exits !== null) {
|
|
||||||
return $db->update('options', [
|
|
||||||
'value' => $value
|
|
||||||
], [
|
|
||||||
'key' => $key
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return $db->insert('options', [
|
|
||||||
'key' => $key,
|
|
||||||
'value' => $value
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tableExits($table)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
return $db->get('sqlite_master', 'name', [
|
|
||||||
'type' => 'table',
|
|
||||||
'name' => $table
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports
|
|
||||||
*/
|
|
||||||
function reportAdd($title, $description, $status = 0, $group = null, $priority = 0)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$status = intval($status);
|
|
||||||
$priority = intval($priority);
|
|
||||||
$db->insert('reports', [
|
|
||||||
'report_title' => $title,
|
|
||||||
'report_description' => $description,
|
|
||||||
'report_status' => $status,
|
|
||||||
'report_group' => $group,
|
|
||||||
'report_priority' => $priority,
|
|
||||||
'created_dt' => date('Y-m-d H:i:s')
|
|
||||||
]);
|
|
||||||
return $db->id();
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportUpdate($report_id, $report_data)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$stm = $db->update('reports', $report_data, [
|
|
||||||
'report_id' => $report_id
|
|
||||||
]);
|
|
||||||
return ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportUpdateStatus($report_id, $status)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$stm = $db->update('reports', [
|
|
||||||
'report_status' => $status
|
|
||||||
], [
|
|
||||||
'report_id' => $report_id
|
|
||||||
]);
|
|
||||||
return ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportUpdateOrdnum($ordnums)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$ordnums = json_decode($ordnums, true);
|
|
||||||
$suc = true;
|
|
||||||
foreach ($ordnums as $report_id => $ordnum) {
|
|
||||||
$stm = $db->update('reports', [
|
|
||||||
'ordnum' => $ordnum
|
|
||||||
], [
|
|
||||||
'report_id' => $report_id
|
|
||||||
]);
|
|
||||||
$suc &= ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
return $suc;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportDelete($report_id)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$stm = $db->delete('reports', [
|
|
||||||
'report_id' => $report_id
|
|
||||||
]);
|
|
||||||
return ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportGet($report_id)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
return $db->get('reports', '*', [
|
|
||||||
'report_id' => $report_id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportGetAll($status = null, $page = null)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
if ($status === null) $status = array(0, 1, 2, 3);
|
|
||||||
$params = [
|
|
||||||
'ORDER' => ['report_priority' => 'DESC', 'ordnum' => 'ASC'],
|
|
||||||
'report_status' => $status
|
|
||||||
];
|
|
||||||
if ($page !== null) $params['LIMIT'] = [$page * 10, 10];
|
|
||||||
return $db->select('reports', '*', $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportGetAllGrouped($status = null, $page = null)
|
|
||||||
{
|
|
||||||
$all = reportGetAll($status, $page);
|
|
||||||
$groups = [];
|
|
||||||
foreach ($all as $report) {
|
|
||||||
$groups[$report['report_status']][] = $report;
|
|
||||||
}
|
|
||||||
return $groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportGetArchived($page = null)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$params = [
|
|
||||||
'ORDER' => ['created_dt' => 'DESC'],
|
|
||||||
'report_status' => '4'
|
|
||||||
];
|
|
||||||
if ($page !== null) $params['LIMIT'] = [$page * 10, 10];
|
|
||||||
return $db->select('reports', '*', $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attachments
|
|
||||||
*/
|
|
||||||
function attachmentGet($attachment_id)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
return $db->get('attachments', '*', [
|
|
||||||
'attachment_id' => $attachment_id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
function attachmentAdd($report_id, $attachment_type, $attachment_content)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stm = $db->insert('attachments', [
|
|
||||||
'report_id' => $report_id,
|
|
||||||
'attachment_type' => $attachment_type,
|
|
||||||
'attachment_content' => $attachment_content,
|
|
||||||
'created_dt' => date('Y-m-d H:i:s')
|
|
||||||
]);
|
|
||||||
return ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachmentUpdate($attachment_id, $attachment_content)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
if (strlen(trim($attachment_content)) <= 0) return attachmentDelete($attachment_id);
|
|
||||||
$stm = $db->update('attachments', [
|
|
||||||
'attachment_content' => $attachment_content,
|
|
||||||
'updated_dt' => date('Y-m-d H:i:s')
|
|
||||||
], [
|
|
||||||
'attachment_id' => $attachment_id
|
|
||||||
]);
|
|
||||||
return ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachmentDelete($attachment_id)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$attachment = attachmentGet($attachment_id);
|
|
||||||
if ($attachment['attachment_type'] == 'file'
|
|
||||||
&& file_exists(UPLOAD_DIR_ATTACHMENTS . $attachment['attachment_content']))
|
|
||||||
{
|
|
||||||
unlink(UPLOAD_DIR_ATTACHMENTS . $attachment['attachment_content']);
|
|
||||||
}
|
|
||||||
$stm = $db->delete('attachments', [
|
|
||||||
'attachment_id' => $attachment_id
|
|
||||||
]);
|
|
||||||
return ($stm->rowCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachmentGetAll($report_id)
|
|
||||||
{
|
|
||||||
global $db;
|
|
||||||
$all = $db->select('attachments', '*', [
|
|
||||||
'ORDER' => ['created_dt' => 'ASC'],
|
|
||||||
'report_id' => $report_id
|
|
||||||
]);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user