Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ Principais endpoints:
- `POST orders` – cria pedido `{ name, phone, address, table, type, items, total, status?, payment? }`.
- `PATCH orders/:id/status` – atualiza status.

### Resetar o banco com os dados padrão

- As credenciais de admin usam os mesmos valores de `PGUSER`/`PGPASSWORD` (ou `ADMIN_USER`/`ADMIN_PASSWORD`). Ajuste as variáveis de ambiente se o erro de conexão indicar senha incorreta.
- Para restaurar as tabelas e dados de exemplo, execute:

```bash
cd server
npm install
npm run db:reset
```

- Também é possível disparar o reset via API autenticada: `POST /admin/reset-database` com `{ "username": "<PGUSER>", "password": "<PGPASSWORD>" }`.

### pgAdmin (local)

Instale pgAdmin localmente ou utilize a imagem oficial. Para conectar ao banco local, crie um novo servidor no pgAdmin com:
Expand Down
25 changes: 25 additions & 0 deletions server/databaseReset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { pool } from './db.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export async function resetDatabase() {
const schemaPath = path.join(__dirname, 'schema.sql');
const sql = fs.readFileSync(schemaPath, 'utf8');
await pool.query(sql);
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
resetDatabase()
.then(() => {
console.log('Banco resetado e populado com dados iniciais.');
})
.catch((err) => {
console.error('Falha ao resetar banco:', err.message);
process.exitCode = 1;
})
.finally(() => pool.end());
}
203 changes: 172 additions & 31 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import express from "express";
import cors from "cors";
import { pool } from "./db.js";
import { resetDatabase } from "./databaseReset.js";

const app = express();
const port = process.env.PORT || 4000;

app.use(cors());
app.use(express.json());

const validUser = process.env.ADMIN_USER || process.env.PGUSER || "postgres";
const validPassword =
process.env.ADMIN_PASSWORD || process.env.PGPASSWORD || "postgres";

const isValidAdmin = (username, password) =>
username === validUser && password === validPassword;

