feat: reestruturacao de menus na sidebar, niveis de acesso e migracao para postgresql/dragonflydb
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"serverUrl": "https://rx.scoreodonto.com",
|
|
||||||
"monitorPath": "C:\\ProgramData\\RF\\Dental Sensor\\Images",
|
|
||||||
"clientName": "COMPUTADOR-LOCAL",
|
|
||||||
"clientType": "windows",
|
|
||||||
"apiKey": "rf-dental-secure-key-2026",
|
|
||||||
"adminPassword": "admin"
|
|
||||||
}
|
|
||||||
Generated
-2628
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "dental-client",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Cliente Windows para envio de imagens dentais",
|
|
||||||
"main": "client-monitor.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node client-monitor.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"socket.io-client": "^4.7.5",
|
|
||||||
"chokidar": "^3.5.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,153 +0,0 @@
|
|||||||
# ✅ CONVERSÃO SQLite → MySQL COMPLETA
|
|
||||||
|
|
||||||
## 📋 RESUMO DA CONVERSÃO
|
|
||||||
|
|
||||||
Todas as referências a SQLite foram convertidas para MySQL.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ ARQUIVOS MODIFICADOS
|
|
||||||
|
|
||||||
### **1. package.json** ✅
|
|
||||||
- ❌ Removido: `sqlite3`
|
|
||||||
- ✅ Adicionado: `mysql2`
|
|
||||||
|
|
||||||
### **2. database.js** ✅
|
|
||||||
- ✅ Convertido de SQLite3 para MySQL2
|
|
||||||
- ✅ Pool de conexões implementado
|
|
||||||
- ✅ Sintaxe MySQL aplicada
|
|
||||||
- ✅ Tabelas criadas corretamente
|
|
||||||
- ✅ Índices aplicados
|
|
||||||
|
|
||||||
### **3. migration-v2.sql** ✅
|
|
||||||
- ✅ Sintaxe SQLite → MySQL
|
|
||||||
- ✅ CREATE TABLE IF NOT EXISTS
|
|
||||||
- ✅ AUTO_INCREMENT no lugar de INTEGER PRIMARY KEY
|
|
||||||
- ✅ TINYINT(1) no lugar de INTEGER
|
|
||||||
- ✅ VARCHAR no lugar de TEXT (onde apropriado)
|
|
||||||
- ✅ Índices criados
|
|
||||||
- ⚠️ Migration complexa criada (com verificações)
|
|
||||||
|
|
||||||
### **4. migration-simple.sql** ✅ (NOVO)
|
|
||||||
- ✅ Migration simplificada
|
|
||||||
- ✅ Recomendada para novo banco
|
|
||||||
- ✅ Cria tabelas completas de uma vez
|
|
||||||
|
|
||||||
### **5. ENV-CONFIG.txt** ✅
|
|
||||||
- ✅ Variáveis SQLite removidas
|
|
||||||
- ✅ Variáveis MySQL adicionadas:
|
|
||||||
- `DB_HOST`
|
|
||||||
- `DB_PORT`
|
|
||||||
- `DB_USER`
|
|
||||||
- `DB_PASSWORD`
|
|
||||||
- `DB_NAME`
|
|
||||||
|
|
||||||
### **6. INSTALACAO-DENTAL-SERVER.md** ✅
|
|
||||||
- ✅ Comandos SQLite → MySQL
|
|
||||||
- ✅ Instruções de migration atualizadas
|
|
||||||
- ✅ Troubleshooting atualizado
|
|
||||||
|
|
||||||
### **7. README.md** ✅
|
|
||||||
- ✅ Referências atualizadas
|
|
||||||
- ✅ Tecnologias atualizadas
|
|
||||||
|
|
||||||
### **8. INSTRUCOES-RAPIDAS.txt** ✅
|
|
||||||
- ✅ Variáveis de ambiente atualizadas
|
|
||||||
- ✅ Comandos de migration atualizados
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 PRINCIPAIS MUDANÇAS
|
|
||||||
|
|
||||||
### **Tipos de Dados**
|
|
||||||
| SQLite | MySQL |
|
|
||||||
|--------|-------|
|
|
||||||
| INTEGER PRIMARY KEY | INT AUTO_INCREMENT PRIMARY KEY |
|
|
||||||
| INTEGER DEFAULT 1 | TINYINT(1) DEFAULT 1 |
|
|
||||||
| TEXT | VARCHAR(255) ou TEXT |
|
|
||||||
| DATETIME DEFAULT CURRENT_TIMESTAMP | DATETIME DEFAULT CURRENT_TIMESTAMP ✅ |
|
|
||||||
|
|
||||||
### **Queries**
|
|
||||||
| SQLite | MySQL |
|
|
||||||
|--------|-------|
|
|
||||||
| `db.get()` | `pool.query()` |
|
|
||||||
| `db.all()` | `pool.query()` |
|
|
||||||
| `db.run()` | `pool.query()` |
|
|
||||||
| `this.lastID` | `result.insertId` |
|
|
||||||
| `this.changes` | `result.affectedRows` |
|
|
||||||
|
|
||||||
### **Connection**
|
|
||||||
| SQLite | MySQL |
|
|
||||||
|--------|-------|
|
|
||||||
| `sqlite3.Database(path)` | `mysql.createPool(config)` |
|
|
||||||
| Direto | Pool de conexões |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 INSTALAÇÃO
|
|
||||||
|
|
||||||
### **1. Criar Banco**
|
|
||||||
```bash
|
|
||||||
mysql -u root -p -e "CREATE DATABASE dental_images CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
|
||||||
```
|
|
||||||
|
|
||||||
### **2. Executar Migration**
|
|
||||||
```bash
|
|
||||||
mysql -u root -p dental_images < migration-simple.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### **3. Configurar .env**
|
|
||||||
```env
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_PORT=3306
|
|
||||||
DB_USER=root
|
|
||||||
DB_PASSWORD=sua_senha
|
|
||||||
DB_NAME=dental_images
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4. Instalar Dependências**
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ FUNCIONALIDADES TESTADAS
|
|
||||||
|
|
||||||
- ✅ Conexão ao MySQL
|
|
||||||
- ✅ Criação de tabelas
|
|
||||||
- ✅ Criação de índices
|
|
||||||
- ✅ Queries básicas (get, all, run)
|
|
||||||
- ✅ Auto-increment
|
|
||||||
- ✅ Datetime defaults
|
|
||||||
- ✅ Pool de conexões
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 DIFERENÇAS IMPORTANTES
|
|
||||||
|
|
||||||
### **Vantagens do MySQL:**
|
|
||||||
- ✅ Pool de conexões (melhor performance)
|
|
||||||
- ✅ Suporte a múltiplos usuários
|
|
||||||
- ✅ Melhor para produção
|
|
||||||
- ✅ Ferramentas de administração (phpMyAdmin, Workbench)
|
|
||||||
|
|
||||||
### **Desvantagens:**
|
|
||||||
- ⚠️ Requer servidor MySQL rodando
|
|
||||||
- ⚠️ Configuração mais complexa
|
|
||||||
- ⚠️ Precisa de credenciais
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 CONCLUSÃO
|
|
||||||
|
|
||||||
**Conversão 100% completa!**
|
|
||||||
|
|
||||||
Todos os arquivos foram atualizados para MySQL. O sistema está pronto para usar com banco MySQL.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ CONVERSÃO COMPLETA
|
|
||||||
**Banco:** MySQL
|
|
||||||
**Versão:** 2.0.0
|
|
||||||
**Data:** 2025-01-26
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
═══════════════════════════════════════════════════════════
|
|
||||||
✅ CONVERSÃO SQLite → MySQL COMPLETA
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
🎉 TODOS OS ARQUIVOS FORAM CONVERTIDOS PARA MySQL!
|
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
✅ O QUE FOI MUDADO
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
1. package.json
|
|
||||||
❌ Removido: sqlite3
|
|
||||||
✅ Adicionado: mysql2
|
|
||||||
|
|
||||||
2. database.js
|
|
||||||
✅ Pool de conexões MySQL
|
|
||||||
✅ Sintaxe MySQL aplicada
|
|
||||||
✅ Funções convertidas
|
|
||||||
|
|
||||||
3. migration-simple.sql (NOVO)
|
|
||||||
✅ Migration MySQL completa
|
|
||||||
✅ Criar banco e tabelas
|
|
||||||
|
|
||||||
4. ENV-CONFIG.txt
|
|
||||||
✅ Variáveis MySQL adicionadas
|
|
||||||
|
|
||||||
5. Documentação
|
|
||||||
✅ INSTALACAO-DENTAL-SERVER.md atualizado
|
|
||||||
✅ README.md atualizado
|
|
||||||
✅ INSTRUCOES-RAPIDAS.txt atualizado
|
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
🚀 COMO USAR COM MYSQL
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
1. CRIAR BANCO:
|
|
||||||
mysql -u root -p
|
|
||||||
CREATE DATABASE dental_images;
|
|
||||||
exit
|
|
||||||
|
|
||||||
2. EXECUTAR MIGRATION:
|
|
||||||
mysql -u root -p dental_images < migration-simple.sql
|
|
||||||
|
|
||||||
3. CONFIGURAR .env:
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_PORT=3306
|
|
||||||
DB_USER=root
|
|
||||||
DB_PASSWORD=sua_senha
|
|
||||||
DB_NAME=dental_images
|
|
||||||
|
|
||||||
4. INSTALAR:
|
|
||||||
npm install
|
|
||||||
|
|
||||||
5. INICIAR:
|
|
||||||
pm2 start server.js --name dental-server
|
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
✅ FUNCIONALIDADES TESTADAS
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
✅ Conexão MySQL
|
|
||||||
✅ Criação de tabelas
|
|
||||||
✅ Índices
|
|
||||||
✅ Queries (get, all, run)
|
|
||||||
✅ Auto-increment
|
|
||||||
✅ Pool de conexões
|
|
||||||
✅ Zero erros de linter
|
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
Status: ✅ CONVERSÃO COMPLETA
|
|
||||||
Banco: MySQL
|
|
||||||
Versão: 2.0.0
|
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════
|
|
||||||
@@ -83,7 +83,7 @@ function generateToken(user, expiresIn = JWT_EXPIRES_IN) {
|
|||||||
async function createInitialUser() {
|
async function createInitialUser() {
|
||||||
try {
|
try {
|
||||||
// Verificar se usuário já existe
|
// Verificar se usuário já existe
|
||||||
const existingUser = await db.get('SELECT * FROM users WHERE username = ?', ['rcesar']);
|
const existingUser = await db.get('SELECT * FROM users WHERE username = $1', ['rcesar']);
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
console.log('👤 Usuário admin já existe:', existingUser.username);
|
console.log('👤 Usuário admin já existe:', existingUser.username);
|
||||||
@@ -96,7 +96,7 @@ async function createInitialUser() {
|
|||||||
// Inserir usuário
|
// Inserir usuário
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO users (username, email, password_hash, is_admin, created_at)
|
`INSERT INTO users (username, email, password_hash, is_admin, created_at)
|
||||||
VALUES (?, ?, ?, 1, NOW())`,
|
VALUES ($1, $2, $3, true, CURRENT_TIMESTAMP) RETURNING id`,
|
||||||
['rcesar', 'rcesar@rcesar.com', passwordHash]
|
['rcesar', 'rcesar@rcesar.com', passwordHash]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ async function runBackfill() {
|
|||||||
await storage.saveImage(thumbFilename, thumbBuffer, img.client_name, img.patient_name, false);
|
await storage.saveImage(thumbFilename, thumbBuffer, img.client_name, img.patient_name, false);
|
||||||
|
|
||||||
// d. Atualizar no banco de dados
|
// d. Atualizar no banco de dados
|
||||||
await db.run('UPDATE images SET thumb_filename = ? WHERE id = ?', [thumbFilename, img.id]);
|
await db.run('UPDATE images SET thumb_filename = $1 WHERE id = $2', [thumbFilename, img.id]);
|
||||||
|
|
||||||
console.log(`✅ [ID ${img.id}] Thumbnail salvo com sucesso: ${thumbFilename} (Paciente: ${img.patient_name})`);
|
console.log(`✅ [ID ${img.id}] Thumbnail salvo com sucesso: ${thumbFilename} (Paciente: ${img.patient_name})`);
|
||||||
successCount++;
|
successCount++;
|
||||||
|
|||||||
@@ -1,42 +1,8 @@
|
|||||||
const { Pool } = require('pg');
|
const { Pool } = require('pg');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
let pool = null;
|
let pool = null;
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// FUNÇÕES AUXILIARES DE COMPATIBILIDADE (MYSQL -> POSTGRES)
|
|
||||||
// ================================================================
|
|
||||||
|
|
||||||
function rewriteToPostgres(sql) {
|
|
||||||
let rewritten = sql;
|
|
||||||
|
|
||||||
// 1. Converter MySQL NOW() para Postgres CURRENT_TIMESTAMP
|
|
||||||
rewritten = rewritten.replace(/\bNOW\(\)/gi, 'CURRENT_TIMESTAMP');
|
|
||||||
|
|
||||||
// 2. Converter parâmetros de ? para $1, $2, etc.
|
|
||||||
let paramIndex = 1;
|
|
||||||
rewritten = rewritten.replace(/\?/g, () => `$${paramIndex++}`);
|
|
||||||
|
|
||||||
// 3. Adicionar RETURNING id nas inserções se não houver
|
|
||||||
if (rewritten.trim().toUpperCase().startsWith('INSERT INTO')) {
|
|
||||||
if (!rewritten.toUpperCase().includes('RETURNING')) {
|
|
||||||
rewritten += ' RETURNING id';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Reescrever ON DUPLICATE KEY UPDATE do MySQL
|
|
||||||
if (rewritten.includes('ON DUPLICATE KEY UPDATE')) {
|
|
||||||
// Isso é específico para a query de inserção de usuário default
|
|
||||||
rewritten = `INSERT INTO users (username, email, password_hash, created_at)
|
|
||||||
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
|
|
||||||
ON CONFLICT (username) DO UPDATE SET
|
|
||||||
email = EXCLUDED.email,
|
|
||||||
password_hash = EXCLUDED.password_hash
|
|
||||||
RETURNING id`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rewritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// INICIALIZAR BANCO DE DADOS POSTGRESQL
|
// INICIALIZAR BANCO DE DADOS POSTGRESQL
|
||||||
// ================================================================
|
// ================================================================
|
||||||
@@ -218,7 +184,7 @@ async function createTables() {
|
|||||||
function get(query, params = []) {
|
function get(query, params = []) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const res = await pool.query(rewriteToPostgres(query), params);
|
const res = await pool.query(query, params);
|
||||||
resolve(res.rows[0] || null);
|
resolve(res.rows[0] || null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -229,7 +195,7 @@ function get(query, params = []) {
|
|||||||
function all(query, params = []) {
|
function all(query, params = []) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const res = await pool.query(rewriteToPostgres(query), params);
|
const res = await pool.query(query, params);
|
||||||
resolve(res.rows || []);
|
resolve(res.rows || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -240,8 +206,7 @@ function all(query, params = []) {
|
|||||||
function run(query, params = []) {
|
function run(query, params = []) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const rewritten = rewriteToPostgres(query);
|
const res = await pool.query(query, params);
|
||||||
const res = await pool.query(rewritten, params);
|
|
||||||
|
|
||||||
// No PostgreSQL, se adicionamos RETURNING id, o id estará no res.rows[0]
|
// No PostgreSQL, se adicionamos RETURNING id, o id estará no res.rows[0]
|
||||||
const insertedId = (res.rows && res.rows[0] && res.rows[0].id) ? res.rows[0].id : null;
|
const insertedId = (res.rows && res.rows[0] && res.rows[0].id) ? res.rows[0].id : null;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ async function checkInstallation() {
|
|||||||
} else {
|
} else {
|
||||||
// Tentar inicializar se .env tem configurações
|
// Tentar inicializar se .env tem configurações
|
||||||
const envContent = fs.readFileSync(envPath, 'utf8');
|
const envContent = fs.readFileSync(envPath, 'utf8');
|
||||||
if (envContent.includes('DB_TYPE=sqlite') || (envContent.includes('DB_HOST') && envContent.includes('DB_NAME'))) {
|
if (envContent.includes('DB_HOST') && envContent.includes('DB_NAME')) {
|
||||||
// Recarregar .env
|
// Recarregar .env
|
||||||
require('dotenv').config({ path: envPath });
|
require('dotenv').config({ path: envPath });
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
/*M!999999\- enable the sandbox mode */
|
|
||||||
-- MariaDB dump 10.19 Distrib 10.11.10-MariaDB, for Linux (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: localhost Database: dental_images
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.11.10-MariaDB-log
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
|
||||||
/*!40101 SET NAMES utf8mb4 */;
|
|
||||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
|
||||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
|
||||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
|
||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `images`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `images`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `images` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`image_guid` varchar(255) NOT NULL,
|
|
||||||
`patient_name` text DEFAULT NULL,
|
|
||||||
`client_name` varchar(100) DEFAULT NULL,
|
|
||||||
`original_filename` text DEFAULT NULL,
|
|
||||||
`filename` text NOT NULL,
|
|
||||||
`enabled` tinyint(1) DEFAULT 1,
|
|
||||||
`tooth_number` varchar(10) DEFAULT NULL,
|
|
||||||
`tooth_side` varchar(10) DEFAULT NULL,
|
|
||||||
`patient_name_resumed` varchar(50) DEFAULT NULL,
|
|
||||||
`original_image_id` int(11) DEFAULT NULL,
|
|
||||||
`rotation` int(11) DEFAULT 0,
|
|
||||||
`flip_horizontal` int(11) DEFAULT 0,
|
|
||||||
`flip_vertical` int(11) DEFAULT 0,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_images_enabled` (`enabled`),
|
|
||||||
KEY `idx_images_original` (`original_image_id`),
|
|
||||||
KEY `idx_patient_name` (`patient_name`(100)),
|
|
||||||
KEY `idx_image_guid` (`image_guid`),
|
|
||||||
KEY `idx_created_at` (`created_at`),
|
|
||||||
KEY `idx_client_name` (`client_name`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `images`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `images` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `images` DISABLE KEYS */;
|
|
||||||
/*!40000 ALTER TABLE `images` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `users`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `users`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `users` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`username` varchar(50) NOT NULL,
|
|
||||||
`email` varchar(100) NOT NULL,
|
|
||||||
`password_hash` text NOT NULL,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `username` (`username`),
|
|
||||||
UNIQUE KEY `email` (`email`)
|
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping data for table `users`
|
|
||||||
--
|
|
||||||
|
|
||||||
LOCK TABLES `users` WRITE;
|
|
||||||
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
|
|
||||||
INSERT INTO `users` VALUES
|
|
||||||
(1,'rcesar','rcesar@rcesar.com','$2b$10$i0zu9BGACsu6TDTsrU1EaOGZkKnvp8zwjOek1W/3m9mP4mzbN8HkC','2025-10-31 07:16:50');
|
|
||||||
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
|
|
||||||
UNLOCK TABLES;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping routines for database 'dental_images'
|
|
||||||
--
|
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
|
||||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
|
||||||
|
|
||||||
-- Dump completed on 2025-10-31 21:24:39
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*M!999999\- enable the sandbox mode */
|
|
||||||
-- MariaDB dump 10.19 Distrib 10.11.10-MariaDB, for Linux (x86_64)
|
|
||||||
--
|
|
||||||
-- Host: localhost Database: dental_images
|
|
||||||
-- ------------------------------------------------------
|
|
||||||
-- Server version 10.11.10-MariaDB-log
|
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
|
||||||
/*!40101 SET NAMES utf8mb4 */;
|
|
||||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
|
||||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
|
||||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
|
||||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
|
||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `images`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `images`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `images` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`image_guid` varchar(255) NOT NULL,
|
|
||||||
`patient_name` text DEFAULT NULL,
|
|
||||||
`client_name` varchar(100) DEFAULT NULL,
|
|
||||||
`original_filename` text DEFAULT NULL,
|
|
||||||
`filename` text NOT NULL,
|
|
||||||
`enabled` tinyint(1) DEFAULT 1,
|
|
||||||
`tooth_number` varchar(10) DEFAULT NULL,
|
|
||||||
`tooth_side` varchar(10) DEFAULT NULL,
|
|
||||||
`patient_name_resumed` varchar(50) DEFAULT NULL,
|
|
||||||
`original_image_id` int(11) DEFAULT NULL,
|
|
||||||
`rotation` int(11) DEFAULT 0,
|
|
||||||
`flip_horizontal` int(11) DEFAULT 0,
|
|
||||||
`flip_vertical` int(11) DEFAULT 0,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_images_enabled` (`enabled`),
|
|
||||||
KEY `idx_images_original` (`original_image_id`),
|
|
||||||
KEY `idx_patient_name` (`patient_name`(100)),
|
|
||||||
KEY `idx_image_guid` (`image_guid`),
|
|
||||||
KEY `idx_created_at` (`created_at`),
|
|
||||||
KEY `idx_client_name` (`client_name`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Table structure for table `users`
|
|
||||||
--
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `users`;
|
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
|
||||||
CREATE TABLE `users` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`username` varchar(50) NOT NULL,
|
|
||||||
`email` varchar(100) NOT NULL,
|
|
||||||
`password_hash` text NOT NULL,
|
|
||||||
`created_at` datetime DEFAULT current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `username` (`username`),
|
|
||||||
UNIQUE KEY `email` (`email`)
|
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Dumping routines for database 'dental_images'
|
|
||||||
--
|
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
|
||||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
|
||||||
|
|
||||||
-- Dump completed on 2025-10-31 21:24:36
|
|
||||||
@@ -5,6 +5,20 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Gerenciamento de Clínicas - RF Dental</title>
|
<title>Gerenciamento de Clínicas - RF Dental</title>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Redirecionar se não estiver autenticado ou não for administrador
|
||||||
|
if (!localStorage.getItem('auth_token')) {
|
||||||
|
window.location.replace('/login');
|
||||||
|
} else if (localStorage.getItem('is_admin') !== 'true') {
|
||||||
|
window.location.replace('/');
|
||||||
|
} else {
|
||||||
|
// Adiciona classe de admin no body para que os estilos de admin sejam exibidos
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.body.classList.add('is-admin');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Fontes -->
|
<!-- Fontes -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
@@ -28,15 +42,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<a href="/" class="nav-item">
|
<div class="nav-section-title">Menu Principal</div>
|
||||||
|
<a href="/" class="nav-item" id="menu-dashboard">
|
||||||
<i class="fa-solid fa-chart-line"></i> Dashboard
|
<i class="fa-solid fa-chart-line"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
<a href="/clients" class="nav-item">
|
<a href="/?action=new-patient" class="nav-item" id="menu-new-patient" onclick="if(window.location.pathname === '/') { openCreatePatientModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-user-plus"></i> Novo Paciente
|
||||||
|
</a>
|
||||||
|
<a href="/?action=sync-upload" class="nav-item" id="menu-upload" onclick="if(window.location.pathname === '/') { openSyncModal('upload'); return false; }">
|
||||||
|
<i class="fa-solid fa-upload"></i> Enviar Imagens
|
||||||
|
</a>
|
||||||
|
<a href="/?action=settings" class="nav-item" id="menu-settings" onclick="if(window.location.pathname === '/') { openSettingsModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-user-gear"></i> Minha Conta
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-only">
|
||||||
|
<div class="nav-section-title">Área Administrativa</div>
|
||||||
|
<a href="/clients" class="nav-item" id="menu-clients">
|
||||||
<i class="fa-solid fa-network-wired"></i> Conexões
|
<i class="fa-solid fa-network-wired"></i> Conexões
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin-clinics" class="nav-item active">
|
<a href="/admin-clinics" class="nav-item active" id="menu-clinics">
|
||||||
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
|
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/?action=users" class="nav-item" id="menu-users" onclick="if(window.location.pathname === '/') { openUserManagementModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-users"></i> Gerenciar Usuários
|
||||||
|
</a>
|
||||||
|
<a href="/?action=sync-devices" class="nav-item" id="menu-sync" onclick="if(window.location.pathname === '/') { openSyncModal('devices'); return false; }">
|
||||||
|
<i class="fa-solid fa-rotate"></i> Sincronização
|
||||||
|
</a>
|
||||||
|
<a href="/?action=plugins" class="nav-item" id="menu-plugins" onclick="if(window.location.pathname === '/') { openPluginsModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-plug"></i> Plugins / Wasabi
|
||||||
|
</a>
|
||||||
|
<a href="/reset" class="nav-item" id="menu-reset" style="color: #dc2626;">
|
||||||
|
<i class="fa-solid fa-triangle-exclamation"></i> Zerar Sistema
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Pegar Token JWT do Admin
|
// Pegar Token JWT do Admin
|
||||||
const token = localStorage.getItem('auth_token') || '';
|
const token = localStorage.getItem('auth_token') || '';
|
||||||
|
const isAdmin = localStorage.getItem('is_admin') === 'true';
|
||||||
|
|
||||||
// Se não tem token, redirecionar imediatamente para login
|
// Se não tem token, redirecionar imediatamente para login
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -11,6 +12,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Se não for administrador, redirecionar imediatamente para a home
|
||||||
|
if (!isAdmin) {
|
||||||
|
window.location.href = '/';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
+13
-12
@@ -78,15 +78,11 @@ async function verifyAuth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateAdminMenuVisibility(isAdmin) {
|
function updateAdminMenuVisibility(isAdmin) {
|
||||||
// Esconder links não autorizados da Sidebar
|
if (isAdmin) {
|
||||||
const connectionsLink = document.querySelector('.sidebar-nav .nav-item[href="/clients"]');
|
document.body.classList.add('is-admin');
|
||||||
const clinicsLink = document.querySelector('.sidebar-nav .nav-item[href="/admin-clinics"]');
|
} else {
|
||||||
if (connectionsLink) connectionsLink.style.display = isAdmin ? 'flex' : 'none';
|
document.body.classList.remove('is-admin');
|
||||||
if (clinicsLink) clinicsLink.style.display = isAdmin ? 'flex' : 'none';
|
}
|
||||||
|
|
||||||
// Esconder botão de gerenciamento de usuários na toolbar
|
|
||||||
const btnUserManagement = document.getElementById('btnUserManagement');
|
|
||||||
if (btnUserManagement) btnUserManagement.style.display = isAdmin ? 'flex' : 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchWithAuth(url, options = {}) {
|
async function fetchWithAuth(url, options = {}) {
|
||||||
@@ -189,7 +185,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
if (action === 'new-patient') window.openCreatePatientModal();
|
if (action === 'new-patient') window.openCreatePatientModal();
|
||||||
else if (action === 'settings') window.openSettingsModal();
|
else if (action === 'settings') window.openSettingsModal();
|
||||||
else if (action === 'plugins') window.openPluginsModal();
|
else if (action === 'plugins') window.openPluginsModal();
|
||||||
else if (action === 'sync') window.openSyncModal();
|
else if (action === 'sync-devices') window.openSyncModal('devices');
|
||||||
|
else if (action === 'sync-upload') window.openSyncModal('upload');
|
||||||
|
else if (action === 'sync') window.openSyncModal('devices');
|
||||||
|
else if (action === 'users') window.openUserManagementModal();
|
||||||
|
|
||||||
// Remove querystring to prevent reopening on reload
|
// Remove querystring to prevent reopening on reload
|
||||||
window.history.replaceState({}, document.title, window.location.pathname);
|
window.history.replaceState({}, document.title, window.location.pathname);
|
||||||
@@ -1505,7 +1504,7 @@ window.triggerSync = triggerSync;
|
|||||||
|
|
||||||
let selectedUploadFiles = [];
|
let selectedUploadFiles = [];
|
||||||
|
|
||||||
function openSyncModal() {
|
function openSyncModal(defaultTab = 'devices') {
|
||||||
const modal = document.getElementById('syncModal');
|
const modal = document.getElementById('syncModal');
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
|
|
||||||
@@ -1513,13 +1512,15 @@ function openSyncModal() {
|
|||||||
modal.style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
|
|
||||||
// Resetar abas
|
// Resetar abas
|
||||||
switchSyncTab('devices');
|
switchSyncTab(defaultTab);
|
||||||
|
|
||||||
// Limpar formulário de upload
|
// Limpar formulário de upload
|
||||||
resetManualUploadForm();
|
resetManualUploadForm();
|
||||||
|
|
||||||
// Carregar dados
|
// Carregar dados
|
||||||
|
if (defaultTab === 'devices') {
|
||||||
loadSyncDevices();
|
loadSyncDevices();
|
||||||
|
}
|
||||||
refreshUploadPatients();
|
refreshUploadPatients();
|
||||||
|
|
||||||
// Escuta de clique fora para fechar modal
|
// Escuta de clique fora para fechar modal
|
||||||
|
|||||||
@@ -179,9 +179,16 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
// Redirecionar se não estiver autenticado
|
// Redirecionar se não estiver autenticado ou não for administrador
|
||||||
if (!localStorage.getItem('auth_token')) {
|
if (!localStorage.getItem('auth_token')) {
|
||||||
window.location.href = '/login';
|
window.location.replace('/login');
|
||||||
|
} else if (localStorage.getItem('is_admin') !== 'true') {
|
||||||
|
window.location.replace('/');
|
||||||
|
} else {
|
||||||
|
// Adiciona classe de admin no body para que os estilos de admin sejam exibidos
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.body.classList.add('is-admin');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
@@ -196,15 +203,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<a href="/" class="nav-item">
|
<div class="nav-section-title">Menu Principal</div>
|
||||||
|
<a href="/" class="nav-item" id="menu-dashboard">
|
||||||
<i class="fa-solid fa-chart-line"></i> Dashboard
|
<i class="fa-solid fa-chart-line"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
<a href="/clients" class="nav-item active">
|
<a href="/?action=new-patient" class="nav-item" id="menu-new-patient" onclick="if(window.location.pathname === '/') { openCreatePatientModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-user-plus"></i> Novo Paciente
|
||||||
|
</a>
|
||||||
|
<a href="/?action=sync-upload" class="nav-item" id="menu-upload" onclick="if(window.location.pathname === '/') { openSyncModal('upload'); return false; }">
|
||||||
|
<i class="fa-solid fa-upload"></i> Enviar Imagens
|
||||||
|
</a>
|
||||||
|
<a href="/?action=settings" class="nav-item" id="menu-settings" onclick="if(window.location.pathname === '/') { openSettingsModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-user-gear"></i> Minha Conta
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-only">
|
||||||
|
<div class="nav-section-title">Área Administrativa</div>
|
||||||
|
<a href="/clients" class="nav-item active" id="menu-clients">
|
||||||
<i class="fa-solid fa-network-wired"></i> Conexões
|
<i class="fa-solid fa-network-wired"></i> Conexões
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin-clinics" class="nav-item">
|
<a href="/admin-clinics" class="nav-item" id="menu-clinics">
|
||||||
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
|
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/?action=users" class="nav-item" id="menu-users" onclick="if(window.location.pathname === '/') { openUserManagementModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-users"></i> Gerenciar Usuários
|
||||||
|
</a>
|
||||||
|
<a href="/?action=sync-devices" class="nav-item" id="menu-sync" onclick="if(window.location.pathname === '/') { openSyncModal('devices'); return false; }">
|
||||||
|
<i class="fa-solid fa-rotate"></i> Sincronização
|
||||||
|
</a>
|
||||||
|
<a href="/?action=plugins" class="nav-item" id="menu-plugins" onclick="if(window.location.pathname === '/') { openPluginsModal(); return false; }">
|
||||||
|
<i class="fa-solid fa-plug"></i> Plugins / Wasabi
|
||||||
|
</a>
|
||||||
|
<a href="/reset" class="nav-item" id="menu-reset" style="color: #dc2626;">
|
||||||
|
<i class="fa-solid fa-triangle-exclamation"></i> Zerar Sistema
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
|
|||||||
@@ -21,15 +21,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<a href="/" class="nav-item active">
|
<div class="nav-section-title">Menu Principal</div>
|
||||||
|
<a href="/" class="nav-item active" id="menu-dashboard" onclick="if(window.location.pathname === '/') { showView('gallery'); return false; }">
|
||||||
<i class="fa-solid fa-chart-line"></i> Dashboard
|
<i class="fa-solid fa-chart-line"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
<a href="/clients" class="nav-item">
|
<a href="#" class="nav-item" id="menu-new-patient" onclick="openCreatePatientModal(); return false;">
|
||||||
|
<i class="fa-solid fa-user-plus"></i> Novo Paciente
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" id="menu-upload" onclick="openSyncModal('upload'); return false;">
|
||||||
|
<i class="fa-solid fa-upload"></i> Enviar Imagens
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" id="menu-settings" onclick="openSettingsModal(); return false;">
|
||||||
|
<i class="fa-solid fa-user-gear"></i> Minha Conta
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-only">
|
||||||
|
<div class="nav-section-title">Área Administrativa</div>
|
||||||
|
<a href="/clients" class="nav-item" id="menu-clients">
|
||||||
<i class="fa-solid fa-network-wired"></i> Conexões
|
<i class="fa-solid fa-network-wired"></i> Conexões
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin-clinics" class="nav-item">
|
<a href="/admin-clinics" class="nav-item" id="menu-clinics">
|
||||||
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
|
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
|
||||||
</a>
|
</a>
|
||||||
|
<a href="#" class="nav-item" id="menu-users" onclick="openUserManagementModal(); return false;">
|
||||||
|
<i class="fa-solid fa-users"></i> Gerenciar Usuários
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" id="menu-sync" onclick="openSyncModal('devices'); return false;">
|
||||||
|
<i class="fa-solid fa-rotate"></i> Sincronização
|
||||||
|
</a>
|
||||||
|
<a href="#" class="nav-item" id="menu-plugins" onclick="openPluginsModal(); return false;">
|
||||||
|
<i class="fa-solid fa-plug"></i> Plugins / Wasabi
|
||||||
|
</a>
|
||||||
|
<a href="/reset" class="nav-item" id="menu-reset" style="color: #dc2626;">
|
||||||
|
<i class="fa-solid fa-triangle-exclamation"></i> Zerar Sistema
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
@@ -75,8 +101,8 @@
|
|||||||
<!-- Scrollable content area -->
|
<!-- Scrollable content area -->
|
||||||
<div class="content-scroll">
|
<div class="content-scroll">
|
||||||
|
|
||||||
<!-- Toolbar de Filtros e Ações Rápidas (Migradas da Sidebar) -->
|
<!-- Toolbar de Filtros e Ações Rápidas -->
|
||||||
<div class="toolbar glass-card" style="margin-bottom: 24px; padding: 16px; display: flex; gap: 16px; align-items: center; justify-content: space-between; flex-wrap: wrap; background: rgba(255, 255, 255, 0.4); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 12px; box-shadow: var(--shadow-sm);">
|
<div class="toolbar glass-card" style="margin-bottom: 24px; padding: 16px; display: flex; gap: 16px; align-items: center; background: rgba(255, 255, 255, 0.4); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 12px; box-shadow: var(--shadow-sm);">
|
||||||
<div class="toolbar-left" style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
<div class="toolbar-left" style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||||
<!-- Filtro de Clientes -->
|
<!-- Filtro de Clientes -->
|
||||||
<select id="clientFilter" class="client-filter" style="padding: 10px 16px; border-radius: 10px; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); font-size: 0.95rem; font-weight: 500; outline: none; min-width: 200px; cursor: pointer;">
|
<select id="clientFilter" class="client-filter" style="padding: 10px 16px; border-radius: 10px; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); font-size: 0.95rem; font-weight: 500; outline: none; min-width: 200px; cursor: pointer;">
|
||||||
@@ -87,27 +113,6 @@
|
|||||||
👁️ <span id="toggleText">Ocultas</span>
|
👁️ <span id="toggleText">Ocultas</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar-right" style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
|
|
||||||
<button class="btn btn-primary" onclick="openCreatePatientModal()" style="padding: 10px 16px; border-radius: 10px; font-weight: 600; font-size: 0.95rem; background: var(--primary-color); color: white; border: none; cursor: pointer; display: flex; align-items: center; gap: 6px; box-shadow: 0 4px 10px rgba(79, 70, 229, 0.2);">
|
|
||||||
➕ Novo Paciente
|
|
||||||
</button>
|
|
||||||
<button id="btnUserManagement" class="btn btn-secondary" onclick="openUserManagementModal()" title="Gerenciar Usuários" style="display: none; padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; align-items: center; gap: 6px;">
|
|
||||||
👥 Gerenciar Usuários
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary" onclick="openSettingsModal()" title="Configurações" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 6px;">
|
|
||||||
⚙️ Configurações
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary" onclick="openPluginsModal()" title="Plugins / Wasabi" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 6px;">
|
|
||||||
🔌 Plugins / Wasabi
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary" onclick="openSyncModal()" title="Sincronização" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 6px;">
|
|
||||||
🔄 Sincronização
|
|
||||||
</button>
|
|
||||||
<a href="/reset" class="btn btn-danger" title="Zerar Sistema" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; background: #fee2e2; color: #dc2626; border: 1px solid #fecaca; text-decoration: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;">
|
|
||||||
⚠️ Zerar Sistema
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
|
|||||||
@@ -942,3 +942,41 @@ body {
|
|||||||
max-height: 95vh;
|
max-height: 95vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RF Dental Premium Sidebar Menus Additions */
|
||||||
|
.nav-section-title {
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 12px 16px 4px 16px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocultação inicial de elementos administrativos */
|
||||||
|
.admin-only {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exibição quando autenticado como admin */
|
||||||
|
body.is-admin .admin-only {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsividade mobile para o agrupamento admin */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-section-title {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
body.is-admin .admin-only {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
gap: 16px !important;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ router.post('/', requireAdmin, async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Verificar se email já existe
|
// Verificar se email já existe
|
||||||
const existing = await db.get(`SELECT id FROM clinics_devices WHERE email = ?`, [email]);
|
const existing = await db.get(`SELECT id FROM clinics_devices WHERE email = $1`, [email]);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return res.status(400).json({ success: false, message: 'Email já cadastrado para outra clínica' });
|
return res.status(400).json({ success: false, message: 'Email já cadastrado para outra clínica' });
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ router.post('/', requireAdmin, async (req, res) => {
|
|||||||
|
|
||||||
await db.run(`
|
await db.run(`
|
||||||
INSERT INTO clinics_devices (clinic_name, email, password_hash, pc_name, machine_token)
|
INSERT INTO clinics_devices (clinic_name, email, password_hash, pc_name, machine_token)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES ($1, $2, $3, $4, $5) RETURNING id
|
||||||
`, [clinic_name, email, password_hash, pc_name, machine_token]);
|
`, [clinic_name, email, password_hash, pc_name, machine_token]);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -74,7 +74,7 @@ router.put('/:id/status', requireAdmin, async (req, res) => {
|
|||||||
const { is_active } = req.body;
|
const { is_active } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.run(`UPDATE clinics_devices SET is_active = ? WHERE id = ?`, [is_active ? 1 : 0, id]);
|
await db.run(`UPDATE clinics_devices SET is_active = $1 WHERE id = $2`, [is_active ? 1 : 0, id]);
|
||||||
res.json({ success: true, message: 'Status atualizado com sucesso' });
|
res.json({ success: true, message: 'Status atualizado com sucesso' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ success: false, message: 'Erro interno' });
|
res.status(500).json({ success: false, message: 'Erro interno' });
|
||||||
@@ -88,7 +88,7 @@ router.delete('/:id', requireAdmin, async (req, res) => {
|
|||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.run(`DELETE FROM clinics_devices WHERE id = ?`, [id]);
|
await db.run(`DELETE FROM clinics_devices WHERE id = $1`, [id]);
|
||||||
res.json({ success: true, message: 'Clínica excluída com sucesso' });
|
res.json({ success: true, message: 'Clínica excluída com sucesso' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ success: false, message: 'Erro interno' });
|
res.status(500).json({ success: false, message: 'Erro interno' });
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ router.post('/login', async (req, res) => {
|
|||||||
|
|
||||||
// Buscar usuário
|
// Buscar usuário
|
||||||
const user = await db.get(
|
const user = await db.get(
|
||||||
'SELECT * FROM users WHERE username = ? OR email = ?',
|
'SELECT * FROM users WHERE username = $1 OR email = $2',
|
||||||
[username, username]
|
[username, username]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ router.get('/verify', async (req, res) => {
|
|||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const decoded = jwt.verify(token, require('../auth').JWT_SECRET);
|
const decoded = jwt.verify(token, require('../auth').JWT_SECRET);
|
||||||
|
|
||||||
const user = await db.get('SELECT * FROM users WHERE id = ?', [decoded.id]);
|
const user = await db.get('SELECT * FROM users WHERE id = $1', [decoded.id]);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ error: 'Usuário não encontrado' });
|
return res.status(404).json({ error: 'Usuário não encontrado' });
|
||||||
@@ -131,7 +131,7 @@ router.post('/create-user', authenticateToken, async (req, res) => {
|
|||||||
|
|
||||||
// Verificar se usuário já existe
|
// Verificar se usuário já existe
|
||||||
const existing = await db.get(
|
const existing = await db.get(
|
||||||
'SELECT * FROM users WHERE username = ? OR email = ?',
|
'SELECT * FROM users WHERE username = $1 OR email = $2',
|
||||||
[username, email]
|
[username, email]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ router.post('/create-user', authenticateToken, async (req, res) => {
|
|||||||
// Criar usuário
|
// Criar usuário
|
||||||
const result = await db.run(
|
const result = await db.run(
|
||||||
`INSERT INTO users (username, email, password_hash, clinic_name, pc_name, is_admin, created_at)
|
`INSERT INTO users (username, email, password_hash, clinic_name, pc_name, is_admin, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, NOW())`,
|
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP) RETURNING id`,
|
||||||
[username, email, passwordHash, clinic_name || null, pc_name || null, !!is_admin]
|
[username, email, passwordHash, clinic_name || null, pc_name || null, !!is_admin]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ router.delete('/users/:id', authenticateToken, async (req, res) => {
|
|||||||
if (parseInt(userId) === req.user.id) {
|
if (parseInt(userId) === req.user.id) {
|
||||||
return res.status(400).json({ error: 'Não é possível excluir o próprio usuário admin' });
|
return res.status(400).json({ error: 'Não é possível excluir o próprio usuário admin' });
|
||||||
}
|
}
|
||||||
await db.run('DELETE FROM users WHERE id = ?', [userId]);
|
await db.run('DELETE FROM users WHERE id = $1', [userId]);
|
||||||
res.json({ success: true, message: 'Usuário excluído com sucesso' });
|
res.json({ success: true, message: 'Usuário excluído com sucesso' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Erro ao excluir usuário' });
|
res.status(500).json({ error: 'Erro ao excluir usuário' });
|
||||||
@@ -200,7 +200,7 @@ router.post('/users/:id/token', authenticateToken, async (req, res) => {
|
|||||||
if (!req.user.is_admin) {
|
if (!req.user.is_admin) {
|
||||||
return res.status(403).json({ error: 'Acesso negado' });
|
return res.status(403).json({ error: 'Acesso negado' });
|
||||||
}
|
}
|
||||||
const user = await db.get('SELECT * FROM users WHERE id = ?', [req.params.id]);
|
const user = await db.get('SELECT * FROM users WHERE id = $1', [req.params.id]);
|
||||||
if (!user) return res.status(404).json({ error: 'Usuário não encontrado' });
|
if (!user) return res.status(404).json({ error: 'Usuário não encontrado' });
|
||||||
|
|
||||||
// Gera um token com duração longa para uso no desktop (ex: 3650 dias = ~10 anos)
|
// Gera um token com duração longa para uso no desktop (ex: 3650 dias = ~10 anos)
|
||||||
@@ -229,7 +229,7 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buscar usuário atual para verificar a senha
|
// Buscar usuário atual para verificar a senha
|
||||||
const user = await db.get('SELECT * FROM users WHERE id = ?', [userId]);
|
const user = await db.get('SELECT * FROM users WHERE id = $1', [userId]);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ error: 'Usuário não encontrado' });
|
return res.status(404).json({ error: 'Usuário não encontrado' });
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
|||||||
|
|
||||||
// Verificar e aplicar novo username se fornecido
|
// Verificar e aplicar novo username se fornecido
|
||||||
if (newUsername && newUsername !== user.username) {
|
if (newUsername && newUsername !== user.username) {
|
||||||
const existing = await db.get('SELECT * FROM users WHERE username = ? AND id != ?', [newUsername, userId]);
|
const existing = await db.get('SELECT * FROM users WHERE username = $1 AND id != $2', [newUsername, userId]);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return res.status(409).json({ error: 'Este nome de usuário já está em uso' });
|
return res.status(409).json({ error: 'Este nome de usuário já está em uso' });
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
|||||||
|
|
||||||
// Verificar e aplicar novo e-mail se fornecido
|
// Verificar e aplicar novo e-mail se fornecido
|
||||||
if (newEmail && newEmail !== user.email) {
|
if (newEmail && newEmail !== user.email) {
|
||||||
const existing = await db.get('SELECT * FROM users WHERE email = ? AND id != ?', [newEmail, userId]);
|
const existing = await db.get('SELECT * FROM users WHERE email = $1 AND id != $2', [newEmail, userId]);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return res.status(409).json({ error: 'Este e-mail já está em uso' });
|
return res.status(409).json({ error: 'Este e-mail já está em uso' });
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
|||||||
|
|
||||||
// Atualizar no banco de dados
|
// Atualizar no banco de dados
|
||||||
await db.run(
|
await db.run(
|
||||||
'UPDATE users SET username = ?, email = ?, password_hash = ? WHERE id = ?',
|
'UPDATE users SET username = $1, email = $2, password_hash = $3 WHERE id = $4',
|
||||||
[finalUsername, finalEmail, finalPasswordHash, userId]
|
[finalUsername, finalEmail, finalPasswordHash, userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ router.post('/login', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Buscar o dispositivo pelo Token
|
// 1. Buscar o dispositivo pelo Token
|
||||||
const device = await db.get(`SELECT * FROM clinics_devices WHERE machine_token = ?`, [machine_token]);
|
const device = await db.get(`SELECT * FROM clinics_devices WHERE machine_token = $1`, [machine_token]);
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return res.status(401).json({ success: false, message: 'Token da máquina inválido ou inexistente' });
|
return res.status(401).json({ success: false, message: 'Token da máquina inválido ou inexistente' });
|
||||||
@@ -46,7 +46,7 @@ router.post('/login', async (req, res) => {
|
|||||||
|
|
||||||
// Atualizar último IP conectado
|
// Atualizar último IP conectado
|
||||||
const clientIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
const clientIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
||||||
await db.run(`UPDATE clinics_devices SET last_ip = ? WHERE id = ?`, [clientIp, device.id]);
|
await db.run(`UPDATE clinics_devices SET last_ip = $1 WHERE id = $2`, [clientIp, device.id]);
|
||||||
|
|
||||||
// 5. Gerar Token JWT de Sessão
|
// 5. Gerar Token JWT de Sessão
|
||||||
// O Token vale por 2 horas, obrigando o cliente a re-logar ou pedir refresh
|
// O Token vale por 2 horas, obrigando o cliente a re-logar ou pedir refresh
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ router.get('/', async (req, res) => {
|
|||||||
`SELECT g.*, COUNT(gi.id) as image_count
|
`SELECT g.*, COUNT(gi.id) as image_count
|
||||||
FROM gtos g
|
FROM gtos g
|
||||||
LEFT JOIN gto_images gi ON gi.gto_id = g.id
|
LEFT JOIN gto_images gi ON gi.gto_id = g.id
|
||||||
WHERE g.patient_name = ?
|
WHERE g.patient_name = $1
|
||||||
GROUP BY g.id
|
GROUP BY g.id
|
||||||
ORDER BY g.created_at DESC`,
|
ORDER BY g.created_at DESC`,
|
||||||
[patient]
|
[patient]
|
||||||
@@ -42,14 +42,14 @@ router.get('/', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.get('/:id', async (req, res) => {
|
router.get('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const gto = await db.get('SELECT * FROM gtos WHERE id = ?', [req.params.id]);
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [req.params.id]);
|
||||||
if (!gto) return res.status(404).json({ error: 'GTO não encontrada' });
|
if (!gto) return res.status(404).json({ error: 'GTO não encontrada' });
|
||||||
|
|
||||||
const images = await db.all(
|
const images = await db.all(
|
||||||
`SELECT i.*, gi.id as link_id
|
`SELECT i.*, gi.id as link_id
|
||||||
FROM gto_images gi
|
FROM gto_images gi
|
||||||
JOIN images i ON i.id = gi.image_id
|
JOIN images i ON i.id = gi.image_id
|
||||||
WHERE gi.gto_id = ?
|
WHERE gi.gto_id = $1
|
||||||
ORDER BY gi.created_at DESC`,
|
ORDER BY gi.created_at DESC`,
|
||||||
[req.params.id]
|
[req.params.id]
|
||||||
);
|
);
|
||||||
@@ -71,10 +71,10 @@ router.post('/', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'gto_number e patient_name são obrigatórios' });
|
return res.status(400).json({ error: 'gto_number e patient_name são obrigatórios' });
|
||||||
}
|
}
|
||||||
const result = await db.run(
|
const result = await db.run(
|
||||||
`INSERT INTO gtos (gto_number, patient_name, description) VALUES (?, ?, ?)`,
|
`INSERT INTO gtos (gto_number, patient_name, description) VALUES ($1, $2, $3) RETURNING id`,
|
||||||
[gto_number.trim(), patient_name.trim(), description || '']
|
[gto_number.trim(), patient_name.trim(), description || '']
|
||||||
);
|
);
|
||||||
const gto = await db.get('SELECT * FROM gtos WHERE id = ?', [result.lastID]);
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [result.lastID]);
|
||||||
res.status(201).json(gto);
|
res.status(201).json(gto);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao criar GTO:', err);
|
console.error('Erro ao criar GTO:', err);
|
||||||
@@ -89,10 +89,10 @@ router.put('/:id', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { gto_number, description } = req.body;
|
const { gto_number, description } = req.body;
|
||||||
await db.run(
|
await db.run(
|
||||||
`UPDATE gtos SET gto_number = ?, description = ? WHERE id = ?`,
|
`UPDATE gtos SET gto_number = $1, description = $2 WHERE id = $3`,
|
||||||
[gto_number, description || '', req.params.id]
|
[gto_number, description || '', req.params.id]
|
||||||
);
|
);
|
||||||
const gto = await db.get('SELECT * FROM gtos WHERE id = ?', [req.params.id]);
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [req.params.id]);
|
||||||
res.json(gto);
|
res.json(gto);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
@@ -104,8 +104,8 @@ router.put('/:id', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.delete('/:id', async (req, res) => {
|
router.delete('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await db.run('DELETE FROM gto_images WHERE gto_id = ?', [req.params.id]);
|
await db.run('DELETE FROM gto_images WHERE gto_id = $1', [req.params.id]);
|
||||||
await db.run('DELETE FROM gtos WHERE id = ?', [req.params.id]);
|
await db.run('DELETE FROM gtos WHERE id = $1', [req.params.id]);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
@@ -123,13 +123,13 @@ router.post('/:id/images', async (req, res) => {
|
|||||||
|
|
||||||
// Verificar se já está vinculada
|
// Verificar se já está vinculada
|
||||||
const existing = await db.get(
|
const existing = await db.get(
|
||||||
'SELECT id FROM gto_images WHERE gto_id = ? AND image_id = ?',
|
'SELECT id FROM gto_images WHERE gto_id = $1 AND image_id = $2',
|
||||||
[req.params.id, image_id]
|
[req.params.id, image_id]
|
||||||
);
|
);
|
||||||
if (existing) return res.status(409).json({ error: 'Imagem já vinculada a esta GTO' });
|
if (existing) return res.status(409).json({ error: 'Imagem já vinculada a esta GTO' });
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
'INSERT INTO gto_images (gto_id, image_id) VALUES (?, ?)',
|
'INSERT INTO gto_images (gto_id, image_id) VALUES ($1, $2) RETURNING id',
|
||||||
[req.params.id, image_id]
|
[req.params.id, image_id]
|
||||||
);
|
);
|
||||||
res.status(201).json({ success: true, message: 'Imagem vinculada à GTO' });
|
res.status(201).json({ success: true, message: 'Imagem vinculada à GTO' });
|
||||||
@@ -145,7 +145,7 @@ router.post('/:id/images', async (req, res) => {
|
|||||||
router.delete('/:id/images/:imageId', async (req, res) => {
|
router.delete('/:id/images/:imageId', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await db.run(
|
await db.run(
|
||||||
'DELETE FROM gto_images WHERE gto_id = ? AND image_id = ?',
|
'DELETE FROM gto_images WHERE gto_id = $1 AND image_id = $2',
|
||||||
[req.params.id, req.params.imageId]
|
[req.params.id, req.params.imageId]
|
||||||
);
|
);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
|
|||||||
@@ -14,17 +14,19 @@ router.get('/', async (req, res) => {
|
|||||||
const search = req.query.search || '';
|
const search = req.query.search || '';
|
||||||
const clientName = req.query.client || '';
|
const clientName = req.query.client || '';
|
||||||
|
|
||||||
let query = `SELECT * FROM images WHERE enabled = ?`;
|
let query = `SELECT * FROM images WHERE enabled = $1`;
|
||||||
let params = [showDisabled ? 0 : 1];
|
let params = [showDisabled ? 0 : 1];
|
||||||
|
let paramIndex = 2;
|
||||||
|
|
||||||
if (clientName) {
|
if (clientName) {
|
||||||
query += ` AND client_name = ?`;
|
query += ` AND client_name = $${paramIndex++}`;
|
||||||
params.push(clientName);
|
params.push(clientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
query += ` AND (patient_name LIKE ? OR image_guid LIKE ? OR filename LIKE ?)`;
|
query += ` AND (patient_name LIKE $${paramIndex} OR image_guid LIKE $${paramIndex + 1} OR filename LIKE $${paramIndex + 2})`;
|
||||||
params.push(`%${search}%`, `%${search}%`, `%${search}%`);
|
params.push(`%${search}%`, `%${search}%`, `%${search}%`);
|
||||||
|
paramIndex += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` ORDER BY created_at DESC LIMIT 100`;
|
query += ` ORDER BY created_at DESC LIMIT 100`;
|
||||||
@@ -47,17 +49,19 @@ router.get('/patients', async (req, res) => {
|
|||||||
const clientName = req.query.client || '';
|
const clientName = req.query.client || '';
|
||||||
const enabledVal = showDisabled ? 0 : 1;
|
const enabledVal = showDisabled ? 0 : 1;
|
||||||
|
|
||||||
let where = `WHERE enabled = ${enabledVal}`;
|
let paramIndex = 1;
|
||||||
let params = [];
|
let where = `WHERE enabled = $${paramIndex++}`;
|
||||||
|
let params = [enabledVal];
|
||||||
|
|
||||||
if (clientName) {
|
if (clientName) {
|
||||||
where += ` AND client_name = ?`;
|
where += ` AND client_name = $${paramIndex++}`;
|
||||||
params.push(clientName);
|
params.push(clientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
where += ` AND (patient_name LIKE ? OR image_guid LIKE ?)`;
|
where += ` AND (patient_name LIKE $${paramIndex} OR image_guid LIKE $${paramIndex + 1})`;
|
||||||
params.push(`%${search}%`, `%${search}%`);
|
params.push(`%${search}%`, `%${search}%`);
|
||||||
|
paramIndex += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group by patient_name — pick thumbnail from most recent image
|
// Group by patient_name — pick thumbnail from most recent image
|
||||||
@@ -102,7 +106,7 @@ router.get('/by-patient', async (req, res) => {
|
|||||||
|
|
||||||
const images = await db.all(
|
const images = await db.all(
|
||||||
`SELECT * FROM images
|
`SELECT * FROM images
|
||||||
WHERE patient_name = ? AND enabled = ?
|
WHERE patient_name = $1 AND enabled = $2
|
||||||
ORDER BY created_at DESC`,
|
ORDER BY created_at DESC`,
|
||||||
[patientName, showDisabled ? 0 : 1]
|
[patientName, showDisabled ? 0 : 1]
|
||||||
);
|
);
|
||||||
@@ -119,7 +123,7 @@ router.get('/by-patient', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.get('/:id', async (req, res) => {
|
router.get('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]);
|
const image = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return res.status(404).json({ error: 'Imagem não encontrada' });
|
return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
}
|
}
|
||||||
@@ -155,14 +159,14 @@ router.get('/stats', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.get('/:id/transformations', async (req, res) => {
|
router.get('/:id/transformations', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]);
|
const image = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return res.status(404).json({ error: 'Imagem não encontrada' });
|
return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buscar a imagem original se esta é uma transformação
|
// Buscar a imagem original se esta é uma transformação
|
||||||
const originalId = image.original_image_id || image.id;
|
const originalId = image.original_image_id || image.id;
|
||||||
const originalImage = await db.get('SELECT * FROM images WHERE id = ?', [originalId]);
|
const originalImage = await db.get('SELECT * FROM images WHERE id = $1', [originalId]);
|
||||||
|
|
||||||
const originalPath = path.join(__dirname, '../uploads', originalImage.filename);
|
const originalPath = path.join(__dirname, '../uploads', originalImage.filename);
|
||||||
let originalBuffer;
|
let originalBuffer;
|
||||||
@@ -217,7 +221,7 @@ router.get('/:id/transformations', async (req, res) => {
|
|||||||
router.post('/:id/transform', async (req, res) => {
|
router.post('/:id/transform', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { rotation, flipH, flipV, remark } = req.body;
|
const { rotation, flipH, flipV, remark } = req.body;
|
||||||
const originalImage = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]);
|
const originalImage = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
|
||||||
|
|
||||||
if (!originalImage) {
|
if (!originalImage) {
|
||||||
return res.status(404).json({ error: 'Imagem não encontrada' });
|
return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
@@ -225,7 +229,7 @@ router.post('/:id/transform', async (req, res) => {
|
|||||||
|
|
||||||
// Buscar a imagem original real se esta é uma transformação
|
// Buscar a imagem original real se esta é uma transformação
|
||||||
const realOriginalId = originalImage.original_image_id || originalImage.id;
|
const realOriginalId = originalImage.original_image_id || originalImage.id;
|
||||||
const realOriginal = await db.get('SELECT * FROM images WHERE id = ?', [realOriginalId]);
|
const realOriginal = await db.get('SELECT * FROM images WHERE id = $1', [realOriginalId]);
|
||||||
|
|
||||||
// Processar a imagem
|
// Processar a imagem
|
||||||
const originalPath = path.join(__dirname, '../uploads', realOriginal.filename);
|
const originalPath = path.join(__dirname, '../uploads', realOriginal.filename);
|
||||||
@@ -250,8 +254,8 @@ router.post('/:id/transform', async (req, res) => {
|
|||||||
const processedPath = path.join(__dirname, '../processed', processedFilename);
|
const processedPath = path.join(__dirname, '../processed', processedFilename);
|
||||||
await fs.writeFile(processedPath, processed);
|
await fs.writeFile(processedPath, processed);
|
||||||
|
|
||||||
// Desabilitar a imagem atual para que não fique duplicada na interface
|
// Desabilitar a imagem active atual para que não fique duplicada na interface
|
||||||
await db.run('UPDATE images SET enabled = 0 WHERE id = ?', [req.params.id]);
|
await db.run('UPDATE images SET enabled = 0 WHERE id = $1', [req.params.id]);
|
||||||
|
|
||||||
const newRemark = remark !== undefined ? remark : originalImage.remark;
|
const newRemark = remark !== undefined ? remark : originalImage.remark;
|
||||||
|
|
||||||
@@ -260,7 +264,7 @@ router.post('/:id/transform', async (req, res) => {
|
|||||||
`INSERT INTO images (
|
`INSERT INTO images (
|
||||||
image_guid, patient_name, client_name, original_filename, filename,
|
image_guid, patient_name, client_name, original_filename, filename,
|
||||||
enabled, rotation, flip_horizontal, flip_vertical, original_image_id, created_at, remark, doctor
|
enabled, rotation, flip_horizontal, flip_vertical, original_image_id, created_at, remark, doctor
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
||||||
[
|
[
|
||||||
`${realOriginal.image_guid}_${timestamp}`,
|
`${realOriginal.image_guid}_${timestamp}`,
|
||||||
realOriginal.patient_name,
|
realOriginal.patient_name,
|
||||||
@@ -298,11 +302,11 @@ router.put('/:id/patient-info', async (req, res) => {
|
|||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
`UPDATE images SET
|
`UPDATE images SET
|
||||||
patient_name_resumed = ?,
|
patient_name_resumed = $1,
|
||||||
tooth_number = ?,
|
tooth_number = $2,
|
||||||
tooth_side = ?,
|
tooth_side = $3,
|
||||||
enabled = 1
|
enabled = 1
|
||||||
WHERE id = ?`,
|
WHERE id = $4`,
|
||||||
[patientNameResumed, toothNumber, toothSide, req.params.id]
|
[patientNameResumed, toothNumber, toothSide, req.params.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -317,7 +321,7 @@ router.put('/:id/patient-info', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.get('/:id/download', async (req, res) => {
|
router.get('/:id/download', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]);
|
const image = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return res.status(404).json({ error: 'Imagem não encontrada' });
|
return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
}
|
}
|
||||||
@@ -344,7 +348,7 @@ router.get('/:id/download', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.delete('/:id', async (req, res) => {
|
router.delete('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]);
|
const image = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return res.status(404).json({ error: 'Imagem não encontrada' });
|
return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
}
|
}
|
||||||
@@ -366,7 +370,7 @@ router.delete('/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apagar do banco
|
// Apagar do banco
|
||||||
await db.run('DELETE FROM images WHERE id = ?', [req.params.id]);
|
await db.run('DELETE FROM images WHERE id = $1', [req.params.id]);
|
||||||
|
|
||||||
res.json({ success: true, message: 'Imagem apagada com sucesso' });
|
res.json({ success: true, message: 'Imagem apagada com sucesso' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -380,13 +384,13 @@ router.delete('/:id', async (req, res) => {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
router.put('/:id/toggle-enabled', async (req, res) => {
|
router.put('/:id/toggle-enabled', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]);
|
const image = await db.get('SELECT * FROM images WHERE id = $1', [req.params.id]);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return res.status(404).json({ error: 'Imagem não encontrada' });
|
return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const newEnabled = image.enabled ? 0 : 1;
|
const newEnabled = image.enabled ? 0 : 1;
|
||||||
await db.run('UPDATE images SET enabled = ? WHERE id = ?', [newEnabled, req.params.id]);
|
await db.run('UPDATE images SET enabled = $1 WHERE id = $2', [newEnabled, req.params.id]);
|
||||||
|
|
||||||
res.json({ success: true, enabled: newEnabled === 1 });
|
res.json({ success: true, enabled: newEnabled === 1 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
// Caminho do banco de dados oficial do Dental Sensor
|
|
||||||
const DENTAL_DB_PATH = 'C:/ProgramData/RF/Dental Sensor/data';
|
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// Conexão LAZY — abre o banco só quando necessário e fecha logo
|
|
||||||
// após, evitando conflitos de lock com o banco principal do servidor
|
|
||||||
// ================================================================
|
|
||||||
function openDentalDb() {
|
|
||||||
const { DatabaseSync } = require('node:sqlite');
|
|
||||||
return new DatabaseSync(DENTAL_DB_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// POST /api/patients - Criar novo paciente no Dental Sensor
|
|
||||||
// ================================================================
|
|
||||||
router.post('/', (req, res) => {
|
|
||||||
let db;
|
|
||||||
try {
|
|
||||||
db = openDentalDb();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Erro ao conectar ao banco do Dental Sensor:', error.message);
|
|
||||||
return res.status(500).json({
|
|
||||||
error: 'Não foi possível conectar ao banco do Dental Sensor. Verifique se o software está instalado.',
|
|
||||||
detail: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { firstName, lastName, doctor, birthday, gender, phone, email, address, zipCode, remark } = req.body;
|
|
||||||
|
|
||||||
if (!firstName || !lastName) {
|
|
||||||
if (db) try { db.close(); } catch (_) {}
|
|
||||||
return res.status(400).json({ error: 'Nome e Sobrenome são obrigatórios.' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedBirthday = birthday ? `${birthday} 00:00:00` : null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const insertStmt = db.prepare(`
|
|
||||||
INSERT INTO Patients (
|
|
||||||
FirstName, LastName, Doctor, Birthday, Gender, Phone, Email, Address, ZipCode, Remark, LastActTime, VFlag
|
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), 1)
|
|
||||||
`);
|
|
||||||
|
|
||||||
const result = insertStmt.run(
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
doctor || '',
|
|
||||||
formattedBirthday,
|
|
||||||
gender !== undefined ? parseInt(gender, 10) : 0,
|
|
||||||
phone || '',
|
|
||||||
email || '',
|
|
||||||
address || '',
|
|
||||||
zipCode || '',
|
|
||||||
remark || ''
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(201).json({
|
|
||||||
success: true,
|
|
||||||
patientId: result.lastInsertRowid,
|
|
||||||
message: 'Paciente cadastrado com sucesso no Dental Sensor'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao inserir paciente:', error);
|
|
||||||
res.status(500).json({ error: 'Erro ao cadastrar paciente: ' + error.message });
|
|
||||||
} finally {
|
|
||||||
// SEMPRE fechar o banco após o uso para liberar o lock
|
|
||||||
if (db) {
|
|
||||||
try { db.close(); } catch (_) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// GET /api/patients - Listar pacientes do Dental Sensor (opcional)
|
|
||||||
// ================================================================
|
|
||||||
router.get('/', (req, res) => {
|
|
||||||
let db;
|
|
||||||
try {
|
|
||||||
db = openDentalDb();
|
|
||||||
const search = req.query.search || '';
|
|
||||||
let stmt;
|
|
||||||
let rows;
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
stmt = db.prepare(`
|
|
||||||
SELECT ID, FirstName, LastName, Doctor, Birthday, Gender, Phone, Remark
|
|
||||||
FROM Patients
|
|
||||||
WHERE (FirstName LIKE ? OR LastName LIKE ?)
|
|
||||||
AND VFlag = 1
|
|
||||||
ORDER BY LastActTime DESC
|
|
||||||
LIMIT 100
|
|
||||||
`);
|
|
||||||
rows = stmt.all(`%${search}%`, `%${search}%`);
|
|
||||||
} else {
|
|
||||||
stmt = db.prepare(`
|
|
||||||
SELECT ID, FirstName, LastName, Doctor, Birthday, Gender, Phone, Remark
|
|
||||||
FROM Patients
|
|
||||||
WHERE VFlag = 1
|
|
||||||
ORDER BY LastActTime DESC
|
|
||||||
LIMIT 100
|
|
||||||
`);
|
|
||||||
rows = stmt.all();
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao listar pacientes do Dental Sensor:', error.message);
|
|
||||||
res.status(500).json({ error: 'Erro ao consultar banco do Dental Sensor: ' + error.message });
|
|
||||||
} finally {
|
|
||||||
if (db) {
|
|
||||||
try { db.close(); } catch (_) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@@ -18,7 +18,7 @@ router.delete('/factory-reset', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Validar usuário e senha
|
// 1. Validar usuário e senha
|
||||||
const user = await db.get('SELECT * FROM users WHERE id = ?', [userId]);
|
const user = await db.get('SELECT * FROM users WHERE id = $1', [userId]);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ error: 'Usuário não encontrado' });
|
return res.status(404).json({ error: 'Usuário não encontrado' });
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-10
@@ -14,7 +14,6 @@ const jwt = require('jsonwebtoken');
|
|||||||
const db = require('./database');
|
const db = require('./database');
|
||||||
const authRoutes = require('./routes/auth');
|
const authRoutes = require('./routes/auth');
|
||||||
const imageRoutes = require('./routes/images');
|
const imageRoutes = require('./routes/images');
|
||||||
const patientsRouter = require('./routes/patients');
|
|
||||||
const gtosRouter = require('./routes/gtos');
|
const gtosRouter = require('./routes/gtos');
|
||||||
const systemRoutes = require('./routes/system');
|
const systemRoutes = require('./routes/system');
|
||||||
const adminClinicsRoutes = require('./routes/admin-clinics');
|
const adminClinicsRoutes = require('./routes/admin-clinics');
|
||||||
@@ -60,7 +59,6 @@ app.use(cookieParser());
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
// ROTAS DA API
|
// ROTAS DA API
|
||||||
// ================================================================
|
// ================================================================
|
||||||
app.use('/api/patients', patientsRouter);
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// ROTAS PÚBLICAS (SEMPRE ACESSÍVEIS)
|
// ROTAS PÚBLICAS (SEMPRE ACESSÍVEIS)
|
||||||
@@ -104,7 +102,7 @@ app.get('/api/clients/check-name', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Verificar se existe no banco de dados na tabela images (busca case-insensitive)
|
// 1. Verificar se existe no banco de dados na tabela images (busca case-insensitive)
|
||||||
const row = await db.get('SELECT COUNT(*) as count FROM images WHERE LOWER(client_name) = LOWER(?)', [name]);
|
const row = await db.get('SELECT COUNT(*) as count FROM images WHERE LOWER(client_name) = LOWER($1)', [name]);
|
||||||
const existsInDb = row && row.count > 0;
|
const existsInDb = row && row.count > 0;
|
||||||
|
|
||||||
// 2. Verificar se existe algum cliente atualmente conectado com esse nome (desconsiderando o próprio clientId)
|
// 2. Verificar se existe algum cliente atualmente conectado com esse nome (desconsiderando o próprio clientId)
|
||||||
@@ -348,10 +346,10 @@ app.post('/api/install', async (req, res) => {
|
|||||||
const passwordHash = await hashPassword(admin.password);
|
const passwordHash = await hashPassword(admin.password);
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO users (username, email, password_hash, created_at)
|
`INSERT INTO users (username, email, password_hash, created_at)
|
||||||
VALUES (?, ?, ?, NOW())
|
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON CONFLICT (username) DO UPDATE SET
|
||||||
email = VALUES(email),
|
email = EXCLUDED.email,
|
||||||
password_hash = VALUES(password_hash)`,
|
password_hash = EXCLUDED.password_hash`,
|
||||||
[admin.username, admin.email, passwordHash]
|
[admin.username, admin.email, passwordHash]
|
||||||
);
|
);
|
||||||
console.log('✅ Usuário admin criado:', admin.username);
|
console.log('✅ Usuário admin criado:', admin.username);
|
||||||
@@ -749,7 +747,7 @@ io.on('connection', (socket) => {
|
|||||||
|
|
||||||
// Verificar se a imagem já foi inserida no banco
|
// Verificar se a imagem já foi inserida no banco
|
||||||
const existing = await db.get(
|
const existing = await db.get(
|
||||||
"SELECT id, filename FROM images WHERE original_filename = ? AND enabled = 1 LIMIT 1",
|
"SELECT id, filename FROM images WHERE original_filename = $1 AND enabled = 1 LIMIT 1",
|
||||||
[data.metadata.fileName]
|
[data.metadata.fileName]
|
||||||
);
|
);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -806,7 +804,7 @@ io.on('connection', (socket) => {
|
|||||||
doctor,
|
doctor,
|
||||||
remark,
|
remark,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||||
[
|
[
|
||||||
data.metadata.guid,
|
data.metadata.guid,
|
||||||
data.patientData.name || 'Paciente não identificado',
|
data.patientData.name || 'Paciente não identificado',
|
||||||
@@ -873,7 +871,7 @@ io.on('connection', (socket) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existing = await db.get(
|
const existing = await db.get(
|
||||||
"SELECT id, filename FROM images WHERE original_filename = ? AND enabled = 1 LIMIT 1",
|
"SELECT id, filename FROM images WHERE original_filename = $1 AND enabled = 1 LIMIT 1",
|
||||||
[data.fileName]
|
[data.fileName]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ async function loadConfigFromDb() {
|
|||||||
let hasSettings = false;
|
let hasSettings = false;
|
||||||
|
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
const row = await db.get('SELECT value FROM settings WHERE key = ?', [k]);
|
const row = await db.get('SELECT value FROM settings WHERE key = $1', [k]);
|
||||||
if (row) {
|
if (row) {
|
||||||
// Mapear de snake_case do banco para camelCase do config
|
// Mapear de snake_case do banco para camelCase do config
|
||||||
const camelKey = k.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
const camelKey = k.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
||||||
@@ -146,7 +146,7 @@ async function updateConfig(newConfig) {
|
|||||||
for (const [key, val] of Object.entries(settingsMap)) {
|
for (const [key, val] of Object.entries(settingsMap)) {
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO settings (key, value)
|
`INSERT INTO settings (key, value)
|
||||||
VALUES (?, ?)
|
VALUES ($1, $2)
|
||||||
ON CONFLICT (key)
|
ON CONFLICT (key)
|
||||||
DO UPDATE SET value = EXCLUDED.value`,
|
DO UPDATE SET value = EXCLUDED.value`,
|
||||||
[key, val]
|
[key, val]
|
||||||
@@ -250,7 +250,7 @@ async function getImageBuffer(filename, clientName = null, patientName = null, i
|
|||||||
if (!cName || !pName) {
|
if (!cName || !pName) {
|
||||||
try {
|
try {
|
||||||
const db = require('./database');
|
const db = require('./database');
|
||||||
const row = await db.get('SELECT client_name, patient_name FROM images WHERE filename = ? OR thumb_filename = ? LIMIT 1', [filename, filename]);
|
const row = await db.get('SELECT client_name, patient_name FROM images WHERE filename = $1 OR thumb_filename = $2 LIMIT 1', [filename, filename]);
|
||||||
if (row) {
|
if (row) {
|
||||||
cName = row.client_name;
|
cName = row.client_name;
|
||||||
pName = row.patient_name;
|
pName = row.patient_name;
|
||||||
|
|||||||
Reference in New Issue
Block a user