const mapOrderRow = (row) => ({
id: row.id,
name: row.customer_name,
Expand All @@ -30,36 +38,86 @@ const mapCustomerRow = (row) => ({
phone: row.phone,
});

app.get("/products", async (_, res) => {
const defaultOwnerId = process.env.DEFAULT_OWNER_ID || "espetinhodatony";

const getOwnerId = (req) =>
(req.headers["x-owner-id"] || req.query.ownerId || req.body?.ownerId || "")
.toString()
.trim();

const requireOwner = (req, res) => {
const ownerId = getOwnerId(req);
if (!ownerId) {
res.status(400).json({ error: "ownerId é obrigatório" });
return null;
}
return ownerId;
};

app.get("/products", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const result = await pool.query(
"SELECT * FROM products ORDER BY category, name"
"SELECT * FROM products WHERE owner_id = $1 ORDER BY category, name",
[ownerId]
);
res.json(result.rows);
});

app.post("/login", (req, res) => {
const { username, password } = req.body || {};
const { username, password, espetoId } = req.body || {};

const validUser =
process.env.ADMIN_USER || process.env.PGUSER || "postgres";
const validPassword =
process.env.ADMIN_PASSWORD || process.env.PGPASSWORD || "postgres";
if (isValidAdmin(username, password)) {
const ownerId = (espetoId || defaultOwnerId).trim();
if (!ownerId) {
res.status(400).json({ message: "Informe o ID do espeto" });
return;
}

if (username === validUser && password === validPassword) {
res.json({ token: "ok", name: "Administrador" });
res.json({
token: "ok",
name: process.env.ADMIN_NAME || "Administrador",
ownerId,
});
return;
}

res.status(401).json({ message: "Credenciais inválidas" });
});

app.post("/admin/reset-database", async (req, res) => {
const { username, password } = req.body || {};

if (!isValidAdmin(username, password)) {
res.status(401).json({ message: "Credenciais inválidas" });
return;
}

try {
await resetDatabase();
res.json({
message: "Banco resetado e populado com dados iniciais.",
});
} catch (error) {
console.error("Erro ao resetar banco", error);
res.status(500).json({
error: "Falha ao resetar banco",
detail: error.message,
});
}
});

app.get(["/customers", "/api/customers"], async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const search = (req.query.search || "").toLowerCase();
let query = "SELECT * FROM customers";
const params = [];
let query = "SELECT * FROM customers WHERE owner_id = $1";
const params = [ownerId];

if (search) {
query += " WHERE LOWER(name) LIKE $1";
query += " AND LOWER(name) LIKE $2";
params.push(`%${search}%`);
}

Expand All @@ -70,6 +128,9 @@ app.get(["/customers", "/api/customers"], async (req, res) => {
});

app.post("/products", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const {
name,
price,
Expand All @@ -80,14 +141,25 @@ app.post("/products", async (req, res) => {
} = req.body;

const query =
"INSERT INTO products (name, price, category, description, active, image_url) VALUES ($1,$2,$3,$4,$5,$6) RETURNING *";
"INSERT INTO products (owner_id, name, price, category, description, active, image_url) VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *";

const values = [name, price, category, description, active, imageUrl];
const values = [
ownerId,
name,
price,
category,
description,
active,
imageUrl,
];
const result = await pool.query(query, values);
res.status(201).json(result.rows[0]);
});

app.put("/products/:id", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const { id } = req.params;
const {
name,
Expand All @@ -99,34 +171,70 @@ app.put("/products/:id", async (req, res) => {
} = req.body;

const query =
"UPDATE products SET name = $1, price = $2, category = $3, description = $4, active = $5, image_url = $6 WHERE id = $7 RETURNING *";
"UPDATE products SET name = $1, price = $2, category = $3, description = $4, active = $5, image_url = $6 WHERE id = $7 AND owner_id = $8 RETURNING *";

const values = [name, price, category, description, active, imageUrl, id];
const values = [
name,
price,
category,
description,
active,
imageUrl,
id,
ownerId,
];
const result = await pool.query(query, values);
if (!result.rowCount) {
res.status(404).json({ error: "Produto não encontrado para este espeto" });
return;
}
res.json(result.rows[0]);
});

app.delete("/products/:id", async (req, res) => {
await pool.query("DELETE FROM products WHERE id = $1", [req.params.id]);
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const result = await pool.query(
"DELETE FROM products WHERE id = $1 AND owner_id = $2",
[req.params.id, ownerId]
);

if (!result.rowCount) {
res.status(404).json({ error: "Produto não encontrado para este espeto" });
return;
}

res.status(204).send();
});

app.get("/orders", async (_, res) => {
app.get("/orders", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const result = await pool.query(
"SELECT * FROM orders ORDER BY created_at DESC"
"SELECT * FROM orders WHERE owner_id = $1 ORDER BY created_at DESC",
[ownerId]
);
res.json(result.rows.map(mapOrderRow));
});

app.get("/orders/queue", async (_, res) => {
app.get("/orders/queue", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const result = await pool.query(
"SELECT * FROM orders WHERE status IN ('pending', 'preparing') ORDER BY created_at ASC"
"SELECT * FROM orders WHERE owner_id = $1 AND status IN ('pending', 'preparing') ORDER BY created_at ASC",
[ownerId]
);
res.json(result.rows.map(mapOrderRow));
});

app.post("/orders", async (req, res) => {
console.log("DEBUG BODY:", req.body);
const ownerId = requireOwner(req, res);
if (!ownerId) return;

try {
const {
name,
Expand All @@ -144,6 +252,7 @@ app.post("/orders", async (req, res) => {

const query = `
INSERT INTO orders (
owner_id,
customer_name,
phone,
address,
Expand All @@ -156,11 +265,12 @@ app.post("/orders", async (req, res) => {
created_at,
date_string
)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
RETURNING *;
`;

const values = [
ownerId,
name,
phone,
address ?? null,
Expand All @@ -178,11 +288,11 @@ app.post("/orders", async (req, res) => {

if (name) {
await pool.query(
`INSERT INTO customers (name, phone, updated_at)
VALUES ($1, $2, NOW())
ON CONFLICT (name)
`INSERT INTO customers (owner_id, name, phone, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (owner_id, name)
DO UPDATE SET phone = COALESCE(EXCLUDED.phone, customers.phone), updated_at = NOW();`,
[name, phone ?? null]
[ownerId, name, phone ?? null]
);
}

Expand All @@ -194,16 +304,28 @@ app.post("/orders", async (req, res) => {
});

app.patch("/orders/:id/status", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const { id } = req.params;
const { status } = req.body;
const result = await pool.query(
"UPDATE orders SET status = $1 WHERE id = $2 RETURNING *",
[status, id]
"UPDATE orders SET status = $1 WHERE id = $2 AND owner_id = $3 RETURNING *",
[status, id, ownerId]
);

if (!result.rowCount) {
res.status(404).json({ error: "Pedido não encontrado para este espeto" });
return;
}

res.json(mapOrderRow(result.rows[0]));
});

app.patch("/orders/:id", async (req, res) => {
const ownerId = requireOwner(req, res);
if (!ownerId) return;

const { id } = req.params;
const { items, total } = req.body;

Expand All @@ -219,11 +341,30 @@ app.patch("/orders/:id", async (req, res) => {
: sanitizedItems.reduce((sum, item) => sum + (item.price || 0) * (item.qty || 0), 0);

const result = await pool.query(
"UPDATE orders SET items = $1, total = $2 WHERE id = $3 RETURNING *",
[JSON.stringify(sanitizedItems), computedTotal, id]
"UPDATE orders SET items = $1, total = $2 WHERE id = $3 AND owner_id = $4 RETURNING *",
[JSON.stringify(sanitizedItems), computedTotal, id, ownerId]
);

if (!result.rowCount) {
res.status(404).json({ error: "Pedido não encontrado para este espeto" });
return;
}

res.json(mapOrderRow(result.rows[0]));
});

app.listen(port, () => console.log(`API escutando na porta ${port}`));
const startServer = async () => {
try {
await pool.query("SELECT 1");
} catch (error) {
console.error(
"Falha ao conectar no banco. Verifique PGUSER/PGPASSWORD/PGDATABASE.",
error.message
);
process.exit(1);
}

app.listen(port, () => console.log(`API escutando na porta ${port}`));
};

startServer();
3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
"start": "node index.js",
"db:reset": "node databaseReset.js"
},
"dependencies": {
"cors": "^2.8.5",
Expand Down
Loading